pub / rememori

Simple file-based bookmarking and notes application
git clone https://src.jayvii.de/pub/rememori.git
Home | Log | Files | Exports | Refs | README | RSS

commit 49d2581a9896fbcb60e435beb236846858a6d66c
parent da4133455749ad995882cc4ca122c10c6c51d32d
Author: JayVii <jayvii[AT]posteo[DOT]de>
Date:   Sat, 26 Oct 2024 11:08:38 +0200

feat: finish basic functionality

Diffstat:
Aassets/css/custom.css | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aassets/js/deletion.js | 9+++++++++
Mconfig/i18n.php | 4+++-
Mindex.php | 187+++++++++++++------------------------------------------------------------------
Alib/delete.php | 20++++++++++++++++++++
Mlib/edit.php | 7++++---
Mlib/helpers.php | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mlib/list.php | 219+++++++++++++++++++++++++++++++++++++++----------------------------------------
Alib/menus.php | 207+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/show.php | 40++++++++++++++++++++++++++++++++++++++++
10 files changed, 599 insertions(+), 274 deletions(-)

diff --git a/assets/css/custom.css b/assets/css/custom.css @@ -0,0 +1,71 @@ +/* Forms and its elements should stretch full width */ +form:not(.inline) > * +{ + width: 100%; +} + +/* The content text area should have a substantial height */ +textarea#content { + height: 400px; +} + +/* provide inline utility class for buttons, forms, etc. */ +.inline { + display: inline-block; +} + +/* Ensure the notes list items do not overflow and break properly */ +.notes_list_item { + overflow: hidden; + word-break: break-all; + margin-bottom: 1em; + padding-bottom: 0.5em; + border-bottom: 1px solid grey; +} + +/* provide a utility class so buttons behave like anchor-objects */ +.likeanchor { + background-color: unset !important; + border: unset !important; + color: var(--accent) !important; + margin: initial !important; + padding: initial !important; + text-decoration: underline; + display: inline-block; +} +.likeanchor:hover { + color: var(--accent-hover) !important; + cursor: pointer !important; +} + +.likenavitem { + background-color: unset !important; + border: 1px solid var(--border) !important; + border-radius: var(--standard-border-radius) !important; + color: var(--text) !important; + margin: 0 .5rem 1rem !important; + padding: .1rem 1rem !important; + text-decoration: none !important; + display: inline-block; +} + +.likenavitem:hover { + background-color: unset !important; + border-color: var(--accent) !important; + color: var(--accent) !important; + cursor: pointer !important; +} + +header { + margin-bottom: 2em; +} + +.danger { + background-color: #ff0000 !important; + border-color: #ff0000 !important; +} + +.danger:hover { + background-color: #ff6161 !important; + border-color: #ff6161 !important; +} diff --git a/assets/js/deletion.js b/assets/js/deletion.js @@ -0,0 +1,9 @@ +function deletion_confirm(id) { + // Ask user about intentions + var status = confirm("Are you sure you want to delete this note?"); + + // If user confirms, submit the given form (delete item) + if (status) { + document.getElementById(id).submit(); + } +} diff --git a/config/i18n.php b/config/i18n.php @@ -13,11 +13,13 @@ if ($lang == "en") { $GLOBALS["i18n_ctime"] = "Last edited"; $GLOBALS["i18n_edit"] = "Edit note"; $GLOBALS["i18n_new"] = "New note"; - $GLOBALS["i18n_list"] = "List notes"; + $GLOBALS["i18n_list"] = "All"; $GLOBALS["i18n_login"] = "Login"; $GLOBALS["i18n_user"] = "Username"; $GLOBALS["i18n_pass"] = "Password"; $GLOBALS["i18n_logout"] = "Logout"; + $GLOBALS["i18n_categories"] = "Categories"; + $GLOBALS["i18n_delete"] = "Delete"; } diff --git a/index.php b/index.php @@ -4,11 +4,6 @@ <?php - /* DEVELOPMENT SETTINGS */ - if (!array_key_exists("action", $_POST)) { - $_POST["action"] = "list"; - } - /* Load library functions */ foreach (glob("./lib/*.php") as $lib) { include($lib); @@ -40,8 +35,12 @@ $action = "login"; } - /* ensure the given filename has a valid extension */ - if (count(preg_grep("/\.txt$/", array($filename), PREG_GREP_INVERT)) > 0) { + /* ensure the given filename (if it is set!) has a valid extension */ + if ( + (count(preg_grep("/\.txt$/", array($filename), PREG_GREP_INVERT)) > 0) + && + ($filename != "") + ){ $filename = $filename . ".txt"; } @@ -55,6 +54,7 @@ <title>Rememori</title> <link rel="stylesheet" type="text/css" href="assets/css/simple.min.css"/> <link rel="stylesheet" href="assets/css/custom.css" /> + <script async src="assets/js/deletion.js"></script> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> @@ -69,177 +69,50 @@ <header> <!-- Buttons --> - <nav> - - <!-- New Note: Edit-Action --> - <form action="/" target="_self" method="post"> - <input - id="user" - name="user" - type="hidden" - value="<?php echo $GLOBALS["user"]; ?>" - > - <input - id="token" - name="token" - type="hidden" - value="<?php echo $GLOBALS["token"]; ?>" - > - <input - id="category" - name="category" - type="hidden" - value="" - > - <input - id="filename" - name="filename" - type="hidden" - value="" - > - <input - id="action" - name="action" - type="hidden" - value="show" - > - <input - type="submit" - value="<?php echo $GLOBALS["i18n_new"]; ?>" - > - </form> - <!-- Logout-Action --> - <form action="/" target="_self" method="post"> - <input - id="user" - name="user" - type="hidden" - value="" - > - <input - id="pass" - name="pass" - type="hidden" - value="" - > - <input - id="token" - name="token" - type="hidden" - value="" - > - <input - id="action" - name="action" - type="hidden" - value="list" - > - <input - type="submit" - value="<?php echo $GLOBALS["i18n_logout"]; ?>" - > - </form> - </nav> + <?php top_navigation(); ?> <!-- Headline --> <h1>Rememori</h1> - <!-- List all categories --> - <form action="/" target="_self" method="post"> - <input - id="user" - name="user" - type="hidden" - value="<?php echo $GLOBALS["user"]; ?>" - > - <input - id="token" - name="token" - type="hidden" - value="<?php echo $GLOBALS["token"]; ?>" - > - <input - id="action" - name="action" - type="hidden" - value="list" - > - <input - type="submit" - value="<?php echo $GLOBALS["i18n_list"]; ?>" - > - </form> - -<?php - /* Fetch all categories of the user */ - $categories_path = glob("./data/" . $GLOBALS["user"] . "/*"); - foreach ($categories_path as $category_path) { - - if (count(glob($category_path . "/" . "*.txt")) > 0) { -?> - <form action="/" target="_self" method="post"> - <input - id="user" - name="user" - type="hidden" - value="<?php echo $GLOBALS["user"]; ?>" - > - <input - id="token" - name="token" - type="hidden" - value="<?php echo $GLOBALS["token"]; ?>" - > - <input - id="category" - name="category" - type="hidden" - value="<?php echo basename($category_path); ?>" - > - <input - id="action" - name="action" - type="hidden" - value="list" - > - <input - type="submit" - value="<?php echo basename($category_path); ?>" - > - </form> -<?php - } // if-statement - } // foreach-loop -?> - </header> <?php } // if-statement +?> + +<?php /* Login action */ if ($action == "login") { show_login_form("list"); - die(); + die(); // ensure the process stops after this + } + + /* Deletion action */ + if ($action == "delete") { + delete_note( + $category, + $filename + ); + /* set action to "list", so we return to the main view again */ + $action = "list"; + $category = ""; } /* Listing action */ if ($action == "list") { - - /* if no category is given, list all. otherwise only show given */ - if ($category != "") { - $categories_path = array( - "./data/" . $GLOBALS["user"] . "/" . $category - ); - } - foreach ($categories_path as $category_path) { - list_notes(basename($category_path)); - } + category_menu(); + list_notes($category); } /* Edit action */ if ($action == "edit") { + /* if no filename is given, try to come up with one ourselves */ + if ($filename == "") { + $filename = generate_filename($content); + } + /* edit note */ edit_note( $category, $filename, diff --git a/lib/delete.php b/lib/delete.php @@ -0,0 +1,20 @@ +<?php + +function delete_note( + string $category, + string $filename, +) { + + /* Create full file path name */ + $dirpath = "./data/" . $GLOBALS["user"] . "/" . $category; + $filepath = $dirpath . "/" . $filename; + + /* delete note */ + $status = unlink($filepath); + + /* return status of deletion action */ + return $status; +} + +?> + diff --git a/lib/edit.php b/lib/edit.php @@ -24,12 +24,13 @@ function edit_note( $filepath_t0 = $category . "/" . $filename; /* if old and new filepaths differ, the file was likely renamed. - * delete the old one and exit immediately */ + * delete the old one and return immediately */ + $deletion = false; if ($filepath_t1 != $filepath_t0) { - return unlink("./data/" . $GLOBALS["user"] . "/" . $filepath_t1); + return delete_note($category, basename($filepath_t1)); } - /* if all actions went through, return "true" */ + /* if all went fine, return "true" */ return true; } // function diff --git a/lib/helpers.php b/lib/helpers.php @@ -10,9 +10,9 @@ function gather_post(string $key) { } function validate_input_string(string $in) { - /* Only allows alphanumeric characters and any of "-_." */ + /* Only allows alphanumeric characters and any of "-_. " */ $out = preg_replace( - "/[^A-Za-z0-9\-\_\.]/", + "/[^A-Za-z0-9\-\_\.\s]/", "_", $in ); @@ -50,4 +50,109 @@ function link_in_first_line(string $filepath) { } +function gather_notes(string $category = "") { + + /* initilise files array */ + $filenames = array(); + + /* Create full path name */ + $dirpath = "./data/" . $GLOBALS["user"]; + + /* if no category is given, search in all categories + * if a category is given, only search in this one */ + if ($category == "") { + + /* list all available categories */ + $categories_path = glob($dirpath . "/*"); + + } else { + + $categories_path = array($dirpath . "/" . $category); + } + + /* loop through each category path and find all files within them */ + foreach ($categories_path as $category_path) { + + $files_path = glob($category_path . "/" . "*.txt"); + + foreach ($files_path as $file_path) { + + array_push( + $filenames, + array( + "category" => basename($category_path), + "name" => preg_replace( + "/\.txt$/", + "", + basename($file_path) + ), + "time" => filectime($file_path), + "link" => link_in_first_line($file_path) + ) + ); + + } // foreach-loop + } // foreach-loop + + /* return filenames array */ + return $filenames; + +} // function + +function page_title( + string $url +) { + + /* initilise title for page */ + $title = ""; + + /* open url handle */ + $handle = fopen($url, "r"); + + /* if connection suceeded, fetch the content up until </title> */ + if ($handle) { + $string = stream_get_line($handle, 0, "</title>"); + fclose($handle); + $string = explode("<title", $string)[1]; + + /* if a title could be gathered, clean it up */ + if (!empty($string)) { + $title = trim((explode(">", $string))[1]); + } + } + + /* return title */ + return $title; +} + +function generate_filename(string $content) { + + /* initilise new filename */ + $filename = ""; + + /* if the first line of the content contains a URL, + * we may use the page title */ + $file_link = preg_replace( + '/^(http(s){0,1}:\/\/[^\s]+)*.*$/', + '\1', + explode(PHP_EOL, $content)[0] + ); + if ($file_link != "") { + $filename = page_title($file_link); + } + + /* if filename is still unset after this + * (file did not contain link OR fetching page title failed) + * we use the sha256sum of the content as fallback name */ + if ($filename == "") { + $filename = hash("sha256", $content, false); + } + + /* ensure filename as a proper file extension */ + $filename = $filename . ".txt"; + + /* return new filename */ + return $filename; +} + ?> diff --git a/lib/list.php b/lib/list.php @@ -1,129 +1,126 @@ <?php function list_notes( - string $category + string $category = "" ) { - /* Create full path name */ - $dirpath = "./data/" . $GLOBALS["user"] . "/" . $category; + /* gather note paths, based on given category */ + $filenames = gather_notes($category); - /* list all available notes */ - $filematches = glob($dirpath . "/" . "*.txt"); + /* Sort filenames by edit timestamp */ + usort($filenames, "sort_by_time"); - if (count($filematches) > 0) { - - /* Gather each file next to its edit time stamp */ - $filenames = array(); - foreach ($filematches as $filename) { - - array_push( - $filenames, - array( - "name" => preg_replace( - "/\.txt$/", - "", - basename($filename) - ), - "time" => filectime($filename), - "link" => link_in_first_line($filename) - ) - ); - } - - /* Sort filenames by edit timestamp */ - usort($filenames, "sort_by_time"); - -?> - - <h4><?php echo $category; ?></h4> - <table> - <thead> - <tr> - <!-- 1st column: filename --> - <th><?php echo $GLOBALS["i18n_filename"]; ?></th> - <!-- 2nd column: edit time --> - <th><?php echo $GLOBALS["i18n_ctime"]; ?></th> - <!-- 3rd column: edit button --> - <th></th> - </tr> - </thead> - <tbody> - -<?php - /* loop through each filename and draw a table row */ - foreach ($filenames as $filename) { + /* loop through each filename and draw a table row */ + foreach ($filenames as $filename) { ?> - <tr> - <!-- 1st column: filename including URL if applicable --> - <td> -<?php - /* if first line of */ - if ($filename["link"] !== false) { -?> - <a href="<?php echo $filename["link"]; ?>" target="_blank"> - <?php echo $filename["name"]; ?> - </a> -<?php - } else { - echo $filename["name"]; - } -?> - </td> - <!-- 2nd column: edit time --> - <td><?php echo date("Y-m-d H:i:s", $filename["time"]); ?></td> - <!-- 3rd columne:form with edit-button --> - <td> - <form action="/" target="_self" method="post"> - <input - id="user" - name="user" - type="hidden" - value="<?php echo $GLOBALS["user"]; ?>" - > - <input - id="token" - name="token" - type="hidden" - value="<?php echo $GLOBALS["token"]; ?>" - > - <input - id="category" - name="category" - type="hidden" - value="<?php echo $category; ?>" - > - <input - id="filename" - name="filename" - type="hidden" - value="<?php echo $filename["name"] . ".txt"; ?>" - > - <input - id="action" - name="action" - type="hidden" - value="show" - > - <input - type="submit" - value="<?php echo $GLOBALS["i18n_edit"]; ?>" - > - </form> - </td> - </tr> + <!-- note list item --> + <div + id="<?php echo $filename["category"] . "_" . $filename["name"]; ?>" + class="notes_list_item" + > <?php - - } // for-loop - + /* if note starts with an URL, link it in the title + * otherwise just print the title */ + if ($filename["link"] !== false) { ?> - </tbody> - </table> + <a href="<?php echo $filename["link"]; ?>" target="_blank"> + <strong><?php echo $filename["name"]; ?></strong> + </a><br> + +<?php } else { ?> + <strong><?php echo $filename["name"]; ?></strong><br> +<?php } ?> + + <!-- date marker --> + <span class="inline"> + <?php echo date("Y-m-d H:i:s", $filename["time"]); ?> + </span> + + <!-- separator --> + <span class="inline">|</span> + + <!-- category button --> + <form action="/" target="_self" method="post" class="inline"> + <input + id="user" + name="user" + type="hidden" + value="<?php echo $GLOBALS["user"]; ?>" + > + <input + id="token" + name="token" + type="hidden" + value="<?php echo $GLOBALS["token"]; ?>" + > + <input + id="category" + name="category" + type="hidden" + value="<?php echo $filename["category"]; ?>" + > + <input + id="action" + name="action" + type="hidden" + value="list" + > + <input + type="submit" + class="likeanchor" + value="<?php echo $filename["category"]; ?>" + > + </form> + + <!-- separator --> + <span class="inline">|</span> + + <!-- edit button --> + <form action="/" method="post" class="inline"> + <input + id="user" + name="user" + type="hidden" + value="<?php echo $GLOBALS["user"]; ?>" + > + <input + id="token" + name="token" + type="hidden" + value="<?php echo $GLOBALS["token"]; ?>" + > + <input + id="category" + name="category" + type="hidden" + value="<?php echo $filename["category"]; ?>" + > + <input + id="filename" + name="filename" + type="hidden" + value="<?php echo $filename["name"] . ".txt"; ?>" + > + <input + id="action" + name="action" + type="hidden" + value="show" + > + <input + type="submit" + class="likeanchor" + value="<?php echo $GLOBALS["i18n_edit"]; ?>" + > + </form> + + </div> <?php - } // if-statement + } // foreach-loop } // function diff --git a/lib/menus.php b/lib/menus.php @@ -0,0 +1,207 @@ +<?php + +function top_navigation() { + +?> + +<!-- Navigation Buttons --> +<nav> + + <!-- All Notes: List-Action --> + <form action="/" method="post" class="inline"> + <input + id="user" + name="user" + type="hidden" + value="<?php echo $GLOBALS["user"]; ?>" + > + <input + id="token" + name="token" + type="hidden" + value="<?php echo $GLOBALS["token"]; ?>" + > + <input + id="category" + name="category" + type="hidden" + value="" + > + <input + id="action" + name="action" + type="hidden" + value="list" + > + <input + class="likenavitem" + type="submit" + value="<?php echo $GLOBALS["i18n_list"]; ?>" + > + </form> + + <!-- New Note: Edit-Action --> + <form action="/" method="post" class="inline"> + <input + id="user" + name="user" + type="hidden" + value="<?php echo $GLOBALS["user"]; ?>" + > + <input + id="token" + name="token" + type="hidden" + value="<?php echo $GLOBALS["token"]; ?>" + > + <input + id="category" + name="category" + type="hidden" + value="" + > + <input + id="filename" + name="filename" + type="hidden" + value="" + > + <input + id="action" + name="action" + type="hidden" + value="show" + > + <input + class="likenavitem" + type="submit" + value="<?php echo $GLOBALS["i18n_new"]; ?>" + > + </form> + + <!-- Logout-Action --> + <form action="/" method="post" class="inline"> + <input + id="user" + name="user" + type="hidden" + value="" + > + <input + id="pass" + name="pass" + type="hidden" + value="" + > + <input + id="token" + name="token" + type="hidden" + value="" + > + <input + id="action" + name="action" + type="hidden" + value="list" + > + <input + class="likenavitem" + type="submit" + value="<?php echo $GLOBALS["i18n_logout"]; ?>" + > + </form> + +</nav> + +<?php + +} // function + +?> + +<?php + +function category_menu() { + +?> + +<!-- List all categories --> +<details> + <summary><?php echo $GLOBALS["i18n_categories"]; ?></summary> + <div class="inline"> + <form action="/" target="_self" method="post" class="inline"> + <input + id="user" + name="user" + type="hidden" + value="<?php echo $GLOBALS["user"]; ?>" + > + <input + id="token" + name="token" + type="hidden" + value="<?php echo $GLOBALS["token"]; ?>" + > + <input + id="action" + name="action" + type="hidden" + value="list" + > + <input + type="submit" + value="<?php echo $GLOBALS["i18n_list"]; ?>" + > + </form> + +<?php + /* Fetch all categories of the user */ + $categories_path = glob("./data/" . $GLOBALS["user"] . "/*"); + foreach ($categories_path as $category_path) { + + if (count(glob($category_path . "/" . "*.txt")) > 0) { +?> + + <form action="/" target="_self" method="post" class="inline"> + <input + id="user" + name="user" + type="hidden" + value="<?php echo $GLOBALS["user"]; ?>" + > + <input + id="token" + name="token" + type="hidden" + value="<?php echo $GLOBALS["token"]; ?>" + > + <input + id="category" + name="category" + type="hidden" + value="<?php echo basename($category_path); ?>" + > + <input + id="action" + name="action" + type="hidden" + value="list" + > + <input + type="submit" + value="<?php echo basename($category_path); ?>" + > + </form> + +<?php + } // if-statement + } // foreach-loop +?> + + </div> +</details> + +<?php +} // function +?> diff --git a/lib/show.php b/lib/show.php @@ -55,6 +55,46 @@ function show_note( <input type="submit" value="<?php echo $GLOBALS["i18n_save"]; ?>"> </form> +<form action="/" method="post" enctype="multipart/form-data" id="delete"> + <input + id="category" + name="category" + type="hidden" + value="<?php echo $category; ?>" + > + <input + id="filename" + name="filename" + type="hidden" + value="<?php echo $filename; ?>" + > + <input + id="action" + name="action" + type="hidden" + value="delete" + > + <input + id="user" + name="user" + type="hidden" + value="<?php echo $GLOBALS["user"]; ?>" + > + <input + id="token" + name="token" + type="hidden" + value="<?php echo $GLOBALS["token"]; ?>" + > + <input + class="danger" + type="button" + value="<?php echo $GLOBALS["i18n_delete"]; ?>" + onclick="deletion_confirm('delete');" + > +</form> + + <?php } // function