| 
<?php
/*
 * SQLite Code Vault :: SQLite Snippets Manager :: Example Admin Page
 *
 * @package SQLite Snippets Manager :: SQLiteCodeVault
 * @version 1.1
 * @author https://www.phpclasses.org/browse/author/144301.html
 * @link https://www.phpclasses.org/package/12863-PHP-SQLite-Code-Vault-is-a-PHP-SQLite-snippets-manager.html
 
 * @license BSD License
 * @filename sqlite-snippets-admin.php
 *
 * please leave this comment block intact so others can find the original source.
 *
 * description: An example admin page for SQLiteCodeVault class to manage code snippets in a SQLite database.
 
 // fixed up listing rows a bit for the add section update
 // added some classes to db for json
 
 // one day I will have to get in to clean this beast, but today isn't that day. Neither is tomorrow.
 
 // v1.1 has a json export/import feature
 
 // currently broken: using not in the search field (and/or work)
 
 // added copy button to copy the code to the clipboard
 // added syntax highlighting and theme dropdown for syntax highlighting
 // added copy json button to copy the json to the clipboard
 // added cookie storing ability to remember the last used theme
 
 // created a cookie management javascript class for the ace.js code editor
 // created a jquery plugin to:
 // - handle the cookie management for combo boxes
 // - attach the ace.js code editor to the code textarea
 // - this was not fun :( but hey it works.
 // -- adds all sorts of cool ways to edit your code with syntax highlighting, error checking, autoindent, etc.
 
 // -- so using highlight.js for the listings and ace.js for the code editor. Note: only currently attached to update code section, but easy enough to add to add section which I will do if everything runs smooth in the update section, which I highly doubt, but hey, fingers crossed.
 
 // fixed some width issues with the page.
 
 */
 
 ob_start();
 session_start();
 
 // Include SQLiteCodeVault class
 require_once 'sqlite-snippets-manager.php';
 
 // Initialize the class
 $snippets = new PHPSQLiteCodeVault();
 
 function get_snippet_data_html($snippet_row)
 {
 global $snippets;
 
 $html = "";
 
 // create a page anchor
 $html .= "<a name='" . $snippet_row['id'] . "'></a>";
 
 $html .= "<div class='title'>" . $snippet_row['title'] . "</div>";
 
 $html .= "<div class='subcontent' style='display:none'>";
 
 $html .= "<div class='code-snippet' data-id='" . $snippet_row['id'] . "'>" . $snippet_row['code'] . "</div>";
 
 $html .= "<div class='json-row'>";
 $html .= "<div class=' json-title'>JSON Export:</div>";
 $html .= "<div class=' json'><textarea class='json-textarea' data-id='" . $snippet_row['id'] . "'>" . $snippets->snippet_to_json_str($snippet_row) . "</textarea></div>";
 $html .= "</div><!-- end json-row -->";
 
 $html .= "<div class='subinfo'>";
 $html .= "<div class='language'>language: " . $snippet_row['language'] . "</div>";
 $html .= "<div class='tags'>tags: " . $snippet_row['tags'] . "</div>";
 $html .= "<div class='author'>author: " . $snippet_row['author'] . "</div>";
 $html .= "<div class='license'>license: " . $snippet_row['license'] . "</div>";
 $html .= "<div class='timestamp'>" . date('Y-m-d H:i:s', $snippet_row['timestamp']) . "</div>";
 $html .= "</div><!-- end subinfo -->";
 
 
 $html .= "<div class='actions'>";
 $html .= '<div><button class="copyBtn" data-id="' . $snippet_row['id'] . '">Copy</button></div>';
 $html .= '<div><button class="copyJsonBtn" data-id="' . $snippet_row['id'] . '">Copy JSON</button></div>';
 $html .= "<div><button class='editBtn' data-id='" . $snippet_row['id'] . "' data-title='" . $snippet_row['title'] . "' data-code='" . htmlentities($snippet_row['code']) . "' data-language='" . $snippet_row['language'] . "' data-tags='" . $snippet_row['tags'] . "' data-author='" . $snippet_row['author'] . "' data-license='" . $snippet_row['license'] . "'>Edit</button></div>";
 $html .= '<div><button class="deleteBtn" data-id="' . $snippet_row['id'] . '">Delete</button></div>';
 
 $html .= "</div>";
 
 
 
 $html .= "</div><!-- end subcontent -->";
 
 
 return $html;
 }
 
 function get_snippet_html($snippet_row, $evenodd="odd")
 {
 // make safe for html
 $snippet_row['title']       = htmlspecialchars($snippet_row['title']);
 $snippet_row['code']        = htmlspecialchars($snippet_row['code']);
 $snippet_row['language']    = htmlspecialchars($snippet_row['language']);
 $snippet_row['tags']        = htmlspecialchars($snippet_row['tags']);
 $snippet_row['author']      = htmlspecialchars($snippet_row['author']);
 $snippet_row['license']     = htmlspecialchars($snippet_row['license']);
 
 
 // Return HTML for a snippet
 $html = "";
 $html .= "<div class='snippet $evenodd' data-id='" . $snippet_row['id'] . "'>";
 $html .= "<div class='snippet-data' data-id='" . $snippet_row['id'] . "'>";
 
 $html .= get_snippet_data_html($snippet_row);
 
 $html .= "</div>";
 
 
 $html .= "</div>";
 
 return $html;
 }
 
 function get_snippets_html()
 {
 global $snippets;
 
 // Return HTML for all snippets to go in .snippets div
 $html = "";
 /*
 $evenodd = 'odd';
 foreach ($snippetsList as $snippet) {
 
 echo get_snippet_html($snippet, $evenodd);
 if ($evenodd == 'odd')
 $evenodd = 'even';
 else
 $evenodd = 'odd';
 }
 */
 
 // if $_SESSION['search'] is set, then search for snippets
 if (isset($_SESSION['search']) && !empty(trim($_SESSION['search']))) {
 $snippetsList = $snippets->fuzzySearch($_SESSION['search']);
 } else {
 // If no search is submitted, get all snippets from database
 $snippetsList = $snippets->getAllSnippets();
 }
 
 $evenodd = "odd";
 foreach ($snippetsList as $snippet) {
 $html .= get_snippet_html($snippet, $evenodd);
 if ($evenodd == "odd") {
 $evenodd = "even";
 } else {
 $evenodd = "odd";
 }
 }
 
 return $html;
 }
 
 
 
 
 
 
 
 
 // If the form is submitted to add a new snippet
 if (isset($_POST['addSnippet'])) {
 // Get form data and add snippet to database
 $snippets->addSnippet($_POST['title'], $_POST['code'], $_POST['language'], $_POST['tags'], $_POST['author'], $_POST['license'], time());
 }
 
 // If the form is submitted to update a snippet
 if (isset($_POST['updateSnippet'])) {
 // Get form data and update snippet in database
 $snippets->saveSnippet($_POST['id'], $_POST['title'], $_POST['code'], $_POST['language'], $_POST['tags'], $_POST['author'], $_POST['license'], time());
 }
 
 // If the form is submitted to search for snippets
 if (isset($_POST['searchSnippet'])&& !empty(trim($_POST['search']))) {
 // Get form data and search for snippets in database
 # $snippetsList = $snippets->getSnippetsBySearch($_POST['search']);
 
 // $snippetsList = $snippets->fuzzySearch($_POST['search']);
 
 $_SESSION['search'] = $_POST['search'];
 } else
 if (isset($_POST['searchSnippet']) && empty(trim($_POST['search']))) {
 $_SESSION['search'] = '%';
 }
 
 // else {
 //     // If no search is submitted, get all snippets from database
 //     // $snippetsList = $snippets->getAllSnippets();
 //     $_SESSION['search'] = '';
 // }
 
 
 
 
 if (isset($_GET['cmd']) && $_GET['cmd'] == 'ajax') {
 // If the request is an AJAX request, return the snippets list and quit
 # echo $snippetsList;
 // $evenodd = "odd";
 // foreach ($snippetsList as $snippet) {
 //     echo get_snippet_html($snippet, $evenodd);
 //     if ($evenodd == "odd") {
 //         $evenodd = "even";
 //     } else {
 //         $evenodd = "odd";
 //     }
 // }
 echo get_snippets_html();
 
 exit;
 } // end if ajax
 
 
 ?><?php
 
 /*
 // Cancel page reload on form submit for update snippet form
 $('#updateSnippetForm form').submit(function(e) {
 
 // Get form data
 var id = $('#updateSnippetForm').find('input[name="id"]').val();
 var title = $('#updateSnippetForm').find('input[name="title"]').val();
 var code = $('#updateSnippetForm').find('textarea[name="code"]').val();
 var language = $('#updateSnippetForm').find('input[name="language"]').val();
 var tags = $('#updateSnippetForm').find('input[name="tags"]').val();
 var author = $('#updateSnippetForm').find('input[name="author"]').val();
 var license = $('#updateSnippetForm').find('input[name="license"]').val();
 
 // Update snippet in database
 $.ajax({
 url: 'sqlite-snippets-admin.php?cmd=ajax',
 type: 'post',
 data: {
 updateSnippet: true,
 id: id,
 title: title,
 code: code,
 language: language,
 tags: tags,
 author: author,
 license: license
 },
 success: function(response) {
 // If snippet is updated successfully, reload the snippet list
 $('#snippetsList').html(response);
 alert('Snippet updated successfully!');
 }
 });
 
 e.preventDefault();
 
 }); // end -- update snippet form submit
 */
 
 // --- BEGIN --- PHP SIDE OF COMMAND PROCESSOR
 
 if(!empty($_GET['ajax']))
 {
 switch($_GET['ajax'])
 {
 case 'add-from-json':
 $json = $_POST['flds']['json'];
 $snippets->snippet_from_json_str_to_db($json);
 
 $return_arr[] = array(
 "command"     => 'alert',
 "process"     => "add-from-json",
 "msg"         => "added from json"
 );
 
 // update snippet list
 $return_arr[] = array(
 "command"     => 'html',
 "selector"     => ".snippets",
 "msg"         => get_snippets_html()
 );
 
 
 $return_arr[] = array(
 "command"     => 'html',
 "selector"     => "#snippetCount",
 "msg"         => $snippets->getSnippetCount()." Snippets in DB"
 );
 
 break;
 
 
 case 'update':
 $id             = $_POST['flds']['id'];
 $title          = $_POST['flds']['title'];
 $code           = $_POST['flds']['code'];
 $language       = $_POST['flds']['language'];
 $tags           = $_POST['flds']['tags'];
 $author         = $_POST['flds']['author'];
 $license        = $_POST['flds']['license'];
 
 if(empty($id))
 {
 $return_arr[] = array(
 "command"     => 'alert',
 "process"     => "update",
 "msg"         => "id is empty for update"
 );
 }
 else
 {
 // update ( saveSnippet($id, $title, $code, $language, $tags, $author, $license, $timestamp) )
 $snippets->saveSnippet($id, $title, $code, $language, $tags, $author, $license, time());
 
 $return_arr[] = array(
 "command"     => 'alert',
 "process"     => "update",
 "msg"         => "updated id: $id"
 );
 
 
 $snippet = $snippets->getSnippetById($id);
 ## print_r($snippet['code']);
 // for sending back to a <pre></pre> fix the stripping of <html> tags
 $snippet['code'] = htmlspecialchars($snippet['code']);
 
 $return_arr[] = array(
 "command"     => 'html',
 "selector"     => ".snippet-data[data-id='$id']",
 "msg"         => get_snippet_data_html($snippet)
 // "msg"         => get_snippet_data_html($snippets->getSnippetById($id))
 );
 
 
 // $return_array[] = array(
 //     "command"     => 'html',
 //     "selector"     => '.snippet12',
 //     // "msg"         => get_snippet_html($snippets->getSnippetById($id))
 //     "msg"         => '....'
 // );
 
 // print_r($return_array);
 }
 break; // end -- update snippet
 
 case 'delete':
 $id = $_POST['flds']['id'];
 
 if(empty($id))
 {
 $return_arr[] = array(
 "command"     => 'alert',
 "process"     => "delete",
 "msg"         => "id is empty for delete"
 );
 }
 else
 {
 // delete ( deleteSnippet($id) )
 $snippets->deleteSnippet($id);
 
 $return_arr[] = array(
 "command"     => 'alert',
 "process"     => "delete",
 "msg"         => "deleted id: $id"
 );
 
 $return_arr[] = array(
 "command"     => 'html',
 "selector"     => "#snippetCount",
 "msg"         => $snippets->getSnippetCount()." Snippets in DB"
 );
 
 
 }
 break; // end -- delete snippet
 
 
 }
 if(!empty($return_arr) && is_array($return_arr))
 die(json_encode($return_arr));
 
 die(); // ajax request, so just quit instead of showing a page
 
 
 
 }
 
 // --- END --- PHP SIDE OF COMMAND PROCESSOR
 
 ?>
 
 
 <!DOCTYPE html>
 <html lang="en">
 <head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Snippet Manager</title>
 <link rel="stylesheet" href="style.css">
 <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
 <script>
 $(document).ready(function() {
 // Show add snippet form when add button is clicked
 $('#addBtn').click(function() {
 $('#addSnippetForm').toggle();
 });
 
 // Hide add snippet form when cancel button is clicked
 $('#cancelBtn').click(function() {
 $('#addSnippetForm').hide();
 });
 
 
 // Cancel page reload on form submit for add snippet form
 $('#addSnippetForm form').submit(function(e) {
 
 // Get form data
 var title = $('#addSnippetForm').find('input[name="title"]').val();
 var code = $('#addSnippetForm').find('textarea[name="code"]').val();
 var language = $('#addSnippetForm').find('input[name="language"]').val();
 var tags = $('#addSnippetForm').find('input[name="tags"]').val();
 var author = $('#addSnippetForm').find('input[name="author"]').val();
 var license = $('#addSnippetForm').find('input[name="license"]').val();
 
 // Add snippet to database
 $.ajax({
 url: 'sqlite-snippets-admin.php?cmd=ajax',
 type: 'post',
 data: {
 addSnippet: true,
 title: title,
 code: code,
 language: language,
 tags: tags,
 author: author,
 license: license
 },
 success: function(response) {
 // If snippet is added successfully, reload the snippet list
 $('#snippetsList').html(response);
 alert('Snippet added successfully!');
 }
 
 
 });
 
 e.preventDefault();
 
 });
 
 
 
 // Cancel page reload on form submit for update snippet form
 // OBSOLETE.. Now using command processor
 $('#updateSnippetForm form').submit(function(e) {
 
 // Get form data
 var id = $('#updateSnippetForm').find('input[name="id"]').val();
 var title = $('#updateSnippetForm').find('input[name="title"]').val();
 var code = $('#updateSnippetForm').find('textarea[name="code"]').val();
 var language = $('#updateSnippetForm').find('input[name="language"]').val();
 var tags = $('#updateSnippetForm').find('input[name="tags"]').val();
 var author = $('#updateSnippetForm').find('input[name="author"]').val();
 var license = $('#updateSnippetForm').find('input[name="license"]').val();
 
 // Update snippet in database
 $.ajax({
 url: 'sqlite-snippets-admin.php?cmd=ajax',
 type: 'post',
 data: {
 updateSnippet: true,
 id: id,
 title: title,
 code: code,
 language: language,
 tags: tags,
 author: author,
 license: license
 },
 success: function(response) {
 // If snippet is updated successfully, reload the snippet list
 $('#snippetsList').html(response);
 alert('Snippet updated successfully!');
 }
 });
 
 e.preventDefault();
 
 }); // end -- update snippet form submit
 
 // Show edit snippet form when edit button is clicked (handle dynamically created edit buttons too)
 // $('.editBtn').click(function() { // old way
 $(document).on('click', '.editBtn', function() {
 var id = $(this).data('id');
 var title = $(this).data('title');
 var code = $(this).data('code');
 var language = $(this).data('language');
 var tags = $(this).data('tags');
 var author = $(this).data('author');
 var license = $(this).data('license');
 
 // code is escaped so unescape it
 const parser = new DOMParser();
 const doc = parser.parseFromString(code, 'text/html');
 code = doc.documentElement.textContent;
 
 $('#updateSnippetForm').find('input[name="id"]').val(id);
 $('#updateSnippetForm').find('input[name="title"]').val(title);
 $('#updateSnippetForm').find('textarea[name="code"]').val(code);
 $('#updateSnippetForm').find('input[name="language"]').val(language);
 $('#updateSnippetForm').find('input[name="tags"]').val(tags);
 $('#updateSnippetForm').find('input[name="author"]').val(author);
 $('#updateSnippetForm').find('input[name="license"]').val(license);
 
 $('#updateSnippetForm').show();
 // scroll to #updateSnippetForm
 $('html, body').animate({
 scrollTop: $("#updateSnippetForm").offset().top
 }, 1000);
 });
 
 // Hide edit snippet form when cancel button is clicked
 $('#cancelBtn').click(function() {
 $('#updateSnippetForm').hide();
 });
 });
 </script>
 </head>
 <body>
 
 <div class='row'>
 <div class='col'><h1 id='pageTtl'>Snippet Manager</h1></div>
 
 <div class='col'><button id="addBtn">Show/Hide Add Snippet</button></div>
 </div>
 
 
 
 <style>
 body {
 font-family:arial;
 }
 
 .row {
 display:flex;
 flex-direction:row;
 justify-content:space-between;
 align-items:center;
 margin:2vw;
 }
 
 .col {
 flex:1;
 }
 
 #pageTtl {
 margin-top:12px;
 font-size:2min;
 }
 
 #addBtn {
 float:right;
 margin-top:0;
 }
 
 
 
 #snippetCount {
 position:fixed;
 /* top:0; */
 bottom:0px;
 
 /* right:10px; */
 left:50%;
 transform:translateX(-50%);
 
 
 background-color: #fff;
 padding: 10px 20px;
 margin-top:0px;
 z-index:9999;
 
 border:1px solid #ccc;
 cursor:pointer;
 
 }
 </style>
 <div id="snippetCount"><?php echo $snippets->getSnippetCount(); ?> Snippets in DB</div>
 
 
 
 
 
 
 <div id="addSnippetForm" style="display:none;">
 
 <style>
 #addSnippetFromJson {
 margin: 20px 0;
 }
 #addSnippetFromJson .fld {
 margin: 5px 0;
 }
 #addSnippetFromJson .fld input {
 width: 100%;
 padding-top:1em;
 padding-bottom:1em;
 }
 </style>
 
 <div id="addSnippetFromJson">
 <div class='title'><h2>Add Snippet From JSON</h2></div>
 <div class='fld'><input type="text" name="json" placeholder="JSON" required></div>
 <div class='fld'><input type="button" class="addJsonBtn" name="addSnippetFromJson" value="Add Snippet From JSON"></div>
 </div>
 
 
 <h2>Add Snippet</h2>
 <form>
 <input type="text" name="title" placeholder="Title" required>
 <textarea class='code-textarea' name="code" placeholder="Code" required></textarea>
 <input type="text" name="language" placeholder="Language" required>
 <input type="text" name="tags" placeholder="Tags" required>
 <input type="text" name="author" placeholder="Author" required>
 <input type="text" name="license" placeholder="License" required>
 <input type="submit" name="addSnippet" value="Add Snippet">
 </form>
 </div>
 
 <br />
 
 <div id="updateSnippetForm" style="display:none;">
 <h2>Edit Snippet</h2>
 <!-- just have action cancel page reload -->
 <form action="return false;">
 <input type="hidden" name="id">
 <input type="text" name="title" placeholder="Title" required>
 <textarea class='code-textarea use_ace_edit' name="code" placeholder="Code" required></textarea>
 <input type="text" name="language" placeholder="Language" required>
 <input type="text" name="tags" placeholder="Tags" required>
 <input type="text" name="author" placeholder="Author" required>
 <input type="text" name="license" placeholder="License" required>
 <!-- <input type="submit" name="updateSnippet" value="Update Snippet"> -->
 <input type="button" id="updateBtn" value="Update">
 <input type="button" id="cancelBtn" value="Cancel">
 </form>
 
 </div>
 
 <form method="post">
 <input type="text" name="search" placeholder="Search Snippets">
 <input type="submit" name="searchSnippet" value="Search">
 </form>
 
 
 
 <h2>Snippets List:<?php if(!empty($_SESSION['search'])) echo ' (Search: ' . $_SESSION['search']. ')'; ?></h2>
 
 <div id="snippetsList">
 
 
 <div class='snippets'>
 
 <?php
 echo get_snippets_html();
 // $evenodd = 'odd';
 // foreach ($snippetsList as $snippet) {
 
 //     echo get_snippet_html($snippet, $evenodd);
 //     if ($evenodd == 'odd')
 //         $evenodd = 'even';
 //     else
 //         $evenodd = 'odd';
 // }
 ?>
 
 </div>
 
 
 </div>
 
 
 <script>
 // BEGIN --> JAVASCRIPT COMMAND PROCESSOR //
 
 function do_cmd_post(url, send_data)
 {
 $.post( url, {
 flds: send_data /* in php will appear as $_POST['flds']  */ },
 function( return_data ) {
 do_cmd_process(return_data);
 }, "json" ); // punt any returned data to processor
 
 }
 // ---
 function do_cmd_process(data) // handle data coming back from ajax
 {
 
 if (data instanceof Array) {
 data.forEach(function(entry) {
 console.log(entry.command);
 
 //console.log(entry.message);
 // handle returned commands //
 switch(entry.command)
 {
 // generic commands //
 case 'alert':
 alert(entry.msg);
 break;
 case 'log':
 console.log(entry.msg);
 break;
 case 'append':
 $(entry.selector).append(entry.msg);
 break;
 case 'prepend':
 $(entry.selector).prepend(entry.msg);
 break;
 case 'html':
 $(entry.selector).html(entry.msg);
 break;
 case 'val':
 $(entry.selector).val(entry.msg);
 break;
 case 'focus':
 $(entry.selector).focus();
 break;
 case 'blur':
 $(entry.selector).blur();
 break;
 case 'clear':
 $(entry.selector).val('');
 break;
 case 'js':
 eval(entry.msg);
 break;
 case 'resize_textarea_to_fit_contents':
 $(entry.selector).height(0);
 $(entry.selector).height($(entry.selector)[0].scrollHeight);
 break;
 case 'disable_input':
 $(entry.selector).prop('disabled', true);
 break;
 case 'enable_input':
 $(entry.selector).prop('disabled', false);
 break;
 
 case 'resize_textareas':
 $(".message_content textarea").each(function(){
 $(this).css("height", ($(this).prop("scrollHeight")) + "px");
 });
 
 break;
 
 } // end switch
 });
 }
 }
 
 // END --> JAVASCRIPT COMMAND PROCESSOR //
 
 $(document).ready(function() {
 // handle copy json button
 $(document).on('click', '.copyJsonBtn', function() {
 
 // get id from attr on button called data-id
 var id = $(this).attr('data-id');
 // get text from a dynamic element .code-snippet with a data-id of id
 var text_fld = $('.json-textarea[data-id="' + id + '"]').val();
 // now copy text_fld to clipboard
 // create a temporary input element
 var $temp = $("<input>");
 // add it to the document
 $("body").append($temp);
 // set the value of the input to the text_fld
 $temp.val(text_fld).select();
 // copy the text to the clipboard
 document.execCommand("copy");
 // remove the temporary input
 $temp.remove();
 
 alert('copied json to clipboard');
 
 }); // end -- handle copy json button
 
 // handle copy button
 $(document).on('click', '.copyBtn', function() {
 
 // get id from attr on button called data-id
 var id = $(this).attr('data-id');
 // get text from a dynamic element .code-snippet with a data-id of id
 var text_fld = $('.code-snippet[data-id="' + id + '"]').text();
 // now copy text_fld to clipboard
 // create a temporary input element
 var $temp = $("<input>");
 // add it to the document
 $("body").append($temp);
 // set the value of the input to the text_fld
 $temp.val(text_fld).select();
 // copy the text to the clipboard
 document.execCommand("copy");
 // remove the temporary input
 $temp.remove();
 
 alert('copied source code to clipboard');
 
 }); // end -- handle copy button
 
 // on click a dynamically created .addJsonBtn
 // add snippet from json
 $(document).on('click', '.addJsonBtn', function() {
 
 // get json from input
 var json = $('input[name="json"]').val();
 
 var send_data   = {
 //"file": $('select[name="conversation_combobox"]').val()
 "json": json
 };
 
 do_cmd_post('sqlite-snippets-admin.php?ajax=add-from-json', send_data);
 
 }); // end -- add snippet from json
 
 //  on click a dynamically created .deleteBtn
 // delete button will alert ('hi')
 $(document).on('click', '.deleteBtn', function() {
 
 // confirm delete
 if(!confirm('Are you sure you want to delete this snippet?'))
 return;
 
 // get id from attr on button called data-id
 var id = $(this).attr('data-id');
 
 var send_data   = {
 //"file": $('select[name="conversation_combobox"]').val()
 "id": id
 };
 
 // hide .snippet with data-id = id
 $('.snippet[data-id="' + id + '"]').hide();
 
 do_cmd_post('sqlite-snippets-admin.php?ajax=delete', send_data);
 
 }); // end -- delete snippet
 
 // updateBtn
 $(document).on('click', '#updateBtn', function() {
 
 // get id from attr on button called data-id
 var id = $('#updateSnippetForm').find('input[name="id"]').val();
 
 var send_data   = {
 //"file": $('select[name="conversation_combobox"]').val()
 "id": id,
 "title": $('#updateSnippetForm').find('input[name="title"]').val(),
 "code": $('#updateSnippetForm').find('textarea[name="code"]').val(),
 "language": $('#updateSnippetForm').find('input[name="language"]').val(),
 "tags": $('#updateSnippetForm').find('input[name="tags"]').val(),
 "author": $('#updateSnippetForm').find('input[name="author"]').val(),
 "license": $('#updateSnippetForm').find('input[name="license"]').val()
 };
 
 do_cmd_post('sqlite-snippets-admin.php?ajax=update', send_data);
 
 }); // end -- update snippet
 
 
 });
 
 
 </script>
 
 
 <style>
 
 
 .snippet {
 position:relative;
 display:block;
 border: 1px solid #ccc;
 }
 .snippet-data {
 position:relative;
 display:block;
 }
 
 .snippet .actions {
 position:relative;
 display:block;
 text-align:right;
 
 }
 
 .snippet .actions div,
 .snippet-data div  {
 display:inline-block;
 padding:1vw;
 
 }
 
 .snippet .actions div {
 cursor:pointer;
 }
 
 .snippet.even {
 background-color:#bbb;
 
 }
 
 .snippet.odd {
 background-color:#ddd;
 }
 
 /* ****************************************** */
 
 /* Container */
 #addSnippetForm, #updateSnippetForm {
 background-color: #f2f2f2;
 border-radius: 5px;
 padding: 20px;
 width: 80%;
 margin: 0 auto;
 box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
 }
 
 /* Form elements */
 input[type=text],
 input[type=submit],
 textarea {
 width: 100%;
 padding: 12px;
 border: 1px solid #ccc;
 border-radius: 4px;
 box-sizing: border-box;
 resize: vertical;
 font-size: 16px;
 font-family: "Arial", sans-serif;
 margin-bottom: 12px;
 }
 
 textarea {
 min-height: 100px;
 }
 
 /* Placeholder styling */
 input[type=text]::placeholder,
 textarea::placeholder {
 color: #999;
 font-style: italic;
 }
 
 /* Submit button */
 input[type=submit] {
 background-color: #04AA6D;
 color: white;
 cursor: pointer;
 transition: background-color 0.2s;
 }
 
 input[type=submit]:hover {
 background-color: #048458;
 }
 
 
 /*  button */
 input[type=button] {
 background-color: #007bff;
 border: none;
 border-radius: 4px;
 color: white;
 cursor: pointer;
 font-family: 'Roboto', sans-serif;
 font-size: 0.9rem;
 padding: 0.5rem 1rem;
 text-transform: uppercase;
 }
 
 input[type=button]:hover {
 background-color: #0056b3;
 }
 
 
 
 
 /* Form headings */
 h2 {
 font-size: 24px;
 font-weight: bold;
 margin-bottom: 20px;
 }
 
 
 
 /* --- */
 
 
 
 .snippets {
 display: grid;
 /* grid-template-columns: repeat(auto-fill, minmax(1400px, 1fr)); */
 grid-gap: 1rem;
 margin: 1rem;
 }
 
 .snippet {
 background-color: #f9f9f9;
 border: 1px solid #e0e0e0;
 border-radius: 4px;
 padding: 1rem;
 font-family: 'Roboto', sans-serif;
 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 }
 
 .snippet-data {
 display: flex;
 flex-direction: column;
 }
 
 .title,
 .language,
 .tags,
 .author,
 .license,
 .timestamp {
 font-size: 0.9rem;
 margin-bottom: 0.5rem;
 }
 
 
 
 .title {
 font-weight: bold;
 font-size: 1.1rem;
 margin-bottom: 0.8rem;
 /* border-bottom: 1px solid #e0e0e0; */
 /* make nicer */
 padding-bottom: 0.5rem;
 
 cursor:pointer;
 }
 
 .code-snippet {
 background-color: #f0f0f0;
 border: 1px solid #e0e0e0;
 border-radius: 4px;
 padding: 0.5rem;
 font-family: 'Courier', monospace;
 white-space: pre-wrap;
 overflow-x: auto;
 }
 
 .actions {
 display: flex;
 justify-content: space-between;
 margin-top: 1rem;
 }
 
 button {
 background-color: #007bff;
 border: none;
 border-radius: 4px;
 color: white;
 cursor: pointer;
 font-family: 'Roboto', sans-serif;
 font-size: 0.9rem;
 padding: 0.5rem 1rem;
 text-transform: uppercase;
 }
 
 button:hover {
 background-color: #0056b3;
 }
 
 .code-snippet .subinfo {
 display: flex;
 justify-content: space-between;
 margin-top: 1rem;
 }
 
 .code-snippet .subinfo .left {
 display: flex;
 flex-direction: column;
 }
 
 .code-snippet .subinfo .right {
 display: flex;
 flex-direction: column;
 align-items: flex-end;
 }
 
 .code-snippet .subinfo .left .language,
 .code-snippet .subinfo .left .tags,
 .code-snippet .subinfo .left .author,
 .code-snippet .subinfo .left .license {
 font-size: 0.9rem;
 margin-bottom: 0.5rem;
 }
 
 .code-snippet .subinfo .right .timestamp {
 font-size: 0.9rem;
 margin-bottom: 0.5rem;
 }
 
 
 
 
 
 .json-row {
 position:relative;
 display: block;
 justify-content: space-between;
 margin-top: 1rem;
 width:100%;
 
 }
 .json-row div
 {
 position:relative;
 display:block;
 width:97%;
 margin:0;
 padding:0;
 }
 
 .json-row textarea {
 width:100%;
 }
 
 
 
 /* --- */
 
 
 </style>
 
 
 <script>
 /* when snippet-data .title is clicked, toggle the code snippet (nearest .subcontent section) */
 $(document).ready(function(){
 // $(".snippet-data .title").click(function(){
 //     $(this).next(".subcontent").slideToggle("slow");
 // });
 /* handle dynamic */
 $(document).on('click', '.snippet-data .title', function(){
 $(this).next(".subcontent").slideToggle("fast");
 });
 });
 </script>
 
 
 
 
 
 
 
 <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
 
 <!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/default.min.css"> -->
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/monokai.min.css">
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/github.min.css">
 <!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/styles/atom-one-dark.min.css"> -->
 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.6.0/highlight.min.js"></script>
 
 
 <style>
 .highlight-theme {
 position:fixed;
 bottom:0px;
 left:0px;
 margin:2px;
 
 }
 
 .highlight-theme-label {
 position:fixed;
 bottom:22px;
 left:0px;
 margin:2px;
 font-size:0.8rem;
 font-weight:bold;
 }
 
 </style>
 
 <!-- label for select -->
 <label class='highlight-theme-label' for="highlight-theme">Highlight.js theme:</label>
 <select name='highlight-theme' class='highlight-theme' onchange="switch_highlight_theme(this.value)">
 <option value='github' SELECTED>github</option>
 <!-- <option value='default'>default</option> -->
 <!-- <option value='monokai'>monokai</option> -->
 <!-- <option value='atom-one-dark'>atom-one-dark</option> -->
 <!-- <option value='nord'>nord</option> -->
 <!-- a11y-dark,a11y-light,agate,an-old-hope,androidstudio,arduino-light,arta,ascetic,atom-one-dark-reasonable,atom-one-light,brown-paper,dark,github-dark-dimmed,github-dark -->
 <!-- <option value='github-dark'>github-dark</option> -->
 <!-- <option value='a11y-dark'>a11y-dark</option> -->
 <option value='a11y-light'>a11y-light</option>
 <!-- <option value='agate'>agate</option> -->
 <option value='arduino-light'>arduino-light</option>
 <!-- <option value='arta'>arta</option> -->
 <!-- <option value='atom-one-dark-reasonable'>atom-one-dark-reasonable</option> -->
 <option value='googlecode'>googlecode</option>
 <option value='intellij-light'>intellij-light</option>
 <!-- <option value='kimbie-light'>kimbie-light</option> -->
 
 </select>
 
 <script>
 function getCookie(cname)
 {
 var name = cname + "=";
 var decodedCookie = decodeURIComponent(document.cookie);
 var ca = decodedCookie.split(';');
 // console.log(ca);
 for(var i = 0; i <ca.length; i++)
 {
 var c = ca[i];
 // console.log(c);
 while (c.charAt(0) == ' ')
 {
 c = c.substring(1);
 }
 if (c.indexOf(name) == 0)
 {
 return c.substring(name.length, c.length);
 }
 }
 return "";
 }
 
 // if there is a cookie set, switch to that theme
 // on document ready
 $(document).ready(function(){
 var highlight_theme = getCookie('highlight-theme');
 if (highlight_theme != "") {
 switch_highlight_theme(highlight_theme);
 }
 });
 
 
 function switch_highlight_theme(themename)
 {
 /* save a cookie with current highlight */
 document.cookie = "highlight-theme="+themename+"; path=/; SameSite=None; Secure";
 // remove all highlight.js stylesheets
 $('link[href*="highlight.js"]').remove();
 // add the new one
 $('head').append('<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/styles/'+themename+'.min.css">');
 // update the highlights
 update_highlights();
 }
 </script>
 
 
 <style>
 /* for highlights */
 .code-snippet {
 background-color:#fff;
 /* color:#fff; */
 padding:1rem;
 border:1px solid #333;
 
 }
 </style>
 
 <script>
 
 function update_highlights()
 {
 $(document).ready(function() {
 // update_highlights();
 document.querySelectorAll('#code-textarea,.code-snippet').forEach(el => {
 // if we do not already have a highlit element then..
 if (!el.classList.contains('hljs'))
 {
 // escape the text //
 el.innerHTML = el.innerHTML.replace(/</g, '<').replace(/>/g, '>');
 hljs.highlightElement(el);
 }
 });
 });
 };
 
 update_highlights();
 
 // when a .title is clicked, update_highlights
 $(document).ready(function(){
 $(document).on('click', '.snippet-data .title', function(){
 update_highlights();
 });
 });
 
 
 </script>
 
 
 
 
 <!-- BEGIN BEGIN BEGIN BEGIN -- ace.js code editor (my js cookie class and jq plugin) -->
 
 <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> -->
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.19.0/ace.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.19.0/ext-language_tools.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.19.0/ext-beautify.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.19.0/ext-settings_menu.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.19.0/ext-spellcheck.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.19.0/ext-whitespace.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.19.0/ext-split.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.19.0/ext-searchbox.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.19.0/ext-statusbar.js"></script>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.19.0/ext-textarea.js"></script>
 
 <script>
 // simple cookie class in javascript
 class Cookie {
 constructor() {
 this.cookie = document.cookie;
 }
 
 get(name) {
 const value = this.cookie.match(`(^|;)\\s*${name}\\s*=\\s*([^;]+)`);
 return value ? value.pop() : '';
 }
 
 set(name, value, days) {
 let expires = '';
 if (days) {
 const date = new Date();
 date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
 expires = `; expires=${date.toUTCString()}`;
 }
 // set for SameSite=None;
 
 document.cookie = `${name}=${value || ''}${expires}; path=/; SameSite=None; Secure`;
 
 // document.cookie = `${name}=${value || ''}${expires}; path=/`;
 }
 
 delete(name) {
 this.set(name, '', -1);
 }
 } // end my cookie class
 
 // example usage:
 // const cookie = new Cookie();
 // cookie.set('name', 'value', 7);
 // cookie.get('name');
 // cookie.delete('name');
 
 </script>
 <!-- the plugin -->
 <script>
 (function($) {
 $.fn.customAceEditor = function(removeyn='no', themeselect_id='#theme-selector', modeselect_id='#mode-selector') {
 // removeyn = 'destroy' to remove the editor
 
 // make an editors array
 var editors = [];
 
 if (removeyn == 'destroy') {
 return this.each(function() {
 const textarea = $(this);
 const editorDiv = textarea.prev();
 editorDiv.remove();
 textarea.show();
 textarea.removeClass('hascodearea');
 });
 }
 
 if (this.hasClass('hascodearea')) {
 this.customAceEditor('destroy');
 }
 
 this.each(function() {
 
 const textarea = $(this);
 const editorDiv = $('<div class="hascodearea">').insertBefore(textarea).width(textarea.width()).height(textarea.height());
 
 const textarea_content = textarea.val();
 
 textarea.addClass('hascodearea');
 textarea.hide();
 
 /* to autoresize the Ace editor first you need to set the height of the editor to auto and then call the resize() method on the editor instance. */
 /* example calling the resize method on the editor instance:
 var editor = ace.edit("editor");
 editor.resize();
 */
 
 
 const editor = ace.edit(editorDiv[0], {
 autoScrollEditorIntoView: false,
 width: '100%',
 height: 'auto',
 fontSize: '16px',
 tabSize: 4,
 useSoftTabs: true,
 showPrintMargin: false,
 showGutter: true,
 highlightActiveLine: true,
 wrap: true,
 enableBasicAutocompletion: true,
 enableLiveAutocompletion: true,
 enableSnippets: true,
 maxLines: Infinity,
 minLines: 5,
 maxLines: 900000,
 scrollPastEnd: 1, /* fixed line 1 disappearing */
 });
 
 // add to editors array
 editors.push(editor);
 
 
 /* when window resizes, resize this editor */
 editor.setAutoScrollEditorIntoView(true);
 editor.setValue(textarea_content, 1); // 1 = moves cursor to end
 
 // localstorage
 //    use themeselect_id and modeselect_id to prefix the localstorage keys
 // revamped to use cookies, however have some variables still sharing localstorage name
 
 localstorage_prefix_theme   =  themeselect_id;
 localstorage_prefix_mode    =  modeselect_id;
 // clean the . and # out of the id's
 localstorage_prefix_theme   =  localstorage_prefix_theme.replace(/\.|#/, '');
 localstorage_prefix_mode    =  localstorage_prefix_mode.replace(/\.|#/, '');
 
 cookie_name_theme = localstorage_prefix_theme+'_theme';
 cookie_name_mode  = localstorage_prefix_mode+'_mode';
 
 const cookie = new Cookie();
 
 console.log('cookie theme:',cookie_name_theme,"::",cookie.get(cookie_name_theme));
 console.log('cookie mode:',cookie_name_mode,"::",cookie.get(cookie_name_mode));
 
 const defaultTheme = cookie.get(cookie_name_theme) || 'monokai';
 const defaultMode = cookie.get(cookie_name_mode) || 'text';
 
 
 // ^^ localstorage
 
 editor.setTheme(`ace/theme/${defaultTheme}`);
 editor.session.setMode(`ace/mode/${defaultMode}`);
 
 //   editor.session.on('change input', function() {
 //     textarea.val(editor.getValue());
 //   });
 
 const themes = [
 "monokai",
 "ambiance", "chaos", "chrome", "clouds", "clouds_midnight", "cobalt",
 "crimson_editor", "dawn", "dracula", "dreamweaver", "eclipse", "github",
 "gob", "gruvbox", "idle_fingers", "iplastic", "katzenmilch", "kr_theme",
 "kuroir", "merbivore", "merbivore_soft", "mono_industrial", "monokai",
 "pastel_on_dark", "solarized_dark", "solarized_light", "sqlserver",
 "terminal", "textmate", "tomorrow", "tomorrow_night", "tomorrow_night_blue",
 "tomorrow_night_bright", "tomorrow_night_eighties", "twilight", "vibrant_ink",
 "xcode"
 // ... (other themes)
 ];
 
 const themeSelector = $(themeselect_id);
 // add attr data-cookie=cookie_name_theme
 themeSelector.attr('data-cookietheme', cookie_name_theme);
 themeSelector.attr('test', 'testthemmmme');
 
 themes.forEach(theme => {
 themeSelector.append($('<option>').val(theme).text(theme));
 });
 
 themeSelector.val(defaultTheme);
 
 themeSelector.on('change', function() {
 editor.setTheme(`ace/theme/${this.value}`);
 // localstorage
 // localStorage.setItem(localstorage_prefix_theme+'defaultTheme', this.value);
 // get cookie name from attr data-cookie
 cookie_name = $(this).attr('data-cookietheme')
 const cookie = new Cookie();
 cookie.delete(cookie_name);
 cookie.set(cookie_name, this.value, 7);
 // alert(this.value);
 });
 
 const modes = [
 "text",
 "javascript", "php", "python", "ruby", "html", "css", "php", "java", "c_cpp",
 "markdown", "json", "xml", "yaml", "typescript", "sql", "go", "lua",
 "swift", "perl", "csharp", "rust", "r"
 // ... (other modes)
 ];
 
 const modeSelector = $(modeselect_id);
 // add attr data-cookie=cookie_name_mode
 modeSelector.attr('data-cookiemode', cookie_name_mode);
 
 
 modes.forEach(mode => {
 modeSelector.append($('<option>').val(mode).text(mode));
 });
 
 modeSelector.val(defaultMode);
 
 modeSelector.on('change', function() {
 editor.session.setMode(`ace/mode/${this.value}`);
 // localstorage
 // localStorage.setItem(localstorage_prefix_mode+'defaultMode', this.value);
 // get cookie name from attr data-cookie
 cookie_name = $(this).attr('data-cookiemode')
 const cookie = new Cookie();
 cookie.delete(cookie_name);
 cookie.set(cookie_name, this.value, 7);
 });
 
 //   editor.setValue(textarea.val(), 1);
 // set editor value to textarea
 
 }); // end each
 
 return editors;
 
 
 
 
 };
 })(jQuery); // end jquery plugin
 </script>
 
 <script>
 // use use use
 
 // after the .code-textarea, append a row containing two blank select boxes with a class of .theme-selector and .mode-selector
 // the code for the jquery append
 $('.use_ace_edit').after('<div class="row"><div class="col-sm-6"><select class="theme-selector"></select></div><div class="col-sm-6"><select class="mode-selector"></select></div></div>');
 
 
 // $('.code-editor-1').customAceEditor('no', '.theme-selector-1', '.mode-selector-1');
 // document ready
 
 
 // $('.code-textarea').customAceEditor('no', '.theme-selector', '.mode-selector');
 
 // document ready first
 $(document).ready(function() {
 
 var the_editors = [];
 
 
 $(document).on('click', '.editBtn', function() {
 // when form done showing execute this
 // show and when complete showing do $('#updateSnippetForm')
 // after showing #updateSnippetForm, execute this
 var code = $(this).data('code');
 console.log(code);
 // code is escaped so unescape it
 const parser = new DOMParser();
 const doc = parser.parseFromString(code, 'text/html');
 code = doc.documentElement.textContent;
 console.log(code);
 
 the_editors = $('.use_ace_edit').customAceEditor('no', '.theme-selector', '.mode-selector');
 // find ace editor kind of like
 // editor = document.querySelector('.ace_editor')
 // the_editors should contain an array of ace editors
 // loop through an set their value to code
 the_editors.forEach(editor => {
 editor.setValue(code, 1);
 });
 
 console.log(the_editors);
 
 });
 
 // when a key down is clicked while an ace code editor is focused
 // set the textarea value to the ace editor value
 $(document).on('keydown', function() {
 the_editors.forEach(editor => {
 $('.use_ace_edit').val(editor.getValue());
 console.log(editor.getValue());
 });
 
 });
 
 });
 
 </script>
 
 
 
 <script>
 // get all ace.js code editors on page, and set them to 100% width (fixed an autoresize issue)
 const editors = document.querySelectorAll('.ace_editor');
 editors.forEach(editor => {
 editor.style.width = '100%';
 });
 </script>
 
 <!-- END END END -- ace.js code editor -->
 
 
 
 </body>
 </html>
 |