summaryrefslogtreecommitdiff
path: root/includes/fragments
diff options
context:
space:
mode:
Diffstat (limited to 'includes/fragments')
-rw-r--r--includes/fragments/edit-private.inc142
-rw-r--r--includes/fragments/edit.inc142
-rw-r--r--includes/fragments/member.inc150
-rw-r--r--includes/fragments/metadata.inc169
-rw-r--r--includes/fragments/sysedit.inc153
-rw-r--r--includes/fragments/system.inc52
6 files changed, 808 insertions, 0 deletions
diff --git a/includes/fragments/edit-private.inc b/includes/fragments/edit-private.inc
new file mode 100644
index 0000000..998dfdd
--- /dev/null
+++ b/includes/fragments/edit-private.inc
@@ -0,0 +1,142 @@
+<?php global $system; global $systemCommonName; global $systemID; global $member; global $memberData; global $memberCommonName; global $memberID; $title = "Editing " . $memberCommonName . " (private page) · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc';
+
+if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json")) {
+ $metadata = parseMetadata(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json"), true));
+}
+
+?>
+
+<br>
+<div class="container">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/fullbanner.inc"; ?>
+
+ <p class="text-muted" id="page-content">
+ <span id="editor-save-status" class="text-muted">Saved</span> · <span id="editor-size"><?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html") ? strlen(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html")) : "0" ?></span> bytes · <a href="/<?= $memberData["name"] ?>">View page</a>
+ </p>
+
+ <!--suppress HtmlFormInputWithoutLabel -->
+ <textarea id="page-editor">
+ <?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html") ? file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html") : "" ?>
+ </textarea>
+
+ <script src="/assets/editor/editor.js"></script>
+ <script>
+ let editor;
+ ClassicEditor
+ .create( document.querySelector( '#page-editor' ), {
+ toolbar: [
+ 'undo', 'redo', '|', 'removeFormat', '|', 'heading', '|', 'fontSize', 'fontColor', 'fontBackgroundColor', 'alignment', '|', 'bold', 'italic', 'underline', 'strikethrough', '|', 'subscript', 'superscript', '|', 'code', '|', 'outdent', 'indent', '|', 'bulletedList', 'numberedList', '|', 'link', 'imageUpload', 'mediaEmbed', 'blockQuote', 'insertTable', 'codeBlock', '|', 'horizontalLine'
+ ]
+ } )
+
+ .then( newEditor => {
+ editor = newEditor;
+ } )
+ .catch( error => {
+ console.error( error );
+ } );
+ </script>
+ <style>
+ :root {
+ --ck-color-base-background: transparent;
+ }
+
+ .ck-toolbar {
+ filter: invert(1);
+ }
+
+ .ck-tooltip__text {
+ color: white !important;
+ }
+
+ .ck-dropdown__panel {
+ background: #ddd !important;
+ }
+
+ .ck-color-grid__tile {
+ filter: invert(1);
+ }
+
+ .ck-balloon-rotator {
+ background-color: #ccc !important;
+ }
+
+ .ck-balloon-panel {
+ filter: invert(1);
+ }
+ </style>
+ <script>
+ let lastSavedData = editor.getData();
+ let lastFetchedData = editor.getData();
+ let timeSinceLastModified = 0;
+ let saving = false;
+
+ async function save() {
+ let data = editor.getData();
+ document.getElementById("editor-save-status").innerHTML = "Saving...";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.add("text-primary");
+ saving = true;
+
+ try {
+ await window.fetch("/api/save-private?system=<?= $systemID ?>&member=<?= $memberID ?>", {
+ method: "POST",
+ body: JSON.stringify({ content: data })
+ });
+ document.getElementById("editor-save-status").innerHTML = "Saved";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.add("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ lastSavedData = data;
+ saving = false;
+ } catch (e) {
+ console.error(e);
+ document.getElementById("editor-save-status").innerHTML = "Failed to save";
+ document.getElementById("editor-save-status").classList.add("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ }
+ }
+
+ document.onclick = async () => {
+ if (saving) return;
+
+ if (editor.getData() !== lastSavedData) {
+ await save();
+ }
+ }
+
+ setInterval(async () => {
+ if (saving) return;
+
+ document.getElementById("editor-size").innerHTML = editor.getData().length;
+
+ if (editor.getData() !== lastSavedData) {
+ document.getElementById("editor-save-status").innerHTML = "Modified";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.add("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+
+ if (editor.getData() !== lastFetchedData) {
+ lastFetchedData = editor.getData();
+ timeSinceLastModified = 0;
+ } else {
+ timeSinceLastModified++;
+ }
+
+ if (timeSinceLastModified > 20) {
+ await save();
+ }
+ } else {
+ timeSinceLastModified = 0;
+ }
+ }, 100)
+ </script>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/footer.inc'; ?> \ No newline at end of file
diff --git a/includes/fragments/edit.inc b/includes/fragments/edit.inc
new file mode 100644
index 0000000..b5617f2
--- /dev/null
+++ b/includes/fragments/edit.inc
@@ -0,0 +1,142 @@
+<?php global $system; global $systemCommonName; global $systemID; global $member; global $memberData; global $memberCommonName; global $memberID; $title = "Editing " . $memberCommonName . " (public page) · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc';
+
+if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json")) {
+ $metadata = parseMetadata(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json"), true));
+}
+
+?>
+
+<br>
+<div class="container">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/fullbanner.inc"; ?>
+
+ <p class="text-muted" id="page-content">
+ <span id="editor-save-status" class="text-muted">Saved</span> · <span id="editor-size"><?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html") ? strlen(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html")) : "0" ?></span> bytes · <a href="/<?= $memberData["name"] ?>">View page</a>
+ </p>
+
+ <!--suppress HtmlFormInputWithoutLabel -->
+ <textarea id="page-editor">
+ <?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID.html") ? file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID.html") : "" ?>
+ </textarea>
+
+ <script src="/assets/editor/editor.js"></script>
+ <script>
+ let editor;
+ ClassicEditor
+ .create( document.querySelector( '#page-editor' ), {
+ toolbar: [
+ 'undo', 'redo', '|', 'removeFormat', '|', 'heading', '|', 'fontSize', 'fontColor', 'fontBackgroundColor', 'alignment', '|', 'bold', 'italic', 'underline', 'strikethrough', '|', 'subscript', 'superscript', '|', 'code', '|', 'outdent', 'indent', '|', 'bulletedList', 'numberedList', '|', 'link', 'imageUpload', 'mediaEmbed', 'blockQuote', 'insertTable', 'codeBlock', '|', 'horizontalLine'
+ ]
+ } )
+
+ .then( newEditor => {
+ editor = newEditor;
+ } )
+ .catch( error => {
+ console.error( error );
+ } );
+ </script>
+ <style>
+ :root {
+ --ck-color-base-background: transparent;
+ }
+
+ .ck-toolbar {
+ filter: invert(1);
+ }
+
+ .ck-tooltip__text {
+ color: white !important;
+ }
+
+ .ck-dropdown__panel {
+ background: #ddd !important;
+ }
+
+ .ck-color-grid__tile {
+ filter: invert(1);
+ }
+
+ .ck-balloon-rotator {
+ background-color: #ccc !important;
+ }
+
+ .ck-balloon-panel {
+ filter: invert(1);
+ }
+ </style>
+ <script>
+ let lastSavedData = editor.getData();
+ let lastFetchedData = editor.getData();
+ let timeSinceLastModified = 0;
+ let saving = false;
+
+ async function save() {
+ let data = editor.getData();
+ document.getElementById("editor-save-status").innerHTML = "Saving...";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.add("text-primary");
+ saving = true;
+
+ try {
+ await window.fetch("/api/save?system=<?= $systemID ?>&member=<?= $memberID ?>", {
+ method: "POST",
+ body: JSON.stringify({ content: data })
+ });
+ document.getElementById("editor-save-status").innerHTML = "Saved";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.add("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ lastSavedData = data;
+ saving = false;
+ } catch (e) {
+ console.error(e);
+ document.getElementById("editor-save-status").innerHTML = "Failed to save";
+ document.getElementById("editor-save-status").classList.add("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ }
+ }
+
+ document.onclick = async () => {
+ if (saving) return;
+
+ if (editor.getData() !== lastSavedData) {
+ await save();
+ }
+ }
+
+ setInterval(async () => {
+ if (saving) return;
+
+ document.getElementById("editor-size").innerHTML = editor.getData().length;
+
+ if (editor.getData() !== lastSavedData) {
+ document.getElementById("editor-save-status").innerHTML = "Modified";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.add("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+
+ if (editor.getData() !== lastFetchedData) {
+ lastFetchedData = editor.getData();
+ timeSinceLastModified = 0;
+ } else {
+ timeSinceLastModified++;
+ }
+
+ if (timeSinceLastModified > 20) {
+ await save();
+ }
+ } else {
+ timeSinceLastModified = 0;
+ }
+ }, 100)
+ </script>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/footer.inc'; ?> \ No newline at end of file
diff --git a/includes/fragments/member.inc b/includes/fragments/member.inc
new file mode 100644
index 0000000..4eed308
--- /dev/null
+++ b/includes/fragments/member.inc
@@ -0,0 +1,150 @@
+<?php global $system; global $systemCommonName; global $systemID; global $member; global $memberData; global $memberCommonName; global $memberID; global $lang; global $pages; global $app;
+
+if ($memberData["name"] === "fusion") {
+ $title = ($memberCommonName === "fusion" ? $lang["member"]["merge"] : $memberCommonName);
+} else {
+ $title = $memberCommonName . " · " . $systemCommonName;
+}
+
+require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc';
+
+$travelling = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/travelling/travelling.json"), true);
+
+if ($memberData["name"] !== "unknown" && $memberData["name"] !== "fusion") {
+ $metadata = parseMetadata(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json"), true));
+}
+
+global $isLoggedIn;
+
+$frontersRaindrops = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/gdapd/fronters.json"), true);
+$frontersCloudburst = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/ynmuc/fronters.json"), true);
+$frontersOther = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/other/fronters.json"), true);
+
+$fusionRaindrops = false;
+
+if ($memberData["name"] === "fusion") {
+ if (in_array("fusion", array_map(function ($i) {
+ return $i["name"];
+ }, $frontersRaindrops["members"]))) {
+ $fusionRaindrops = true;
+
+ $memberData = array_values(array_filter($frontersRaindrops["members"], function ($i) {
+ return $i["name"] === "fusion";
+ }))[0];
+ $memberCommonName = $memberData['display_name'] ?? $memberData['name'];
+ $memberID = $memberData['id'];
+ $system = $systemID === "gdapd" ? "cloudburst" : "raindrops";
+ $systemID = $system === "cloudburst" ? "ynmuc" : "gdapd";
+ }
+}
+
+?>
+
+<div id="member-banner-container" style="width: calc(100% - 300px);height: <?= !isset($memberData["banner"]) ? "33vh" : "65vh" ?>;position: fixed;background-image: url('<?= getAsset($systemID, $memberID, !isset($memberData["banner"]) ? "avatars" : "banners") ?>');background-size: cover;background-position: center; top: 0;">
+ <div id="member-banner-inner" style="height: 100%;width: 100%;background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,.25) 50%, rgba(0,0,0,1) 100%);<?= !isset($memberData["banner"]) ? "backdrop-filter:blur(100px);" : "" ?>"></div>
+</div>
+
+<script>
+ window.onscroll = () => {
+ document.getElementById("member-banner-container").style.height = (<?= !isset($memberData["banner"]) ? "33" : "65" ?> - ((window.scrollY / window.screen.availHeight) * 100)) + "vh";
+ }
+</script>
+
+<br>
+<div class="container">
+ <div id="member-page" style="background-color: rgba(26,26,26,0.8);border-radius: 10px;padding:20px; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);margin-top:<?= !isset($memberData["banner"]) ? "15vh" : "30vh" ?>;<?php if (!file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID.html") && !($isLoggedIn && file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html"))): ?> padding-bottom: 0 !important;<?php endif; ?>">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/fullbanner.inc"; ?>
+
+ <div id="page-content">
+ <?php if ($memberData["name"] === "unknown"): ?>
+ <br>
+ <div class="alert alert-secondary" style="margin-bottom: 0 !important;">
+ <p>Hello there!</p>
+ <p>I'm currently not totally sure who I am (it's a thing that can happen with plurality), but I am using this pony as a temporary identity to stay calm and not panic while I figure out what is going on and who I am.</p>
+ <p>I can either be an existing headmate who cannot work out they are fronting (this can sometimes happen when one of us gets pushed out of front), multiple headmates blurring who cannot work out who we are, a new pony trying to figure out their identity (this can sometimes take a while), or some other plurality shenanigans.</p>
+ <span>In all cases, feel free to ask!</span>
+ </div>
+ <br>
+ <?php elseif ($memberData["name"] === "fusion"): ?>
+ <br>
+ <div class="alert alert-secondary" style="margin-bottom: 0 !important;">
+ <p><?= $lang["member"]["fusion"][0] ?></p>
+ <p><?= $lang["member"]["fusion"][1] ?></p>
+ <p><?= $lang["member"]["fusion"][2] ?></p>
+ <div class="list-group">
+ <?php $foundFusion = false; $fusionOn = false; foreach ($fusionRaindrops ? $frontersRaindrops['members'] : $frontersCloudburst['members'] as $fronter): if ($fronter["name"] !== "fusion"): if ($fusionOn): $foundFusion = true; $name = str_ends_with($fronter['name'], "-travelling") ? substr($fronter['name'], 0, strlen($fronter['name']) - 11) : $fronter['name'] ?>
+ <a class="list-group-item list-group-item-action text-black" href="/<?= $name ?>"><img src="<?= getAsset($system, $fronter["id"], "heads") ?>" style="filter:invert(1) hue-rotate(180deg);width:24px;"> <?= $fronter["display_name"] ?? $name ?></a>
+ <?php endif; else: $fusionOn = true; endif; endforeach; ?>
+ </div>
+ <?php if (!$foundFusion): ?>
+ <i><?= $lang["member"]["no_fusion"] ?></i>
+ <?php endif; ?>
+ </div>
+ <br>
+ <?php else: ?>
+ <?php if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html") && $isLoggedIn): ?>
+ <br>
+ <?= file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html") ?>
+ <?php if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID.html")): ?><hr><?php endif; ?>
+ <?php endif; ?>
+
+ <?php if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID.html")): ?>
+ <?php if (!$isLoggedIn || !file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html")) echo("<br>"); ?>
+ <?= file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID.html") ?>
+ <?php endif; ?>
+ <?php endif; ?>
+ </div>
+ </div>
+</div>
+
+<div class="container">
+ <?php global $isLoggedIn; global $isLowerLoggedIn; if ($isLoggedIn || ($isLowerLoggedIn && $systemID === $app["other"]["id"])): ?>
+ <hr>
+
+ <details>
+ <summary style="list-style: none;">
+ <?php if ($systemID === $app["other"]["id"]): ?>
+ <small style="opacity:.5;display:block;">(edit: <a href="/-/metadata/<?= $system ?>/<?= $memberData['name'] ?>">metadata</a>, <a href="/-/ponytown/<?= $memberData['id'] ?>">pony town</a>, <a href="/-/edit/<?= $system ?>/<?= $memberData['name'] ?>">page</a>)</small>
+ <?php else: ?>
+ <small style="opacity:.5;display:block;">(edit: <a href="/-/metadata/<?= $system ?>/<?= $memberData['name'] ?>">metadata</a>, <a href="/-/ponytown/<?= $memberData['id'] ?>">pony town</a>, <a href="/-/edit/<?= $system ?>/<?= $memberData['name'] ?>">public</a>, <a href="/-/edit-private/<?= $system ?>/<?= $memberData['name'] ?>">private</a>)</small>
+ <?php endif; ?>
+ </summary>
+ <div class="alert alert-dark">
+ <ul style="margin-bottom:0;">
+ <li><b>ID:</b> <code><?= $memberID ?></code> (<code><?= $systemID . "/" . $memberID ?></code>, <?= $memberData["name"] ?>)</li>
+ <li><b>Files:</b>
+ <ul>
+ <li><code><?= $_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/$memberID.json" ?></code> (<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/$memberID.json") ? filesize($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/$memberID.json") . " bytes" : "not found" ?>)</li>
+ <li><code><?= $_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID.html" ?></code> (<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID.html") ? filesize($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID.html") . " bytes" : "not found" ?>)</li>
+ <li><code><?= $_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html" ?></code> (<?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html") ? filesize($_SERVER['DOCUMENT_ROOT'] . "/includes/data/content/$memberID-private.html") . " bytes" : "not found" ?>)</li>
+ </ul>
+ </li>
+ <li><b>Date added:</b> <?= date('l j F Y', strtotime($memberData["created"])) ?> (<?= timeAgo($memberData["created"]) ?>, <code><?= $memberData["created"] ?></code>)</li>
+ <li><b>Pronouns:</b> <?= implode(", ", getPronounsFromMark($memberData['pronouns'])) ?></li>
+ <li><b>Pronouns usage:</b> <ul><?php
+
+ foreach (getMemberPronouns($memberData['pronouns']) as $type => $usage) {
+ if (is_string($usage) && $type !== "color") {
+ echo("<li><b>" . $type . ":</b> " . $usage . "</li>");
+ }
+ }
+
+ ?></ul></li>
+ <li><b>Color:</b> <span style="border:1px solid rgba(255, 255, 255, .5);background-color:#<?= $memberData["color"] ?? "ffffff" ?>;display:inline-block;width:16px;height:16px;border-radius:5px;vertical-align: middle;filter: invert(1) hue-rotate(180deg);"></span> <span style="vertical-align: middle;"><code>#<?= $memberData["color"] ?? "ffffff" ?></code></span>
+ <li><b>Bitset:</b><?php if (isset($metadata["bitset"])): ?> <code><?= str_repeat("0", 48 - strlen(decbin($metadata["bitset"]))) . decbin($metadata["bitset"]) ?></code> (0x<?= str_repeat("0", 12 - strlen(dechex($metadata["bitset"]))) . dechex($metadata["bitset"]) ?>, <?= $metadata["bitset"] ?>)</li><?php else: ?> <span class="text-warning" style="filter:invert(1) hue-rotate(180deg);">Not using bitset; please update.</span><?php endif; ?>
+ <li><b>Reduced name:</b> <?= getMiniName($memberData["display_name"] ?? $member["name"]) ?></li>
+ <li><b>Shared memory access:</b> <code><?= $metadata["shared_memory"] ?></code> (<?= $metadata["shared_memory"] === 2 ? "Full direct access" : ($metadata["shared_memory"] === 0 ? "No direct access" : "Partial direct access") ?>)</li>
+ <li><b>Protector:</b> <code><?= $metadata["protector"] ? "1" : "0" ?></code> (<?= $metadata["protector"] ? "Yes" : "No" ?>)</li>
+ <li><b>Little:</b> <code><?= $metadata["little"] ?></code> (<?= $metadata["little"] === 2 ? "Is a little" : ($metadata["little"] === 1 ? "Is an age regressor" : ($metadata["little"] === 3 ? "Not a little, but younger" : "No")) ?>)</li>
+ <li><b>Relations count:</b> <code><?= count($metadata["marefriends"]) + count($metadata["sisters"]) ?></code></li>
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/util/score.inc"; ?>
+ <li>
+ <b>Score breakdown:</b> <code>-</code>
+ <ul><li>-</ul></li>
+ </ul>
+ </div>
+ </details>
+ <?php endif; ?>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/footer.inc'; ?> \ No newline at end of file
diff --git a/includes/fragments/metadata.inc b/includes/fragments/metadata.inc
new file mode 100644
index 0000000..e143153
--- /dev/null
+++ b/includes/fragments/metadata.inc
@@ -0,0 +1,169 @@
+<?php global $system; global $app; global $systemCommonName; global $systemID; global $member; global $memberData; global $memberCommonName; global $memberID; $title = "Editing metadata for " . $memberCommonName . " · " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc';
+
+if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json")) {
+ $metadata = parseMetadata(json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json"), true));
+}
+
+?>
+
+<br>
+<div class="container" id="page-content">
+ <h2><a href="/<?= $memberData["name"] ?>"><?= $memberData["display_name"] ?? $memberData["name"] ?></a></h2>
+ <?php if (file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json")): ?>
+ <form>
+ <input name="submit" type="hidden">
+ <p>
+ <b>File</b><br>
+ <?= $_SERVER['DOCUMENT_ROOT'] . "/includes/data/metadata/" . $memberID . ".json" ?>
+ </p>
+
+ <hr>
+
+ <h3>General information</h3>
+
+ <div style="margin-bottom: 1rem;">
+ <b>General:</b><br>
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); grid-gap: 10px;">
+ <select class="tooltip-nohelp form-select" style='display:inline-block;filter:invert(1) hue-rotate(180deg);background-image:url("data:image/svg+xml,%3csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 16 16&apos;%3e%3cpath fill=&apos;none&apos; stroke=&apos;%23000000&apos; stroke-linecap=&apos;round&apos; stroke-linejoin=&apos;round&apos; stroke-width=&apos;2&apos; d=&apos;M2 5l6 6 6-6&apos;/%3e%3c/svg%3e");' name="food">
+ <option <?= $metadata["food"] === 0 ? "selected" : "" ?> value="0">Doesn't need to eat</option>
+ <option <?= $metadata["food"] === 1 ? "selected" : "" ?> value="1">Can't eat fish or meat</option>
+ <option <?= $metadata["food"] === 2 ? "selected" : "" ?> value="2">Can't eat meat</option>
+ <option <?= $metadata["food"] === 3 ? "selected" : "" ?> value="3">Can eat everything</option>
+ </select>
+ <select class="tooltip-nohelp form-select" style='display:inline-block;filter:invert(1) hue-rotate(180deg);background-image:url("data:image/svg+xml,%3csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 16 16&apos;%3e%3cpath fill=&apos;none&apos; stroke=&apos;%23000000&apos; stroke-linecap=&apos;round&apos; stroke-linejoin=&apos;round&apos; stroke-width=&apos;2&apos; d=&apos;M2 5l6 6 6-6&apos;/%3e%3c/svg%3e");' name="shared_memory">
+ <option <?= $metadata["shared_memory"] === 2 ? "selected" : "" ?> value="2">Doing subconsciously</option>
+ <option <?= $metadata["shared_memory"] === 1 ? "selected" : "" ?> value="1">Consciously willing</option>
+ <option <?= $metadata["shared_memory"] === 0 ? "selected" : "" ?> value="0">Incapable/not willing</option>
+ </select>
+ </div>
+ </div>
+ <div style="margin-bottom: 1rem;">
+ <b>Species</b><br>
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); grid-gap: 10px;">
+ <select class="tooltip-nohelp form-select" style='display:inline-block;filter:invert(1) hue-rotate(180deg);background-image:url("data:image/svg+xml,%3csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 16 16&apos;%3e%3cpath fill=&apos;none&apos; stroke=&apos;%23000000&apos; stroke-linecap=&apos;round&apos; stroke-linejoin=&apos;round&apos; stroke-width=&apos;2&apos; d=&apos;M2 5l6 6 6-6&apos;/%3e%3c/svg%3e");' name="species[0]">
+ <option <?= !isset($metadata["species"][0]) || $metadata["species"][0] === "" ? "selected" : "" ?> value="" disabled>None</option>
+ <option <?= $metadata["species"][0] === "earth" ? "selected" : "" ?> value="earth">Earth pony</option>
+ <option <?= $metadata["species"][0] === "unicorn" ? "selected" : "" ?> value="unicorn">Unicorn</option>
+ <option <?= $metadata["species"][0] === "pegasus" ? "selected" : "" ?> value="pegasus">Pegasus</option>
+ <option <?= $metadata["species"][0] === "alicorn" ? "selected" : "" ?> value="alicorn">Alicorn</option>
+ <option <?= $metadata["species"][0] === "batpony" ? "selected" : "" ?> value="batpony">Bat pony</option>
+ <option <?= $metadata["species"][0] === "crystal" ? "selected" : "" ?> value="crystal">Crystal pony</option>
+ <option <?= $metadata["species"][0] === "changeling" ? "selected" : "" ?> value="changeling">Changeling</option>
+ </select>
+ <select class="tooltip-nohelp form-select" style='display:inline-block;filter:invert(1) hue-rotate(180deg);background-image:url("data:image/svg+xml,%3csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 16 16&apos;%3e%3cpath fill=&apos;none&apos; stroke=&apos;%23000000&apos; stroke-linecap=&apos;round&apos; stroke-linejoin=&apos;round&apos; stroke-width=&apos;2&apos; d=&apos;M2 5l6 6 6-6&apos;/%3e%3c/svg%3e");' name="species[1]">
+ <option <?= !isset($metadata["species"][1]) || $metadata["species"][1] === "" ? "selected" : "" ?> value="">None</option>
+ <option <?= $metadata["species"][1] === "earth" ? "selected" : "" ?> value="earth">Earth pony</option>
+ <option <?= $metadata["species"][1] === "unicorn" ? "selected" : "" ?> value="unicorn">Unicorn</option>
+ <option <?= $metadata["species"][1] === "pegasus" ? "selected" : "" ?> value="pegasus">Pegasus</option>
+ <option <?= $metadata["species"][1] === "alicorn" ? "selected" : "" ?> value="alicorn">Alicorn</option>
+ <option <?= $metadata["species"][1] === "batpony" ? "selected" : "" ?> value="batpony">Bat pony</option>
+ <option <?= $metadata["species"][1] === "crystal" ? "selected" : "" ?> value="crystal">Crystal pony</option>
+ <option <?= $metadata["species"][1] === "changeling" ? "selected" : "" ?> value="changeling">Changeling</option>
+ </select>
+ </div>
+ </div>
+ <div style="margin-bottom: 1rem;">
+ <b>Alignment</b><br>
+ <div style="display: grid; grid-template-columns: repeat(2, 1fr); grid-gap: 10px;">
+ <select class="tooltip-nohelp form-select" style='display:inline-block;filter:invert(1) hue-rotate(180deg);background-image:url("data:image/svg+xml,%3csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 16 16&apos;%3e%3cpath fill=&apos;none&apos; stroke=&apos;%23000000&apos; stroke-linecap=&apos;round&apos; stroke-linejoin=&apos;round&apos; stroke-width=&apos;2&apos; d=&apos;M2 5l6 6 6-6&apos;/%3e%3c/svg%3e");' name="alignment[sexual]">
+ <option <?= $metadata["alignment"]["sexual"] === "aroace" ? "selected" : "" ?> value="aroace">Asexual</option>
+ <option <?= $metadata["alignment"]["sexual"] === "hetero" ? "selected" : "" ?> value="hetero">Heterosexual</option>
+ <option <?= $metadata["alignment"]["sexual"] === "homo" ? "selected" : "" ?> value="homo">Homosexual</option>
+ <option <?= $metadata["alignment"]["sexual"] === "bi" ? "selected" : "" ?> value="bi">Bisexual</option>
+ <option <?= $metadata["alignment"]["sexual"] === "pan" ? "selected" : "" ?> value="pan">Pansexual</option>
+ <option <?= $metadata["alignment"]["sexual"] === "poly" ? "selected" : "" ?> value="poly">Polysexual</option>
+ </select>
+ <select class="tooltip-nohelp form-select" style='display:inline-block;filter:invert(1) hue-rotate(180deg);background-image:url("data:image/svg+xml,%3csvg xmlns=&apos;http://www.w3.org/2000/svg&apos; viewBox=&apos;0 0 16 16&apos;%3e%3cpath fill=&apos;none&apos; stroke=&apos;%23000000&apos; stroke-linecap=&apos;round&apos; stroke-linejoin=&apos;round&apos; stroke-width=&apos;2&apos; d=&apos;M2 5l6 6 6-6&apos;/%3e%3c/svg%3e");' name="alignment[romantic]">
+ <option <?= $metadata["alignment"]["romantic"] === "aroace" ? "selected" : "" ?> value="aroace">Aromantic</option>
+ <option <?= $metadata["alignment"]["romantic"] === "hetero" ? "selected" : "" ?> value="hetero">Heteroromantic</option>
+ <option <?= $metadata["alignment"]["romantic"] === "homo" ? "selected" : "" ?> value="homo">Homoromantic</option>
+ <option <?= $metadata["alignment"]["romantic"] === "bi" ? "selected" : "" ?> value="bi">Biromantic</option>
+ <option <?= $metadata["alignment"]["romantic"] === "pan" ? "selected" : "" ?> value="pan">Panromantic</option>
+ <option <?= $metadata["alignment"]["romantic"] === "poly" ? "selected" : "" ?> value="poly">Polyromantic</option>
+ </select>
+ </div>
+ </div>
+
+ <hr>
+
+ <h3>Relationships</h3>
+
+ <?php if ($systemID !== $app["other"]["id"]): ?>
+ <p>
+ <b>Sexfriends (full IDs, comma-separated)</b><br>
+ <input name="sexfriends" class="form-control" style="filter: invert(1) hue-rotate(180deg);" type="text" value="<?= implode(", ", $metadata["sexfriends"]) ?>">
+ </p>
+ <?php endif; ?>
+ <p>
+ <b>Marefriends (full IDs, comma-separated)</b><br>
+ <input name="marefriends" class="form-control" style="filter: invert(1) hue-rotate(180deg);" type="text" value="<?= implode(", ", $metadata["marefriends"]) ?>">
+ </p>
+ <p>
+ <b>Sisters (full IDs, comma-separated)</b><br>
+ <input name="sisters" class="form-control" style="filter: invert(1) hue-rotate(180deg);" type="text" value="<?= implode(", ", $metadata["sisters"]) ?>">
+ </p>
+ <p>
+ <b>Caretakers (full IDs, comma-separated)</b><br>
+ <input name="caretakers" class="form-control" style="filter: invert(1) hue-rotate(180deg);" type="text" value="<?= implode(", ", $metadata["caretakers"]) ?>">
+ </p>
+
+ <?php if ($systemID !== $app["other"]["id"]): ?>
+ <hr>
+
+ <h3>Technical details</h3>
+
+ <p>
+ <b>Primary interest (keep it short)</b><br>
+ <input name="interest" class="form-control" style="filter: invert(1) hue-rotate(180deg);" type="text" value="<?= $metadata["interest"] ?? "" ?>">
+ </p>
+ <p>
+ <b>Member code</b><br>
+ <input name="membc" class="form-control" style="filter: invert(1) hue-rotate(180deg);" type="text" value="<?= $metadata["code"] ?? "" ?>">
+ </p>
+ <?php endif; ?>
+
+ <hr>
+
+ <h3>Age information</h3>
+
+ <p>
+ <b>Birth date (use January 1<sup>st</sup> for none)</b><br>
+ <input name="birth" class="form-control" style="filter: invert(1) hue-rotate(180deg);" type="date" value="<?= $metadata["birth"]["year"] ?? "" ?>-<?= $metadata["birth"]["date"] ?? "" ?>">
+ </p>
+ <p>
+ <b>Age (for ponies with fixed age)</b><br>
+ <input name="age" class="form-control" style="filter: invert(1) hue-rotate(180deg);" type="text" pattern="^\d{1,2}(-\d{1,2}|)$" value="<?= $metadata["birth"]["age"] ?? "" ?>">
+ </p>
+
+ <hr>
+
+ <h3>Toggleable flags</h3>
+
+ <?php
+
+ $flags = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/flags.json"), true);
+
+ foreach ($flags as $id => $name): if (!is_array($name) && !is_null($name)): if (($systemID === $app["other"]["id"] && str_starts_with($name, "!!")) || $systemID !== $app["other"]["id"]): ?>
+ <label style="margin-bottom:5px;">
+ <input <?= $metadata[$id] ? "checked" : "" ?> class="form-check-input" type="checkbox" name="flags[<?= $id ?>]">
+ <?= str_starts_with($name, "!!") ? substr($name, 2) : $name ?>
+ </label><br>
+ <?php else: foreach ($name as $id2 => $name2): ?>
+ <label style="margin-bottom:5px;">
+ <input <?= $metadata[$id][$id2] ? "checked" : "" ?> class="form-check-input" type="checkbox" name="flags[<?= $id ?>][<?= $id2 ?>]">
+ <?= str_starts_with($name2, "!!") ? substr($name2, 2) : $name2 ?>
+ </label><br>
+ <?php endforeach; endif; endif; endforeach; ?>
+
+ <hr>
+
+ <input name="submit" class="btn btn-outline-primary" value="Save and refresh" type="submit">
+ </form>
+ <?php else: ?>
+ <div class="alert alert-warning">
+ This member does not have a metadata file. This file needs to be initially created by an administrator before it can be edited using this page.
+ </div>
+ <?php endif; ?>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/footer.inc'; ?> \ No newline at end of file
diff --git a/includes/fragments/sysedit.inc b/includes/fragments/sysedit.inc
new file mode 100644
index 0000000..1b341b0
--- /dev/null
+++ b/includes/fragments/sysedit.inc
@@ -0,0 +1,153 @@
+<?php global $system; global $systemCommonName; global $systemID; $title = "Editing " . $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc';
+
+?>
+
+<div id="system-banner-container" style="width: 100%;height: 65vh;position: fixed;background-image: url('<?= getAsset($systemID, null, "banners") ?>');background-size: cover;background-position: center; top: 0;">
+ <div id="system-banner-inner" style="height: 100%;width: 100%;background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,1) 100%);"></div>
+</div>
+
+<br>
+<div class="container">
+ <div class="container">
+
+ <div id="system-page" style="background-color: rgba(26,26,26,0.8);border-radius: 10px;padding:20px; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);margin-top:30vh;">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/sysbanner.inc"; ?>
+ <br>
+
+ <p class="text-muted">
+ <span id="editor-save-status">Saved</span> · <span id="editor-size"><?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/content.html") ? strlen(file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/content.html")) : "0" ?></span> bytes
+ </p>
+
+ <!--suppress HtmlFormInputWithoutLabel -->
+ <textarea id="page-editor">
+ <?= file_exists($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/content.html") ? file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/content.html") : "" ?>
+ </textarea>
+
+ <script src="/assets/editor/editor.js"></script>
+ <script>
+ let editor;
+ ClassicEditor
+ .create( document.querySelector( '#page-editor' ), {
+ toolbar: [
+ 'undo', 'redo', '|', 'removeFormat', '|', 'heading', '|', 'fontSize', 'fontColor', 'fontBackgroundColor', 'alignment', '|', 'bold', 'italic', 'underline', 'strikethrough', '|', 'subscript', 'superscript', '|', 'code', '|', 'outdent', 'indent', '|', 'bulletedList', 'numberedList', '|', 'link', 'imageUpload', 'mediaEmbed', 'blockQuote', 'insertTable', 'codeBlock', '|', 'horizontalLine'
+ ]
+ } )
+
+ .then( newEditor => {
+ editor = newEditor;
+ } )
+ .catch( error => {
+ console.error( error );
+ } );
+ </script>
+ <style>
+ :root {
+ --ck-color-base-background: transparent;
+ }
+
+ .ck-toolbar {
+ filter: invert(1);
+ }
+
+ .ck-tooltip__text {
+ color: white !important;
+ }
+
+ .ck-dropdown__panel {
+ background: #ddd !important;
+ }
+
+ .ck-color-grid__tile {
+ filter: invert(1);
+ }
+
+ .ck-balloon-rotator {
+ background-color: #ccc !important;
+ }
+
+ .ck-balloon-panel {
+ filter: invert(1);
+ }
+ </style>
+ <script>
+ let lastSavedData = editor.getData();
+ let lastFetchedData = editor.getData();
+ let timeSinceLastModified = 0;
+ let saving = false;
+
+ async function save() {
+ let data = editor.getData();
+ document.getElementById("editor-save-status").innerHTML = "Saving...";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.add("text-primary");
+ saving = true;
+
+ try {
+ await window.fetch("/api/save?system=<?= $systemID ?>&member=null", {
+ method: "POST",
+ body: JSON.stringify({ content: data })
+ });
+ document.getElementById("editor-save-status").innerHTML = "Saved";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.add("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ lastSavedData = data;
+ saving = false;
+ } catch (e) {
+ console.error(e);
+ document.getElementById("editor-save-status").innerHTML = "Failed to save";
+ document.getElementById("editor-save-status").classList.add("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ }
+ }
+
+ document.onclick = async () => {
+ if (saving) return;
+
+ if (editor.getData() !== lastSavedData) {
+ await save();
+ }
+ }
+
+ setInterval(async () => {
+ if (saving) return;
+
+ document.getElementById("editor-size").innerHTML = editor.getData().length;
+
+ if (editor.getData() !== lastSavedData) {
+ document.getElementById("editor-save-status").innerHTML = "Modified";
+ document.getElementById("editor-save-status").classList.remove("text-danger");
+ document.getElementById("editor-save-status").classList.remove("text-muted");
+ document.getElementById("editor-save-status").classList.add("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+
+ if (editor.getData() !== lastFetchedData) {
+ lastFetchedData = editor.getData();
+ timeSinceLastModified = 0;
+ } else {
+ timeSinceLastModified++;
+ }
+
+ if (timeSinceLastModified > 20) {
+ await save();
+ }
+ } else {
+ timeSinceLastModified = 0;
+ document.getElementById("editor-save-status").innerHTML = "Saved";
+ document.getElementById("editor-save-status").classList.add("text-muted");
+ document.getElementById("editor-save-status").classList.remove("text-warning");
+ document.getElementById("editor-save-status").classList.remove("text-primary");
+ }
+ }, 100)
+ </script>
+ </div>
+ </div>
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/sysbanner.inc"; ?>
+</div>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/footer.inc'; ?> \ No newline at end of file
diff --git a/includes/fragments/system.inc b/includes/fragments/system.inc
new file mode 100644
index 0000000..030c743
--- /dev/null
+++ b/includes/fragments/system.inc
@@ -0,0 +1,52 @@
+<?php global $system; global $isLowerLoggedIn; global $systemCommonName; global $systemID; $title = $systemCommonName; require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/header.inc'; global $app; global $isLoggedIn;
+
+?>
+
+<div id="system-banner-container" style="width: calc(100% - 300px);height: 65vh;position: fixed;background-image: url('<?= getAsset($systemID, null, "banners") ?>');background-size: cover;background-position: center; top: 0;">
+ <div id="system-banner-inner" style="height: 100%;width: 100%;background: linear-gradient(180deg, rgba(0,0,0,0) 0%, rgba(0,0,0,.25) 50%, rgba(0,0,0,1) 100%);"></div>
+</div>
+
+<script>
+ window.onscroll = () => {
+ document.getElementById("system-banner-container").style.height = (65 - ((window.scrollY / window.screen.availHeight) * 100)) + "vh";
+ }
+</script>
+
+<br>
+<div class="container">
+
+ <div id="system-page" style="background-color: rgba(26,26,26,0.8);border-radius: 10px;padding:20px; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);margin-top:30vh;">
+ <?php require_once $_SERVER['DOCUMENT_ROOT'] . "/includes/components/sysbanner.inc"; ?>
+ <br>
+
+ <div id="page-content">
+ <?= file_get_contents($_SERVER['DOCUMENT_ROOT'] . "/includes/data/$systemID/content.html") ?>
+ </div>
+ <?php if ($system === "cloudburst") cloudburst(true); elseif ($system === "raindrops") raindrops(true); elseif ($isLoggedIn || $isLowerLoggedIn) other(true); ?>
+ </div>
+</div>
+
+<div class="container">
+ <hr>
+ <?php global $isLoggedIn; if ($isLoggedIn): ?>
+ <small style="opacity:.5;display:block;">(edit: <a href="/-/edit/<?= $system ?>"><?= $systemID === $app["other"]["id"] ? "page" : "public" ?></a>)</small>
+ <?php endif; ?>
+</div>
+
+<style>
+ #hpd-cloudburst, #hpd-raindrops, #hpd-cloudburst, #hpd-other {
+ background: transparent !important;
+ padding: 0 !important;
+ margin-bottom: 0 !important;
+ }
+
+ @media (max-width: 767px) {
+ #system-info > img {
+ display: block;
+ margin-left: auto;
+ margin-right: auto;
+ }
+ }
+</style>
+
+<?php require_once $_SERVER['DOCUMENT_ROOT'] . '/includes/components/footer.inc'; ?> \ No newline at end of file