This commit is contained in:
Joey Kimsey
2024-12-21 16:26:05 -05:00
parent 0c2fa757dd
commit f8e75e847d
59 changed files with 861 additions and 387 deletions

View File

@ -13,6 +13,7 @@ namespace TheTempusProject\Classes;
use TheTempusProject\Houdini\Classes\Issues; use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Houdini\Classes\Forms; use TheTempusProject\Houdini\Classes\Forms;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Canary\Bin\Canary as Debug; use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Bedrock\Functions\Check; use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Bedrock\Functions\Upload; use TheTempusProject\Bedrock\Functions\Upload;
@ -186,17 +187,90 @@ class Preferences {
} }
public function getFormHtml( $populated = [] ) { public function getFormHtml( $populated = [] ) {
// dv( self::$preferences );
$form = ''; $form = '';
// Added so i can force some sort of ordering
$inputTypes = [
'file' => [],
'select' => [],
'timezone' => [],
'checkbox' => [],
'switch' => [],
];
foreach ( self::$preferences as $name => $details ) { foreach ( self::$preferences as $name => $details ) {
$tempPrefsArray = $this->normalizePreferenceArray( $name, $details ); $tempPrefsArray = $this->normalizePreferenceArray( $name, $details );
if ( isset( $populated[ $name ] ) ) { if ( isset( $populated[ $name ] ) ) {
$tempPrefsArray['default'] = $populated[$name]; $tempPrefsArray['default'] = $populated[$name];
} }
$form .= Forms::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['default'], $tempPrefsArray['options'] ); // $form .= Forms::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['default'], $tempPrefsArray['options'] );
if ( $tempPrefsArray['type'] == 'checkbox' ) {
$tempPrefsArray['type'] = 'switch';
}
$inputTypes[ $tempPrefsArray['type'] ][] = self::getFormFieldHtml( $name, $tempPrefsArray['pretty'], $tempPrefsArray['type'], $tempPrefsArray['default'], $tempPrefsArray['options'] );
}
foreach ( $inputTypes as $skip => $items ) {
$form .= implode( ' ', $items );
} }
return $form; return $form;
} }
public static function getFormFieldHtml( $fieldname, $fieldTitle, $type, $defaultValue = '', $options = null ) {
$html = '';
switch ( $type ) {
case 'radio':
case 'bool':
case 'boolean':
$fieldHtml = Forms::getRadioHtml( $fieldname, [ 'true', 'false' ], $defaultValue );
break;
case 'select':
$fieldHtml = Forms::getSelectHtml( $fieldname, $options, $defaultValue );
break;
case 'customSelect':
if ( empty( $options ) ) {
$options = '{' . $fieldname . '-options}';
}
$fieldHtml = Forms::getSelectHtml( $fieldname, $options, $defaultValue );
break;
case 'block':
$fieldHtml = Forms::getTextBlockHtml( $fieldname, $defaultValue );
break;
case 'text':
case 'url':
$fieldHtml = Forms::getTextHtml( $fieldname, $defaultValue );
break;
case 'checkbox':
$fieldHtml = Forms::getCheckboxHtml( $fieldname, $defaultValue );
break;
case 'switch':
$fieldHtml = Forms::getSwitchHtml( $fieldname, $defaultValue );
break;
case 'timezone':
$fieldHtml = Forms::getTimezoneHtml( $defaultValue );
break;
case 'file':
$fieldHtml = Forms::getFileHtml( $fieldname );
break;
default:
Debug::error( "unknown field type: $type" );
break;
}
$html .= '<div class="mb-3 row">';
$html .= '<label for="' . $fieldname . '" class="col-lg-6 col-form-label text-end">' . $fieldTitle . '</label>';
$html .= '<div class="col-lg-6">';
$html .= $fieldHtml;
$html .= '</div>';
if ( 'file' === $type ) {
$html .= '<div class="mb-3 row">';
$html .= '<h4 class="col-lg-6 col-form-label text-end">Current Image</h4>';
$html .= '<div class="col-lg-6">';
$html .= '<img alt="User Avatar" src="{ROOT_URL}' . $defaultValue . '" class="img-circle img-fluid p-2 avatar-125">';
$html .= '</div>';
}
$html .= '</div>';
return Template::parse( $html );
}
public function convertFormToArray( $fillMissing = true, $defaultsOnly = true ) { public function convertFormToArray( $fillMissing = true, $defaultsOnly = true ) {
$prefsArray = []; $prefsArray = [];
foreach ( self::$preferences as $name => $details ) { foreach ( self::$preferences as $name => $details ) {

View File

@ -135,3 +135,8 @@ body {
.text-shadow-3 { .text-shadow-3 {
text-shadow: 0 .5rem 1.5rem rgba(255, 255, 255, .25); text-shadow: 0 .5rem 1.5rem rgba(255, 255, 255, .25);
} }
.form-control {
background-color: #1f1f1f;
color: #e0e0e0;
}

View File

@ -317,31 +317,54 @@ body {
a.atb-green:hover { a.atb-green:hover {
color: #1b947f; color: #1b947f;
} }
button.atb-green:hover {
color: #1b947f;
}
.atb-green-outline-only {
border-color: #85bd3e;
}
a.atb-green-outline-only:hover {
border-color: #85bd3e;
}
button.atb-green-outline-only:hover {
border-color: #85bd3e;
}
.atb-green-outline { .atb-green-outline {
color: #85bd3e; color: #85bd3e;
border-color: #85bd3e; border-color: #85bd3e;
} }
.atb-green-outline-only {
border-color: #85bd3e;
}
a.atb-green-outline:hover { a.atb-green-outline:hover {
color: #1b947f; color: #1b947f;
border-color: #1b947f; border-color: #1b947f;
} }
button.atb-green-outline:hover {
color: #1b947f;
border-color: #1b947f;
}
.atb-green-bg { .atb-green-bg {
color: #fff; color: #fff;
background-color: #85bd3e; background-color: #85bd3e;
border-color: #85bd3e; border-color: #85bd3e;
} }
a.atb-green-bg:hover { a.atb-green-bg:hover {
background-color: #1b947f; background-color: #1b947f;
border-color: #1b947f; border-color: #1b947f;
/* background-color: #44a466; */
/* background-color: #3fa269; */
} }
button.atb-green-bg:hover {
background-color: #1b947f;
border-color: #1b947f;
}
.bookmark-card.dragging { .bookmark-card.dragging {
opacity: 0.7; opacity: 0.7;

View File

@ -71,6 +71,7 @@ class Bookmarks extends Controller {
} }
public function index() { public function index() {
self::$title = 'Manage Bookmarks - {SITENAME}';
if ( Input::exists('submit') ) { if ( Input::exists('submit') ) {
$prefs = new Preferences; $prefs = new Preferences;
$user = new User; $user = new User;
@ -116,6 +117,7 @@ class Bookmarks extends Controller {
} }
public function unsorted() { public function unsorted() {
self::$title = 'Unsorted Bookmarks - {SITENAME}';
$bookmarks = self::$bookmarks->noFolder(); $bookmarks = self::$bookmarks->noFolder();
Views::view( 'bookmarks.bookmarks.unsorted', $bookmarks ); Views::view( 'bookmarks.bookmarks.unsorted', $bookmarks );
} }
@ -147,6 +149,7 @@ class Bookmarks extends Controller {
} }
public function createBookmark( $id = null ) { public function createBookmark( $id = null ) {
self::$title = 'Add Bookmark - {SITENAME}';
$folderID = Input::get('folder_id') ? Input::get('folder_id') : $id; $folderID = Input::get('folder_id') ? Input::get('folder_id') : $id;
$folderID = Input::post('folder_id') ? Input::post('folder_id') : $id; $folderID = Input::post('folder_id') ? Input::post('folder_id') : $id;
$this->setFolderSelect( $folderID ); $this->setFolderSelect( $folderID );
@ -182,6 +185,7 @@ class Bookmarks extends Controller {
} }
public function editBookmark( $id = null ) { public function editBookmark( $id = null ) {
self::$title = 'Edit Bookmark - {SITENAME}';
$folderID = Input::exists('folder_id') ? Input::post('folder_id') : ''; $folderID = Input::exists('folder_id') ? Input::post('folder_id') : '';
$bookmark = self::$bookmarks->findById( $id ); $bookmark = self::$bookmarks->findById( $id );
@ -263,6 +267,7 @@ class Bookmarks extends Controller {
} }
public function createFolder( $id = 0 ) { public function createFolder( $id = 0 ) {
self::$title = 'Create Folder - {SITENAME}';
$folderID = Input::exists('folder_id') ? Input::post('folder_id') : $id; $folderID = Input::exists('folder_id') ? Input::post('folder_id') : $id;
$folders = self::$folders->simpleByUser(); $folders = self::$folders->simpleByUser();
if ( ! empty( $folders ) ) { if ( ! empty( $folders ) ) {
@ -287,6 +292,7 @@ class Bookmarks extends Controller {
} }
public function editFolder( $id = null ) { public function editFolder( $id = null ) {
self::$title = 'Edit Folder - {SITENAME}';
$folder = self::$folders->findById( $id ); $folder = self::$folders->findById( $id );
if ( $folder == false ) { if ( $folder == false ) {
@ -345,6 +351,11 @@ class Bookmarks extends Controller {
* Dashboards * Dashboards
*/ */
public function addDash() { public function addDash() {
self::$title = 'Add Dashboard - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to add dashboards.' );
return $this->index();
}
$folders = self::$folders->byUser() ?? []; $folders = self::$folders->byUser() ?? [];
if ( !empty( $folders ) ) { if ( !empty( $folders ) ) {
@ -387,6 +398,11 @@ class Bookmarks extends Controller {
} }
public function editDash( $id = null ) { public function editDash( $id = null ) {
self::$title = 'Edit Dashboard - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to edit dashboards.' );
return $this->index();
}
$dash = self::$dashboards->findById( $id ); $dash = self::$dashboards->findById( $id );
if ( $dash == false ) { if ( $dash == false ) {
@ -449,6 +465,10 @@ class Bookmarks extends Controller {
} }
public function deleteDash( $id = null ) { public function deleteDash( $id = null ) {
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to delete dashboards.' );
return $this->index();
}
$dash = self::$dashboards->findById( $id ); $dash = self::$dashboards->findById( $id );
if ( $dash == false ) { if ( $dash == false ) {
Issues::add( 'error', 'Unknown Dashboard' ); Issues::add( 'error', 'Unknown Dashboard' );
@ -468,6 +488,11 @@ class Bookmarks extends Controller {
} }
public function dashboard( $uuid = null ) { public function dashboard( $uuid = null ) {
self::$title = 'Bookmark Dashboard - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to view dashboards.' );
return $this->index();
}
$dash = self::$dashboards->findByUuid( $uuid ); $dash = self::$dashboards->findByUuid( $uuid );
if ( $dash == false ) { if ( $dash == false ) {
return $this->dashboards(); return $this->dashboards();
@ -476,6 +501,32 @@ class Bookmarks extends Controller {
Issues::add( 'error', 'You do not have permission to view this dash.' ); Issues::add( 'error', 'You do not have permission to view this dash.' );
return $this->dashboards(); return $this->dashboards();
} }
if ( Input::exists( 'submit' ) ) {
if ( Forms::check( 'updateDashboard' ) ) {
$filters = '';
$folders = '';
if ( is_array( Input::post('link_filter') ) && ! empty( Input::post('link_filter') ) ) {
$filters = implode( ',', Input::post('link_filter') );
}
if ( ! empty( Input::post('link_order') ) ) {
$folders = Input::post('link_order');
}
$result = self::$dashboards->updateDash( $dash->ID, $filters, $folders );
if ( !$result ) {
Issues::add( 'error', [ 'There was an error saving your dashboard.' => Check::userErrors() ] );
} else {
Issues::add( 'success', 'Your dashboard has been saved.' );
}
} else {
Issues::add( 'error', [ 'There was an error saving your dashboard.' => Check::userErrors() ] );
}
unset( $_POST );
}
$dash = self::$dashboards->findByUuid( $uuid );
$foldersArray = []; $foldersArray = [];
if ( ! empty( $dash->link_order ) ) { if ( ! empty( $dash->link_order ) ) {
@ -504,12 +555,19 @@ class Bookmarks extends Controller {
if ( ! empty( $dash->saved_prefs ) ) { if ( ! empty( $dash->saved_prefs ) ) {
$this->setDashToggles( explode( ',', $dash->saved_prefs ) ); $this->setDashToggles( explode( ',', $dash->saved_prefs ) );
} else {
$this->setDashToggles( [] );
} }
return Views::view( 'bookmarks.dashboards.view', $dash ); return Views::view( 'bookmarks.dashboards.view', $dash );
} }
public function dashboards() { public function dashboards() {
self::$title = 'Bookmark Dashboards - {SITENAME}';
if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to manage dashboards.' );
return $this->index();
}
$dashboards = self::$dashboards->byUser(); $dashboards = self::$dashboards->byUser();
return Views::view( 'bookmarks.dashboards.list', $dashboards ); return Views::view( 'bookmarks.dashboards.list', $dashboards );
} }
@ -627,6 +685,7 @@ class Bookmarks extends Controller {
} }
public function import() { public function import() {
self::$title = 'Bookmark Import - {SITENAME}';
if ( !App::$isMember ) { if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to import bookmarks.' ); Issues::add( 'notice', 'You must have an active membership to import bookmarks.' );
return $this->index(); return $this->index();
@ -644,47 +703,41 @@ class Bookmarks extends Controller {
if ( isset( $_FILES['bookmark_file'] ) ) { if ( isset( $_FILES['bookmark_file'] ) ) {
$file = $_FILES['bookmark_file']; $file = $_FILES['bookmark_file'];
// Check file size
if ($file['size'] > 1024 * 1024) { // 1024 KB = 1 MB if ($file['size'] > 1024 * 1024) { // 1024 KB = 1 MB
die('The file is too large. Maximum size is 1 MB.'); Issues::add( 'error', 'There is a 1 meg limit on bookmark imports at this time.' );
return Views::view( 'bookmarks.import' );
} }
// Check file extension
$fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); $fileExtension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
if ($fileExtension !== 'html') { if ($fileExtension !== 'html') {
die('Invalid file type. Only .html files are allowed.'); Issues::add( 'error', 'Invalid file type. Only .html files are allowed.' );
return Views::view( 'bookmarks.import' );
} }
// Proceed with file processing
$fileContent = file_get_contents($file['tmp_name']); $fileContent = file_get_contents($file['tmp_name']);
} else { } else {
die('No file was uploaded.'); Issues::add( 'error', 'No Import detected' );
return Views::view( 'bookmarks.import' );
} }
$out = $this->parseBookmarks($fileContent); $out = $this->parseBookmarks($fileContent);
$date = 'today'; $date = date('F j, Y');
$description = 'Imported on ' . $date . ' from file: ' . $file['name']; $description = 'Imported on ' . $date . ' from file: ' . $file['name'];
$importFolder = self::$folders->create( 'New Import', 0, $description ); $importFolder = self::$folders->create( 'New Import', 0, $description );
// $importFolder = 1;
// echo 'make import folder: ' . PHP_EOL;
foreach ($out as $folder => $bookmarks) { foreach ($out as $folder => $bookmarks) {
// echo 'make folder: ' . $folder . PHP_EOL;
$currentFolder = self::$folders->create( $folder, $importFolder, $description ); $currentFolder = self::$folders->create( $folder, $importFolder, $description );
foreach ($bookmarks as $index => $bookmark) { foreach ($bookmarks as $index => $bookmark) {
// echo 'make folder: ' . $bookmark['url'] . PHP_EOL;
self::$bookmarks->create( $bookmark['name'], $bookmark['url'], $currentFolder); self::$bookmarks->create( $bookmark['name'], $bookmark['url'], $currentFolder);
} }
} }
Session::flash( 'success', 'Your Bookmark has been created.' ); Session::flash( 'success', 'Your Bookmark has been created.' );
Redirect::to( 'bookmarks/bookmarks/'. $importFolder ); Redirect::to( 'bookmarks/bookmarks/'. $importFolder );
// echo '</pre>';
// exit;
// dv ( $out );
} }
public function export() { public function export() {
self::$title = 'Bookmark Export - {SITENAME}';
if ( !App::$isMember ) { if ( !App::$isMember ) {
Issues::add( 'notice', 'You must have an active membership to export bookmarks.' ); Issues::add( 'notice', 'You must have an active membership to export bookmarks.' );
return $this->index(); return $this->index();
@ -720,7 +773,11 @@ class Bookmarks extends Controller {
Session::flash( 'error', 'Folder not found.' ); Session::flash( 'error', 'Folder not found.' );
return Redirect::to( 'bookmarks/index' ); return Redirect::to( 'bookmarks/index' );
} }
$links = self::$bookmarks->byFolder( $folder->ID );
$hiddenExcluded = ! ( Input::post('hiddenIncluded') ?? false );
$archivedExcluded = ! ( Input::post('archivedIncluded') ?? false );
$links = self::$bookmarks->byFolder( $folder->ID, $hiddenExcluded, $archivedExcluded );
$htmlDoc .= $this->exportFolder( $folder->title, $folder->createdAt, $folder->createdAt, $links ); $htmlDoc .= $this->exportFolder( $folder->title, $folder->createdAt, $folder->createdAt, $links );
} }
$htmlDoc .= '</DL><p>' . PHP_EOL; $htmlDoc .= '</DL><p>' . PHP_EOL;
@ -774,6 +831,10 @@ class Bookmarks extends Controller {
return Views::view( 'bookmarks.share', $panelArray ); return Views::view( 'bookmarks.share', $panelArray );
} }
/**
* Private
*/
private function exportFolder( $title, $editedAt, $createdAt, $links ) { private function exportFolder( $title, $editedAt, $createdAt, $links ) {
$htmlDoc = '<DT><H3 ADD_DATE="'.$createdAt.'" LAST_MODIFIED="'.$editedAt.'">'.$title.'</H3>' . PHP_EOL; $htmlDoc = '<DT><H3 ADD_DATE="'.$createdAt.'" LAST_MODIFIED="'.$editedAt.'">'.$title.'</H3>' . PHP_EOL;
$htmlDoc .= '<DL><p>' . PHP_EOL; $htmlDoc .= '<DL><p>' . PHP_EOL;

View File

@ -29,6 +29,7 @@ class BookmarksForms extends Forms {
self::addHandler( 'exportBookmarks', __CLASS__, 'exportBookmarks' ); self::addHandler( 'exportBookmarks', __CLASS__, 'exportBookmarks' );
self::addHandler( 'createDashboard', __CLASS__, 'createDashboard' ); self::addHandler( 'createDashboard', __CLASS__, 'createDashboard' );
self::addHandler( 'editDashboard', __CLASS__, 'editDashboard' ); self::addHandler( 'editDashboard', __CLASS__, 'editDashboard' );
self::addHandler( 'updateDashboard', __CLASS__, 'updateDashboard' );
} }
public static function createBookmark() { public static function createBookmark() {
@ -190,6 +191,17 @@ class BookmarksForms extends Forms {
return true; return true;
} }
public static function updateDashboard() {
if ( ! Input::exists( 'submit' ) ) {
return false;
}
if ( !self::token() ) {
Check::addUserError( 'There was an issue with your request.' );
return false;
}
return true;
}
public static function editDashboard() { public static function editDashboard() {
if ( ! Input::exists( 'submit' ) ) { if ( ! Input::exists( 'submit' ) ) {
return false; return false;

View File

@ -1,30 +1,9 @@
let masonryInstance;
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
// Handle all bootstrap popover inits // Handle all bootstrap popover inits
const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]') const popoverTriggerList = document.querySelectorAll('[data-bs-toggle="popover"]')
const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)) const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl))
const masonryContainer = document.querySelector('[data-masonry]');
if ( ! masonryContainer ) {
return;
}
const masonryInstance = new Masonry(masonryContainer, {
columnHeight: '.accordion',
percentPosition: true
});
const updateMasonryLayout = () => masonryInstance.layout();
masonryContainer.addEventListener('hidden.bs.collapse', updateMasonryLayout);
masonryContainer.addEventListener('shown.bs.collapse', updateMasonryLayout);
const observer = new MutationObserver(() => {
updateMasonryLayout();
});
// Observe all cards for changes in the DOM
document.querySelectorAll('.card').forEach((card) => {
observer.observe(card, { childList: true, subtree: true });
});
// Initialize all toggle functions // Initialize all toggle functions
toggleVisibility('editModeSwitch', 'edit-mode'); toggleVisibility('editModeSwitch', 'edit-mode');
toggleVisibility('showArchivedSwitch', 'link-archived'); toggleVisibility('showArchivedSwitch', 'link-archived');
@ -39,6 +18,119 @@ document.addEventListener('DOMContentLoaded', () => {
toggleVisibility('dashShowHiddenSwitch', 'link-hidden'); toggleVisibility('dashShowHiddenSwitch', 'link-hidden');
toggleVisibility('dashAddButtonSwitch', 'btn-addlink'); toggleVisibility('dashAddButtonSwitch', 'btn-addlink');
toggleVisibility('dashShareButtonSwitch', 'btn-share'); toggleVisibility('dashShareButtonSwitch', 'btn-share');
// Retrieve the list of collapsed folderCard IDs from local storage
const collapsedFolders = JSON.parse(localStorage.getItem( 'collapsedFolders' )) || [];
// Collapse the elements stored in local storage when the page loads
collapsedFolders.forEach((folderId) => {
const collapseElement = document.querySelector(`#Collapse${folderId}`);
if (collapseElement) {
collapseElement.classList.remove('show');
}
});
// Add event listeners to track expand and collapse actions
document.querySelectorAll('.accordion-item').forEach((item) => {
const folderCardId = item.closest('.bookmark-card')?.id?.replace('folderCard', '');
if ( ! folderCardId ) return;
const collapseElement = item.querySelector('.collapse');
// Listen for collapse and expand events
collapseElement.addEventListener('hidden.bs.collapse', () => {
// Add the folderCard ID to local storage when collapsed
if (!collapsedFolders.includes(folderCardId)) {
collapsedFolders.push(folderCardId);
localStorage.setItem( 'collapsedFolders' , JSON.stringify(collapsedFolders));
}
});
collapseElement.addEventListener('shown.bs.collapse', () => {
// Remove the folderCard ID from local storage when expanded
const index = collapsedFolders.indexOf(folderCardId);
if (index > -1) {
collapsedFolders.splice(index, 1);
localStorage.setItem( 'collapsedFolders' , JSON.stringify(collapsedFolders));
}
});
});
const masonryContainer = document.getElementById("bookmarkSort");
if ( ! masonryContainer ) {
return;
}
if ( localStorage.getItem('manageFolderOrder') ) {
loadDashLinkOrder();
}
masonryInstance = new Masonry(masonryContainer, {
columnHeight: '.accordion',
itemSelector: ".bookmark-card",
percentPosition: false
});
const test = () => masonryInstance.layout();
masonryContainer.addEventListener('hidden.bs.collapse', test );
masonryContainer.addEventListener('shown.bs.collapse', test );
// Select all folder-raise and folder-lower buttons
const raiseButtons = document.querySelectorAll(".folder-raise");
const lowerButtons = document.querySelectorAll(".folder-lower");
// Function to move the folderCard up
function moveUp(event) {
const folderCard = event.target.closest(".bookmark-card");
const bookmarkSort = document.getElementById("bookmarkSort");
const orderId = document.getElementById("dashLinkOrder");
if (folderCard) {
const previousSibling = folderCard.previousElementSibling;
if (previousSibling) {
// Remove and reinsert the element before the previous sibling
bookmarkSort.removeChild(folderCard);
bookmarkSort.insertBefore(folderCard, previousSibling);
masonryInstance.reloadItems();
masonryInstance.layout();
updateDashLinkOrder();
}
}
}
// Function to move the folderCard down
function moveDown(event) {
const folderCard = event.target.closest(".bookmark-card");
const bookmarkSort = document.getElementById("bookmarkSort");
if (folderCard) {
const nextSibling = folderCard.nextElementSibling;
if (nextSibling) {
// Remove and reinsert the element after the next sibling
bookmarkSort.removeChild(folderCard);
bookmarkSort.insertBefore(folderCard, nextSibling.nextSibling);
masonryInstance.reloadItems();
masonryInstance.layout();
updateDashLinkOrder();
}
}
}
// Add event listeners to the raise buttons
raiseButtons.forEach(button => {
button.addEventListener("click", moveUp);
});
// Add event listeners to the lower buttons
lowerButtons.forEach(button => {
button.addEventListener("click", moveDown);
});
// after hiding/showing elements
masonryInstance.layout();
}); });
// Function to handle showing or hiding elements based on the checkbox state // Function to handle showing or hiding elements based on the checkbox state
@ -54,141 +146,70 @@ function toggleVisibility(switchId, className) {
switchElement.addEventListener('change', () => { switchElement.addEventListener('change', () => {
if (switchElement.checked) { if (switchElement.checked) {
elementsToToggle.forEach(element => { elementsToToggle.forEach(element => {
element.style.display = ''; // Show the element (default display) element.style.display = '';
}); });
} else { } else {
elementsToToggle.forEach(element => { elementsToToggle.forEach(element => {
element.style.display = 'none'; // Hide the element element.style.display = 'none';
}); });
} }
if (typeof masonryInstance !== 'undefined') {
masonryInstance.reloadItems();
masonryInstance.layout()
return;
}
}); });
// Trigger the toggle initially to set the correct visibility on page load // Trigger the toggle initially to set the correct visibility on page load
switchElement.dispatchEvent(new Event('change')); switchElement.dispatchEvent(new Event('change'));
} }
document.addEventListener('DOMContentLoaded', function () {
const bookmarkSort = document.getElementById('bookmarkSort');
if ( ! bookmarkSort ) {
return;
}
let draggingElement = null;
let placeholder = null;
let initialX = 0;
let initialY = 0;
let offsetX = 0;
let offsetY = 0;
// Function to handle the drag start function updateDashLinkOrder() {
const handleDragStart = (e, element) => { const bookmarkCards = document.querySelectorAll("#bookmarkSort .bookmark-card");
draggingElement = element; const ids = Array.from( bookmarkCards ).map( card => {
const match = card.id.match(/folderCard(\d+)/);
return match ? match[1] : null;
}).filter( id => id !== null );
// Create a placeholder to maintain layout const dashLinkOrder = document.getElementById("dashLinkOrder");
placeholder = document.createElement('div'); if (dashLinkOrder) {
placeholder.style.height = `${draggingElement.offsetHeight}px`; dashLinkOrder.value = ids.join(",");
placeholder.style.marginBottom = getComputedStyle(draggingElement).marginBottom;
placeholder.classList.add('placeholder');
draggingElement.parentNode.insertBefore(placeholder, draggingElement);
const rect = element.getBoundingClientRect();
initialX = e.clientX;
initialY = e.clientY;
offsetX = e.clientX - rect.left;
offsetY = e.clientY - rect.top;
// Prevent the element from moving in the normal flow
element.classList.add('dragging');
element.style.position = 'absolute';
draggingElement.style.width = `${rect.width}px`; // Preserve width
draggingElement.style.height = `${rect.height}px`; // Preserve height
element.style.zIndex = 1000; // Bring the element on top of others
element.style.left = `${rect.left}px`;
element.style.top = `${rect.top}px`;
e.preventDefault();
};
// Function to handle dragging movement
const handleDragMove = (e) => {
if (draggingElement) {
const dx = e.clientX - initialX + offsetX;
const dy = e.clientY - initialY + offsetY;
draggingElement.style.left = `${dx}px`;
draggingElement.style.top = `${dy}px`;
}
};
// Function to handle the drag end
const handleDragEnd = () => {
if (draggingElement) {
const rect = draggingElement.getBoundingClientRect();
// Reset the position styles of the dragged element
draggingElement.style.position = '';
draggingElement.style.zIndex = ''; // Reset z-index
draggingElement.style.left = '';
draggingElement.style.top = '';
draggingElement.classList.remove('dragging');
// Re-insert the element back into the DOM properly
const closestElement = getClosestElement(rect.left, rect.top);
console.error(closestElement.id);
if (closestElement) {
bookmarkSort.insertBefore(draggingElement, closestElement);
console.log( 'insertBefore' );
} else { } else {
bookmarkSort.appendChild(draggingElement); localStorage.setItem('manageFolderOrder', ids.join(","));
console.log( 'append' ); }
} }
// Reorder the elements after the drag ends function loadDashLinkOrder() {
reorderElements(); const storedOrder = localStorage.getItem("manageFolderOrder"); // Get the saved order
draggingElement = null; const bookmarkSort = document.getElementById("bookmarkSort");
}
};
// Function to reorder the elements inside the container if (!storedOrder || !bookmarkSort) return; // Exit if no saved order or no container
const reorderElements = () => {
const elements = Array.from(bookmarkSort.children);
const sortedIds = elements.map(element => element.id);
console.log('New order:', sortedIds);
};
// Function to handle the drop event const orderArray = storedOrder.split(","); // Convert the saved order into an array
const handleDrop = (e) => { const bookmarkCards = Array.from(document.querySelectorAll("#bookmarkSort .bookmark-card"));
e.preventDefault();
};
// Helper function to find the closest element based on mouse position // Create a map for quick lookup of cards by their ID
const getClosestElement = (x, y) => { const cardMap = new Map();
const elements = Array.from(bookmarkSort.children).filter( bookmarkCards.forEach(card => {
(el) => el !== placeholder && el !== draggingElement const match = card.id.match(/folderCard(\d+)/);
); if (match) cardMap.set(match[1], card);
});
let closest = null; // Reorder elements based on the saved order
let closestDistance = Number.POSITIVE_INFINITY; const orderedElements = [];
orderArray.forEach(id => {
elements.forEach((child) => { if (cardMap.has(id)) {
const rect = child.getBoundingClientRect(); orderedElements.push(cardMap.get(id)); // Add elements in the specified order
const distance = Math.abs(rect.top - y); cardMap.delete(id); // Remove from the map once processed
if (distance < closestDistance) {
closestDistance = distance;
closest = child;
} }
}); });
return closest; // Add any remaining (unspecified) elements to the end of the list
}; const remainingElements = Array.from(cardMap.values());
orderedElements.push(...remainingElements);
// Attach event listeners to all .card-header elements for dragging // Clear the container and append the elements in the new order
Array.from(document.querySelectorAll('.mover-arrow')).forEach(cardHeader => { bookmarkSort.innerHTML = ""; // Remove all children
cardHeader.addEventListener('mousedown', (e) => handleDragStart(e, cardHeader.closest('.bookmark-card'))); orderedElements.forEach(element => bookmarkSort.appendChild(element)); // Append in order
}); }
// Handle the dragging process
document.addEventListener('mousemove', handleDragMove);
document.addEventListener('mouseup', handleDragEnd);
bookmarkSort.addEventListener('dragover', handleDrop); // Listen for the drop event to update the order
});

View File

@ -41,7 +41,7 @@ class BookmarkDashboards extends DatabaseModel {
public function create( $title, $saved_prefs, $link_order, $description = '' ) { public function create( $title, $saved_prefs, $link_order, $description = '' ) {
if ( ! Check::dataTitle( $title ) ) { if ( ! Check::dataTitle( $title ) ) {
Debug::info( 'Views: illegal title.' ); Debug::info( 'Dash: illegal title.' );
return false; return false;
} }
$fields = [ $fields = [
@ -54,8 +54,8 @@ class BookmarkDashboards extends DatabaseModel {
'createdAt' => time(), 'createdAt' => time(),
]; ];
if ( ! self::$db->insert( $this->tableName, $fields ) ) { if ( ! self::$db->insert( $this->tableName, $fields ) ) {
new CustomException( 'viewCreate' ); new CustomException( 'dashCreate' );
Debug::error( "Views: not created " . var_export($fields,true) ); Debug::error( "Dash: not created " . var_export($fields,true) );
return false; return false;
} }
return self::$db->lastId(); return self::$db->lastId();
@ -63,11 +63,11 @@ class BookmarkDashboards extends DatabaseModel {
public function update( $id, $title, $saved_prefs, $link_order, $description = '' ) { public function update( $id, $title, $saved_prefs, $link_order, $description = '' ) {
if ( !Check::id( $id ) ) { if ( !Check::id( $id ) ) {
Debug::info( 'Views: illegal ID.' ); Debug::info( 'Dash: illegal ID.' );
return false; return false;
} }
if ( !Check::dataTitle( $title ) ) { if ( !Check::dataTitle( $title ) ) {
Debug::info( 'Views: illegal title.' ); Debug::info( 'Dash: illegal title.' );
return false; return false;
} }
$fields = [ $fields = [
@ -77,8 +77,29 @@ class BookmarkDashboards extends DatabaseModel {
'link_order' => $link_order, 'link_order' => $link_order,
]; ];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) { if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'viewUpdate' ); new CustomException( 'dashUpdate' );
Debug::error( "Views: $id not updated" ); Debug::error( "Dash: $id not updated" );
return false;
}
return true;
}
public function updateDash( $id, $saved_prefs = '', $link_order = '' ) {
if ( !Check::id( $id ) ) {
Debug::info( 'Dash: illegal ID.' );
return false;
}
$fields = [];
$fields['saved_prefs'] = $saved_prefs;
if ( ! empty( $link_order ) ) {
$fields['link_order'] = $link_order;
}
if ( empty( $fields ) ) {
return true;
}
if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'dashUpdate' );
Debug::error( "Dash: $id not updated" );
return false; return false;
} }
return true; return true;

View File

@ -146,15 +146,16 @@ class Bookmarks extends DatabaseModel {
return $this->filter( $bookmarks->results() ); return $this->filter( $bookmarks->results() );
} }
public function byFolder( $id, $limit = null ) { public function byFolder( $id, $hiddenExcluded = false, $archivedExcluded = false ) {
$whereClause = ['createdBy', '=', App::$activeUser->ID, 'AND']; $whereClause = ['createdBy', '=', App::$activeUser->ID ];
$whereClause = array_merge( $whereClause, [ 'AND', 'folderID', '=', $id ] );
$whereClause = array_merge( $whereClause, [ 'folderID', '=', $id ] ); if ( ! empty( $hiddenExcluded ) ) {
if ( empty( $limit ) ) { $whereClause = array_merge( $whereClause, [ 'AND', 'hiddenAt', 'is', 'NULL'] );
$bookmarks = self::$db->get( $this->tableName, $whereClause );
} else {
$bookmarks = self::$db->get( $this->tableName, $whereClause, 'ID', 'DESC', [0, $limit] );
} }
if ( ! empty( $archivedExcluded ) ) {
$whereClause = array_merge( $whereClause, [ 'AND', 'archivedAt', 'is', 'NULL'] );
}
$bookmarks = self::$db->get( $this->tableName, $whereClause );
if ( ! $bookmarks->count() ) { if ( ! $bookmarks->count() ) {
Debug::info( 'No Bookmarks found.' ); Debug::info( 'No Bookmarks found.' );
return false; return false;
@ -343,17 +344,18 @@ class Bookmarks extends DatabaseModel {
$instance = $data; $instance = $data;
$end = true; $end = true;
} }
$base_url = $this->getBaseUrl( $instance->url );
if ( empty( $instance->icon ) ) {
$instance->iconHtml = '<i class="fa fa-fw fa-link"></i>'; $instance->iconHtml = '<i class="fa fa-fw fa-link"></i>';
} else { if ( ! empty( $instance->icon ) ) {
if ( strpos($instance->icon, 'http') !== false) { if ( strpos($instance->icon, 'http') !== false) {
$instance->iconHtml = '<img src="' . $instance->icon .'">'; $instance->iconHtml = '<img src="' . $instance->icon .'">';
} else { } else {
if ( ! empty( $instance->url ) ) {
$base_url = $this->getBaseUrl( $instance->url );
$instance->iconHtml = '<img src="' . $base_url . ltrim( $instance->icon, '/' ) .'">'; $instance->iconHtml = '<img src="' . $base_url . ltrim( $instance->icon, '/' ) .'">';
} }
} }
}
if ( $instance->privacy == 'private' ) { if ( $instance->privacy == 'private' ) {
$instance->privacyBadge = '<span class="mx-2 translate-center badge bg-success rounded-pill">Private</span>'; $instance->privacyBadge = '<span class="mx-2 translate-center badge bg-success rounded-pill">Private</span>';
@ -439,7 +441,7 @@ class Bookmarks extends DatabaseModel {
return false; return false;
} }
$fields = [ $fields = [
'hiddenAt' => 0, 'hiddenAt' => NULL,
]; ];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) { if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'bookmarkUpdate' ); new CustomException( 'bookmarkUpdate' );
@ -503,7 +505,7 @@ class Bookmarks extends DatabaseModel {
return false; return false;
} }
$fields = [ $fields = [
'archivedAt' => 0, 'archivedAt' => NULL,
]; ];
if ( !self::$db->update( $this->tableName, $id, $fields ) ) { if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'bookmarkUpdate' ); new CustomException( 'bookmarkUpdate' );
@ -779,7 +781,7 @@ class Bookmarks extends DatabaseModel {
} }
} }
function isValidImageUrl($url) { public function isValidImageUrl($url) {
$headers = @get_headers($url); $headers = @get_headers($url);
if ($headers && strpos($headers[0], '200') !== false) { if ($headers && strpos($headers[0], '200') !== false) {

View File

@ -62,37 +62,37 @@ class Bookmarks extends Plugin {
'default' => 'false', 'default' => 'false',
], ],
'showArchivedSwitch' => [ 'showArchivedSwitch' => [
'pretty' => 'Bookmarks default setting for showing archived bookmarks', 'pretty' => 'Show archived bookmarks by default',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'false', 'default' => 'false',
], ],
'showHiddenSwitch' => [ 'showHiddenSwitch' => [
'pretty' => 'Bookmarks default setting for showing hidden bookmarks', 'pretty' => 'Show hidden bookmarks by default',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'false', 'default' => 'false',
], ],
'archiveButtonSwitch' => [ 'archiveButtonSwitch' => [
'pretty' => 'Bookmarks default setting for showing the archive buttons', 'pretty' => 'Show the archive buttons by default',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'false', 'default' => 'false',
], ],
'visibilityButtonSwitch' => [ 'visibilityButtonSwitch' => [
'pretty' => 'Bookmarks default setting for showing the visibility buttons', 'pretty' => 'Show the visibility buttons by default',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'false', 'default' => 'false',
], ],
'privacyButtonSwitch' => [ 'privacyButtonSwitch' => [
'pretty' => 'Bookmarks default setting for showing the privacy buttons', 'pretty' => 'Show the privacy buttons by default',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'true', 'default' => 'true',
], ],
'shareButtonSwitch' => [ 'shareButtonSwitch' => [
'pretty' => 'Bookmarks default setting for showing the share buttons', 'pretty' => 'Show the share buttons by default',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'true', 'default' => 'true',
], ],
'addButtonSwitch' => [ 'addButtonSwitch' => [
'pretty' => 'Bookmarks default setting for showing the add buttons', 'pretty' => 'Show the add buttons by default',
'type' => 'checkbox', 'type' => 'checkbox',
'default' => 'true', 'default' => 'true',
], ],

View File

@ -48,7 +48,7 @@
<!-- Submit Button --> <!-- Submit Button -->
<div class="text-center"> <div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Create</button> <button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Create</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>

View File

@ -48,7 +48,7 @@
<!-- Submit Button --> <!-- Submit Button -->
<div class="text-center"> <div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Save</button> <button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Save</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>

View File

@ -19,7 +19,7 @@
<td style="text-align: center;"> <td style="text-align: center;">
{privacy} {privacy}
</td> </td>
<td><a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="btn btn-sm btn-outline-primary"><i class="fa fa-fw fa-upload"></i></a></td> <td><a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-outline-warning"><i class="fa fa-fw fa-pencil"></i></a></td> <td><a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-outline-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-outline-danger"><i class="fa fa-fw fa-trash"></i></a></td> <td><a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-outline-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr> </tr>
@ -34,5 +34,5 @@
</tbody> </tbody>
</table> </table>
<div class="text-center"> <div class="text-center">
<a href="{ROOT_URL}bookmarks/createBookmark" class="btn btn-md btn-primary">Create</a> <a href="{ROOT_URL}bookmarks/createBookmark" class="btn btn-md atb-green-bg">Create</a>
</div> </div>

View File

@ -23,7 +23,7 @@
<td style="text-align: center;"> <td style="text-align: center;">
{privacy} {privacy}
</td> </td>
<td><a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="btn btn-sm btn-outline-primary atb-green-outline"><i class="fa fa-fw fa-upload"></i></a></td> <td><a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-outline-warning"><i class="fa fa-fw fa-pencil"></i></a></td> <td><a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-outline-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-outline-danger"><i class="fa fa-fw fa-trash"></i></a></td> <td><a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-outline-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr> </tr>
@ -38,7 +38,7 @@
</tbody> </tbody>
</table> </table>
<div class="text-center"> <div class="text-center">
<a href="{ROOT_URL}bookmarks/createBookmark" class="btn btn-lg btn-primary atb-green-bg">Add</a> <a href="{ROOT_URL}bookmarks/createBookmark" class="btn btn-lg atb-green-bg">Add</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,9 +3,12 @@
<div class="card m-3 accordion"> <div class="card m-3 accordion">
<div class="accordion-item"> <div class="accordion-item">
<div class="card-header accordion-header text-center bg-{color} context-main"> <div class="card-header accordion-header text-center bg-{color} context-main">
<span class="h4 float-left mover-arrow"><i class="fa-solid fa-arrows-up-down-left-right"></i></span> <span class="float-start">
<button class="btn btn-sm pe-3 folder-raise context-main" type="button"><i class="fa-solid fa-arrow-up"></i></button>
<button class="btn btn-sm ps-3 folder-lower context-main" type="button"><i class="fa-solid fa-arrow-down"></i></button>
</span>
<span class="h4 text-center mx-5" data-bs-target="#Collapse{ID}" data-bs-toggle="collapse" aria-expanded="true" aria-controls="Collapse{ID}">{title}</span> <span class="h4 text-center mx-5" data-bs-target="#Collapse{ID}" data-bs-toggle="collapse" aria-expanded="true" aria-controls="Collapse{ID}">{title}</span>
<a class="btn btn-sm btn-primary btn-rounded float-end btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}"> <a class="btn btn-sm atb-green-bg btn-rounded float-end btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i> <i class="fa fa-fw fa-share"></i>
</a> </a>
</div> </div>
@ -35,8 +38,8 @@
<div class="card-footer d-flex justify-content-center align-items-center context-main-bg"> <div class="card-footer d-flex justify-content-center align-items-center context-main-bg">
<a href="{ROOT_URL}bookmarks/createBookmark/{ID}" class="btn btn-sm btn-success btn-addlink"><i class="fa fa-fw fa-plus"></i></a></td> <a href="{ROOT_URL}bookmarks/createBookmark/{ID}" class="btn btn-sm btn-success btn-addlink"><i class="fa fa-fw fa-plus"></i></a></td>
<span class="ms-auto"> <span class="ms-auto">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm btn-outline-primary"><i class="fa fa-fw fa-list"></i></a> <a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-list"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm btn-outline-primary"><i class="fa fa-fw fa-info-circle"></i></a></td> <a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-info-circle"></i></a></td>
<a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-sm btn-warning edit-mode"><i class="fa fa-fw fa-pencil"></i></a></td> <a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-sm btn-warning edit-mode"><i class="fa fa-fw fa-pencil"></i></a></td>
<a href="{ROOT_URL}bookmarks/deleteFolder/{ID}" class="btn btn-sm btn-danger edit-mode"><i class="fa fa-fw fa-trash"></i></a></td> <a href="{ROOT_URL}bookmarks/deleteFolder/{ID}" class="btn btn-sm btn-danger edit-mode"><i class="fa fa-fw fa-trash"></i></a></td>
</span> </span>

View File

@ -1,14 +1,14 @@
{LOOP} {LOOP}
<li class="list-group-item context-main-bg bg-{color} {hidden_class} {archived_class} mb-1"> <li class="list-group-item context-main-bg bg-{color} {hidden_class} {archived_class} mb-1">
<a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="context-main">{iconHtml}</a> <a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="context-main">{iconHtml}</a>
<a href="{url}"> {title}</a> <a href="{url}" class="text-decoration-none atb-green"> {title}</a>
<span class="float-end"> <span class="float-end">
<span class="btn-hideshow">{hideBtn}</span> <span class="btn-hideshow">{hideBtn}</span>
<span class="btn-archive">{archiveBtn}</span> <span class="btn-archive">{archiveBtn}</span>
<span class="btn-publish">{publish}</span> <span class="btn-publish">{publish}</span>
<a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-warning edit-mode"><i class="fa fa-fw fa-pencil"></i></a> <a href="{ROOT_URL}bookmarks/editBookmark/{ID}" class="btn btn-sm btn-warning edit-mode"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-danger edit-mode"><i class="fa fa-fw fa-trash"></i></a> <a href="{ROOT_URL}bookmarks/deleteBookmark/{ID}" class="btn btn-sm btn-danger edit-mode"><i class="fa fa-fw fa-trash"></i></a>
<a class="btn btn-sm btn-primary btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}"> <a class="btn btn-sm atb-green-bg btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i> <i class="fa fa-fw fa-share"></i>
</a> </a>
<div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true"> <div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true">

View File

@ -3,7 +3,7 @@
<a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="context-main">{iconHtml}</a> <a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="context-main">{iconHtml}</a>
<a href="{url}"> {title}</a> <a href="{url}"> {title}</a>
<span class="float-end"> <span class="float-end">
<a class="btn btn-sm btn-primary" data-bs-toggle="modal" data-bs-target="#linkShare{ID}"> <a class="btn btn-sm atb-green-bg" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i> <i class="fa fa-fw fa-share"></i>
</a> </a>
<div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true"> <div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true">

View File

@ -9,7 +9,7 @@
<span class="text-center px-5">{privacyBadge}</span> <span class="text-center px-5">{privacyBadge}</span>
<span class="h4 text-center px-5">{title}</span> <span class="h4 text-center px-5">{title}</span>
</span> </span>
<a class="btn btn-sm btn-primary btn-rounded btn-share atb-green-bg" data-bs-toggle="modal" data-bs-target="#linkShare{ID}"> <a class="btn btn-sm btn-rounded btn-share atb-green-bg" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i> <i class="fa fa-fw fa-share"></i>
</a> </a>
</div> </div>
@ -39,8 +39,8 @@
</div> </div>
<div class="card-footer d-flex justify-content-center align-items-center context-main-bg"> <div class="card-footer d-flex justify-content-center align-items-center context-main-bg">
<span class="ms-auto"> <span class="ms-auto">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm btn-outline-primary atb-green-outline"><i class="fa fa-fw fa-list"></i></a> <a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-list"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm btn-outline-primary atb-green-outline"><i class="fa fa-fw fa-info-circle"></i></a></td> <a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-info-circle"></i></a></td>
</span> </span>
</div> </div>
</div> </div>

View File

@ -4,7 +4,7 @@
<a href="{url}"> {title}</a>{privacyBadge} <a href="{url}"> {title}</a>{privacyBadge}
<span class="float-end"> <span class="float-end">
{publish} {publish}
<a class="btn btn-sm btn-primary btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}"> <a class="btn btn-sm atb-green-bg btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i> <i class="fa fa-fw fa-share"></i>
</a> </a>
<div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true"> <div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true">

View File

@ -3,7 +3,7 @@
<a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="context-main">{iconHtml}</a> <a href="{ROOT_URL}bookmarks/bookmark/{ID}" class="context-main">{iconHtml}</a>
<a href="{url}"> {title}</a> <a href="{url}"> {title}</a>
<span class="float-end"> <span class="float-end">
<a class="btn btn-sm btn-primary btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}"> <a class="btn btn-sm atb-green-bg btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i> <i class="fa fa-fw fa-share"></i>
</a> </a>
<div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true"> <div class="modal fade" id="linkShare{ID}" tabindex="-1" style="display: none;" aria-hidden="true">

View File

@ -35,7 +35,7 @@
<!-- Submit Button --> <!-- Submit Button -->
<div class="text-center"> <div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Create</button> <button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Create</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>

View File

@ -25,5 +25,3 @@
</div> </div>
</div> </div>
</div> </div>

View File

@ -35,7 +35,7 @@
<!-- Submit Button --> <!-- Submit Button -->
<div class="text-center"> <div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Save</button> <button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Save</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>

View File

@ -3,9 +3,12 @@
<div class="card m-3 accordion"> <div class="card m-3 accordion">
<div class="accordion-item"> <div class="accordion-item">
<div class="card-header accordion-header text-center bg-{color} context-main"> <div class="card-header accordion-header text-center bg-{color} context-main">
<span class="h4 float-left mover-arrow"><i class="fa-solid fa-arrows-up-down-left-right"></i></span> <span class="float-start">
<button class="btn btn-sm pe-3 folder-raise atb-green" type="button"><i class="fa-solid fa-arrow-up"></i></button>
<button class="btn btn-sm ps-3 folder-lower atb-green" type="button"><i class="fa-solid fa-arrow-down"></i></button>
</span>
<span class="h4 text-center mx-5" data-bs-target="#Collapse{ID}" data-bs-toggle="collapse" aria-expanded="true" aria-controls="Collapse{ID}">{title}</span> <span class="h4 text-center mx-5" data-bs-target="#Collapse{ID}" data-bs-toggle="collapse" aria-expanded="true" aria-controls="Collapse{ID}">{title}</span>
<a class="btn btn-sm btn-primary btn-rounded float-end btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}"> <a class="btn btn-sm atb-green-bg btn-rounded float-end btn-share" data-bs-toggle="modal" data-bs-target="#linkShare{ID}">
<i class="fa fa-fw fa-share"></i> <i class="fa fa-fw fa-share"></i>
</a> </a>
</div> </div>
@ -35,8 +38,8 @@
<div class="card-footer d-flex justify-content-center align-items-center context-main-bg"> <div class="card-footer d-flex justify-content-center align-items-center context-main-bg">
<a href="{ROOT_URL}bookmarks/createBookmark/{ID}" class="btn btn-sm btn-success btn-addlink"><i class="fa fa-fw fa-plus"></i></a></td> <a href="{ROOT_URL}bookmarks/createBookmark/{ID}" class="btn btn-sm btn-success btn-addlink"><i class="fa fa-fw fa-plus"></i></a></td>
<span class="ms-auto"> <span class="ms-auto">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm btn-outline-primary"><i class="fa fa-fw fa-list"></i></a> <a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-list"></i></a>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm btn-outline-primary"><i class="fa fa-fw fa-info-circle"></i></a></td> <a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm atb-green-outline"><i class="fa fa-fw fa-info-circle"></i></a></td>
</span> </span>
</div> </div>
</div> </div>

View File

@ -6,7 +6,7 @@
<p>From here you can easily create, update, or remove any bookmark dashboards you have created.</p> <p>From here you can easily create, update, or remove any bookmark dashboards you have created.</p>
<p>Dashboards are a feature that allow you to build customized bookmark pages that you can easily save and open la</p> <p>Dashboards are a feature that allow you to build customized bookmark pages that you can easily save and open la</p>
</div> </div>
<div class="row g-3 text-center p-2" data-masonry='{ "percentPosition": false }' id="bookmarkSort"> <div class="row g-3 text-center p-2">
<table class="table context-main"> <table class="table context-main">
<thead> <thead>
<tr> <tr>
@ -22,7 +22,7 @@
<tr> <tr>
<td class="">{title}</td> <td class="">{title}</td>
<td>{description}</td> <td>{description}</td>
<td><a href="{ROOT_URL}bookmarks/dashboard/{uuid}" class="btn btn-sm btn-primary atb-green-bg"><i class="fa fa-fw fa-upload"></i></a></td> <td><a href="{ROOT_URL}bookmarks/dashboard/{uuid}" class="btn btn-sm atb-green-bg"><i class="fa fa-fw fa-upload"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/editDash/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td> <td><a href="{ROOT_URL}bookmarks/editDash/{ID}" class="btn btn-sm btn-warning"><i class="fa fa-fw fa-pencil"></i></a></td>
<td><a href="{ROOT_URL}bookmarks/deleteDash/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td> <td><a href="{ROOT_URL}bookmarks/deleteDash/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr> </tr>
@ -37,7 +37,7 @@
</tbody> </tbody>
</table> </table>
<div class=""> <div class="">
<a href="{ROOT_URL}bookmarks/addDash" class="btn btn-lg btn-primary atb-green-bg">Create</a> <a href="{ROOT_URL}bookmarks/addDash" class="btn btn-lg atb-green-bg">Create</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -4,7 +4,9 @@
<hr> <hr>
<form method="post"> <form method="post">
<fieldset> <fieldset>
<div class="row g-3" data-masonry='{ "percentPosition": false }' id="bookmarkSort"> <input type="hidden" name="token" value="{TOKEN}">
<input type="hidden" name="link_order" value="{link_order}" id="dashLinkOrder">
<div class="row g-3" id="bookmarkSort">
{folderPanels} {folderPanels}
</div> </div>
<hr> <hr>
@ -15,7 +17,7 @@
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="h-100 d-flex align-items-center"> <div class="h-100 d-flex align-items-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg mx-5">Save</button> <button type="submit" name="submit" value="submit" class="btn btn-lg mx-5 atb-green-bg">Save</button>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,9 +2,9 @@
<div class="offset-2 col-8 p-3 context-main-bg"> <div class="offset-2 col-8 p-3 context-main-bg">
<legend class="text-center">Bookmark Export</legend> <legend class="text-center">Bookmark Export</legend>
<hr> <hr>
<h3 class="text-center text-muted">Select which folders to include in the export</h3> <h3 class="text-center">Select which folders to include in the export</h3>
<form action="" method="post"> <form action="" method="post">
<div class="row g-3 col-4 offset-4" data-masonry='{ "percentPosition": false }' id="bookmarkSort"> <div class="row g-3 col-4 offset-4">
<table class="table context-main"> <table class="table context-main">
<thead> <thead>
<tr> <tr>
@ -39,9 +39,28 @@
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="mb-3 row">
<label for="title" class="col-lg-3 col-form-label text-end align-items-center">Bookmark Filters:</label>
<div class="col-lg-6">
<div class="p-2">
<!-- Status Filters -->
<div class="d-flex align-items-center border p-2 rounded mb-3 context-main-bg">
<div class="me-3 fw-bold">Status:</div>
<div class="form-check form-switch me-3">
<input class="form-check-input" type="checkbox" value="true" id="archivedIncluded" name="archivedIncluded">
<label class="form-check-label" for="archivedIncluded">Include Archived</label>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" value="true" id="hiddenIncluded" name="hiddenIncluded">
<label class="form-check-label" for="hiddenIncluded">Include Hidden</label>
</div>
</div>
</div>
</div>
</div>
<div class="text-center"> <div class="text-center">
<p>Literally every browser is chromium based now, so they all have a standard import/export.</p> <p>Literally every browser is chromium based now, so they all have a standard import/export.</p>
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg atb-green-bg">Create Export</button> <button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Create Export</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -55,7 +55,7 @@
<!-- Call to Action --> <!-- Call to Action -->
<div class="text-center mt-5"> <div class="text-center mt-5">
<a href="/brave" class="btn btn-primary atb-green-bg btn-lg"> <a href="/brave" class="btn atb-green-bg btn-lg">
<i class="fas fa-download me-2"></i>Get the Addon for Brave <i class="fas fa-download me-2"></i>Get the Addon for Brave
</a> </a>
</div> </div>

View File

@ -55,7 +55,7 @@
<!-- Call to Action --> <!-- Call to Action -->
<div class="text-center mt-5"> <div class="text-center mt-5">
<a href="/chrome" class="btn btn-primary atb-green-bg btn-lg"> <a href="/chrome" class="btn atb-green-bg btn-lg">
<i class="fas fa-download me-2"></i>Get the Addon for Chrome <i class="fas fa-download me-2"></i>Get the Addon for Chrome
</a> </a>
</div> </div>

View File

@ -55,7 +55,7 @@
<!-- Call to Action --> <!-- Call to Action -->
<div class="text-center mt-5"> <div class="text-center mt-5">
<a href="#" class="btn btn-primary atb-green-bg btn-lg"> <a href="#" class="btn atb-green-bg btn-lg">
<i class="fas fa-download me-2"></i>Get the Addon for Edge <i class="fas fa-download me-2"></i>Get the Addon for Edge
</a> </a>
</div> </div>

View File

@ -55,7 +55,7 @@
<!-- Call to Action --> <!-- Call to Action -->
<div class="text-center mt-5"> <div class="text-center mt-5">
<a href="/firefox" class="btn btn-primary atb-green-bg btn-lg"> <a href="/firefox" class="btn atb-green-bg btn-lg">
<i class="fas fa-download me-2"></i>Get the Addon for Firefox <i class="fas fa-download me-2"></i>Get the Addon for Firefox
</a> </a>
</div> </div>

View File

@ -14,7 +14,7 @@
<i class="fab fa-chrome fa-3x text-primary mb-3"></i> <i class="fab fa-chrome fa-3x text-primary mb-3"></i>
<h5 class="card-title">Chrome Extension</h5> <h5 class="card-title">Chrome Extension</h5>
<p class="card-text">Enhance your Chrome browser with advanced features and effortless organization.</p> <p class="card-text">Enhance your Chrome browser with advanced features and effortless organization.</p>
<a href="/extensions/chrome" class="btn btn-primary atb-green-bg btn-sm">Learn More</a> <a href="/extensions/chrome" class="btn atb-green-bg btn-sm">Learn More</a>
</div> </div>
</div> </div>
</div> </div>
@ -26,7 +26,7 @@
<i class="fab fa-firefox fa-3x text-warning mb-3"></i> <i class="fab fa-firefox fa-3x text-warning mb-3"></i>
<h5 class="card-title">Firefox Extension</h5> <h5 class="card-title">Firefox Extension</h5>
<p class="card-text">Seamlessly integrate with Firefox for a smoother browsing experience.</p> <p class="card-text">Seamlessly integrate with Firefox for a smoother browsing experience.</p>
<a href="/extensions/firefox" class="btn btn-primary atb-green-bg btn-sm">Learn More</a> <a href="/extensions/firefox" class="btn atb-green-bg btn-sm">Learn More</a>
</div> </div>
</div> </div>
</div> </div>
@ -38,7 +38,7 @@
<i class="fab fa-opera fa-3x text-danger mb-3"></i> <i class="fab fa-opera fa-3x text-danger mb-3"></i>
<h5 class="card-title">Opera Extension</h5> <h5 class="card-title">Opera Extension</h5>
<p class="card-text">Boost your Opera browser with intuitive features and tools.</p> <p class="card-text">Boost your Opera browser with intuitive features and tools.</p>
<a href="/extensions/opera" class="btn btn-primary atb-green-bg btn-sm">Learn More</a> <a href="/extensions/opera" class="btn atb-green-bg btn-sm">Learn More</a>
</div> </div>
</div> </div>
</div> </div>
@ -50,7 +50,7 @@
<i class="fab fa-brave fa-3x text-orange mb-3"></i> <i class="fab fa-brave fa-3x text-orange mb-3"></i>
<h5 class="card-title">Brave Extension</h5> <h5 class="card-title">Brave Extension</h5>
<p class="card-text">Enjoy secure and private browsing with Brave, enhanced by our extension.</p> <p class="card-text">Enjoy secure and private browsing with Brave, enhanced by our extension.</p>
<a href="/extensions/brave" class="btn btn-primary atb-green-bg btn-sm">Learn More</a> <a href="/extensions/brave" class="btn atb-green-bg btn-sm">Learn More</a>
</div> </div>
</div> </div>
</div> </div>
@ -62,7 +62,7 @@
<i class="fab fa-edge fa-3x text-info mb-3"></i> <i class="fab fa-edge fa-3x text-info mb-3"></i>
<h5 class="card-title">Edge Extension</h5> <h5 class="card-title">Edge Extension</h5>
<p class="card-text">Maximize productivity on Microsoft Edge with our extension.</p> <p class="card-text">Maximize productivity on Microsoft Edge with our extension.</p>
<a href="/extensions/edge" class="btn btn-primary atb-green-bg btn-sm">Learn More</a> <a href="/extensions/edge" class="btn atb-green-bg btn-sm">Learn More</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -55,7 +55,7 @@
<!-- Call to Action --> <!-- Call to Action -->
<div class="text-center mt-5"> <div class="text-center mt-5">
<a href="#" class="btn btn-primary atb-green-bg btn-lg"> <a href="#" class="btn atb-green-bg btn-lg">
<i class="fas fa-download me-2"></i>Get the Addon for Opera <i class="fas fa-download me-2"></i>Get the Addon for Opera
</a> </a>
</div> </div>

View File

@ -40,7 +40,7 @@
<!-- Submit Button --> <!-- Submit Button -->
<div class="text-center"> <div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Create</button> <button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Create</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>

View File

@ -38,7 +38,7 @@
<!-- Submit Button --> <!-- Submit Button -->
<div class="text-center"> <div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Save</button> <button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Save</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>

View File

@ -14,7 +14,7 @@
<td>{prettyPrivacy}</td> <td>{prettyPrivacy}</td>
<td>{description}</td> <td>{description}</td>
<td> <td>
<a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm btn-outline-primary atb-green-outline mx-1"><i class="fa fa-fw fa-info-circle"></i></a> <a href="{ROOT_URL}bookmarks/folders/{ID}" class="btn btn-sm atb-green-outline mx-1"><i class="fa fa-fw fa-info-circle"></i></a>
<a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-sm btn-outline-warning mx-1"><i class="fa fa-fw fa-pencil"></i></a> <a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-sm btn-outline-warning mx-1"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{ROOT_URL}bookmarks/deleteFolder/{ID}" class="btn btn-sm btn-outline-danger mx-1"><i class="fa fa-fw fa-trash"></i></a> <a href="{ROOT_URL}bookmarks/deleteFolder/{ID}" class="btn btn-sm btn-outline-danger mx-1"><i class="fa fa-fw fa-trash"></i></a>
</td> </td>
@ -30,5 +30,5 @@
</tbody> </tbody>
</table> </table>
<div class="text-center"> <div class="text-center">
<a href="{ROOT_URL}bookmarks/createFolder" class="btn btn-md btn-primary">Create</a> <a href="{ROOT_URL}bookmarks/createFolder" class="btn btn-md atb-green-bg">Create</a>
</div> </div>

View File

@ -45,7 +45,7 @@
<!-- Admin Controls --> <!-- Admin Controls -->
<div class="card-footer text-center"> <div class="card-footer text-center">
<a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn btn-primary atb-green-bg btn-sm me-2" data-bs-toggle="tooltip" title="Broadcast Message"> <a href="{ROOT_URL}bookmarks/bookmarks/{ID}" class="btn atb-green-bg btn-sm me-2" data-bs-toggle="tooltip" title="Broadcast Message">
<i class="fa fa-list"></i> Bookmarks <i class="fa fa-list"></i> Bookmarks
</a> </a>
<a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-warning btn-sm me-2" data-bs-toggle="tooltip" title="Edit User"> <a href="{ROOT_URL}bookmarks/editFolder/{ID}" class="btn btn-warning btn-sm me-2" data-bs-toggle="tooltip" title="Edit User">

View File

@ -8,7 +8,7 @@
<input type="file" name="bookmark_file" id="bookmark_file" accept=".html"> <input type="file" name="bookmark_file" id="bookmark_file" accept=".html">
</div> </div>
<div class="text-center"> <div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg center-block atb-green-bg">Import</button> <button type="submit" name="submit" value="submit" class="btn btn-lg center-block atb-green-bg">Import</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -58,7 +58,7 @@
</fieldset> </fieldset>
</div> </div>
<div class="card-footer d-flex justify-content-center align-items-center context-main-bg"> <div class="card-footer d-flex justify-content-center align-items-center context-main-bg">
<button type="submit" name="submit" value="submit" class="btn btn-md btn-outline-primary my-2">Save as Default</button> <button type="submit" name="submit" value="submit" class="btn btn-md atb-green-outline my-2">Save as Default</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -19,6 +19,8 @@ use TheTempusProject\Classes\AdminController;
use TheTempusProject\Plugins\Members as MemberModel; use TheTempusProject\Plugins\Members as MemberModel;
use TheTempusProject\Houdini\Classes\Issues; use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\Bedrock\Functions\Input; use TheTempusProject\Bedrock\Functions\Input;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Hermes\Functions\Redirect;
class Members extends AdminController { class Members extends AdminController {
public function __construct() { public function __construct() {
@ -28,13 +30,46 @@ class Members extends AdminController {
} }
public function index( $data = null ) { public function index( $data = null ) {
self::$title = 'Admin - Membership Scripts';
Views::view( 'members.admin.scripts' );
}
public function orphans( $data = null, $id = null ) {
self::$title = 'Admin - Orphaned PRoducts';
if ( $data = 'abandon' && ! empty( $id ) ) {
MemberModel::orphanAbandon( $id );
Redirect::to('admin/members/orphans');
}
$orphans = MemberModel::findOrphans();
Views::view( 'members.admin.orphans', $orphans );
}
public function webhooks( $data = null, $id = null ) {
self::$title = 'Admin - Membership Webhooks'; self::$title = 'Admin - Membership Webhooks';
if ( $data = 'delete' && ! empty( $id ) ) {
MemberModel::webhookRemove( $id );
Redirect::to('admin/members/webhooks');
}
$data = [];
$webhooks = MemberModel::webhookList();
foreach ($webhooks->data as $key => $webhook) {
$hook = new \stdClass;
$hook->id = $webhook->id;
$hook->enabled_events = implode( ', <br>', $webhook->enabled_events );
$hook->status = $webhook->status;
$hook->url = $webhook->url;
$data[] = $hook;
}
Components::set( 'urltouse', Routes::getAddress() );
if ( !Input::exists( 'submit' ) ) { if ( !Input::exists( 'submit' ) ) {
return Views::view( 'members.admin.webhooks' ); return Views::view( 'members.admin.webhooks', $data );
} }
MemberModel::webhookSetup(); MemberModel::webhookSetup();
Issues::add( 'success', 'Webhooks Generated' ); Issues::add( 'success', 'Webhooks Generated' );
Issues::add( 'error', 'Now, LEAVE!' ); Issues::add( 'error', 'Now, LEAVE!' );
Redirect::to('admin/members/webhooks');
} }
} }

View File

@ -70,7 +70,7 @@ class Member extends Controller {
} }
try { try {
$session = self::$stripe->billingPortal->sessions->create([ $session = self::$stripe->billingPortal->sessions->create([
'customer' => $customer, 'customer' => $customer->stripe_customer,
'return_url' => Routes::getAddress() . 'member/manage', 'return_url' => Routes::getAddress() . 'member/manage',
]); ]);
} catch (\Stripe\Exception\InvalidRequestException $e) { } catch (\Stripe\Exception\InvalidRequestException $e) {
@ -174,7 +174,7 @@ class Member extends Controller {
Session::flash( 'success', 'We aren\'t currently accepting new members, please check back soon!' ); Session::flash( 'success', 'We aren\'t currently accepting new members, please check back soon!' );
return Redirect::home(); return Redirect::home();
} }
Views::view( 'members.landing1', $product ); Views::view( 'members.landing', $product );
} }
public function checkout( $plan = 'monthly' ) { public function checkout( $plan = 'monthly' ) {
@ -187,6 +187,19 @@ class Member extends Controller {
Issues::add( 'error', 'no customer' ); Issues::add( 'error', 'no customer' );
return $this->index(); return $this->index();
} }
$plan = strtolower( $plan );
if ( ! in_array( $plan, ['monthly','yearly','upgrade'] ) ) {
Session::flash( 'error', 'Unknown plan' );
return Redirect::to( 'home/index' );
}
if ( $plan === 'upgrade' ) {
$plan = 'yearly';
$successUrl = Routes::getAddress() . 'member/payment/upgrade?session_id={CHECKOUT_SESSION_ID}';
} else {
$successUrl = Routes::getAddress() . 'member/payment/complete?session_id={CHECKOUT_SESSION_ID}';
}
$stripePrice = $this->findPrice( $plan ); $stripePrice = $this->findPrice( $plan );
$session = self::$stripe->checkout->sessions->create([ $session = self::$stripe->checkout->sessions->create([
@ -197,7 +210,7 @@ class Member extends Controller {
'quantity' => 1, 'quantity' => 1,
]], ]],
'mode' => 'subscription', 'mode' => 'subscription',
'success_url' => Routes::getAddress() . 'member/payment/complete?session_id={CHECKOUT_SESSION_ID}', 'success_url' => $successUrl,
'cancel_url' => Routes::getAddress() . 'member/payment/cancel', 'cancel_url' => Routes::getAddress() . 'member/payment/cancel',
]); ]);
header('Location: ' . $session->url); header('Location: ' . $session->url);
@ -206,7 +219,7 @@ class Member extends Controller {
public function payment( $type = '' ) { public function payment( $type = '' ) {
$type = strtolower( $type ); $type = strtolower( $type );
if ( ! in_array( $type, ['cancel','complete'] ) ) { if ( ! in_array( $type, ['cancel','complete','upgrade'] ) ) {
Session::flash( 'error', 'Unknown Payment' ); Session::flash( 'error', 'Unknown Payment' );
return Redirect::to( 'home/index' ); return Redirect::to( 'home/index' );
} }
@ -216,6 +229,11 @@ class Member extends Controller {
return Views::view( 'members.paymentcanceled' ); return Views::view( 'members.paymentcanceled' );
} }
if ( $type == 'upgrade' ) {
self::$title = 'Better Members Area';
return Views::view( 'members.upgradeCompleted' );
}
self::$title = '(almost) Members Area'; self::$title = '(almost) Members Area';
Views::view( 'members.paymentcomplete' ); Views::view( 'members.paymentcomplete' );
} }

View File

@ -27,8 +27,8 @@ class MembershipProducts extends DatabaseModel {
public $databaseMatrix = [ public $databaseMatrix = [
[ 'name', 'varchar', '128' ], [ 'name', 'varchar', '128' ],
[ 'description', 'text', '' ], [ 'description', 'text', '' ],
[ 'monthly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4399.22 === 439922 [ 'monthly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4,399.22 === 439922
[ 'yearly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4399.22 === 439922 [ 'yearly_price', 'int', '10' ], // must be int value greater than 99 IE $1.00 === 100, $4,399.22 === 439922
[ 'stripe_product', 'varchar', '64' ], // not-required to create - generated by stripe after [ 'stripe_product', 'varchar', '64' ], // not-required to create - generated by stripe after
[ 'stripe_price_monthly', 'varchar', '64' ], // not-required to create - generated by stripe after [ 'stripe_price_monthly', 'varchar', '64' ], // not-required to create - generated by stripe after
[ 'stripe_price_yearly', 'varchar', '64' ], // not-required to create - generated by stripe after [ 'stripe_price_yearly', 'varchar', '64' ], // not-required to create - generated by stripe after
@ -166,7 +166,7 @@ class MembershipProducts extends DatabaseModel {
public function updateStripeYearlyPrice( $product, $yearly_price ) { public function updateStripeYearlyPrice( $product, $yearly_price ) {
$stripe->prices->update( $stripe->prices->update(
$yearly->id, $yearly_price,
['lookup_key' => ""] ['lookup_key' => ""]
); );
return $this->createStripeYearlyPrice( $product, $yearly_price ); return $this->createStripeYearlyPrice( $product, $yearly_price );
@ -174,7 +174,7 @@ class MembershipProducts extends DatabaseModel {
public function updateStripeMonthlyPrice( $product, $monthly_price ) { public function updateStripeMonthlyPrice( $product, $monthly_price ) {
$stripe->prices->update( $stripe->prices->update(
$monthly->id, $monthly_price,
['lookup_key' => ""] ['lookup_key' => ""]
); );
return $this->createStripeMonthlyPrice( $product, $monthly_price ); return $this->createStripeMonthlyPrice( $product, $monthly_price );
@ -208,7 +208,7 @@ class MembershipProducts extends DatabaseModel {
if ( ! $data->count() ) { if ( ! $data->count() ) {
return false; return false;
} }
return $data->first(); return $this->filter( $data->first() );
} }
public function mainProduct() { public function mainProduct() {

View File

@ -19,6 +19,7 @@ use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Hermes\Functions\Route as Routes; use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Models\Memberships; use TheTempusProject\Models\Memberships;
use TheTempusProject\Models\MembershipProducts as Products; use TheTempusProject\Models\MembershipProducts as Products;
use TheTempusProject\Canary\Bin\Canary as Debug;
class Members extends Plugin { class Members extends Plugin {
public static $stripe; public static $stripe;
@ -46,17 +47,17 @@ class Members extends Plugin {
'text' => '<i class="fa fa-fw fa-arrow-down"></i> Memberships', 'text' => '<i class="fa fa-fw fa-arrow-down"></i> Memberships',
'url' => [ 'url' => [
[ [
'text' => '<i class="fa fa-fw fa-database"></i> Products', 'text' => '<i class="fa fa-fw fa-barcode"></i> Products',
'url' => '{ROOT_URL}admin/products', 'url' => '{ROOT_URL}admin/products',
], ],
[ [
'text' => '<i class="fa fa-fw fa-database"></i> Subscriptions', 'text' => '<i class="fa fa-fw fa-bag-shopping"></i> Subscriptions',
'url' => '{ROOT_URL}admin/records', 'url' => '{ROOT_URL}admin/records',
], ],
// [ [
// 'text' => '<i class="fa fa-fw fa-database"></i> Invoices', 'text' => '<i class="fa fa-fw fa-code"></i> Scripts',
// 'url' => '{ROOT_URL}admin/invoices', 'url' => '{ROOT_URL}admin/members',
// ], ],
], ],
], ],
]; ];
@ -116,10 +117,7 @@ class Members extends Plugin {
]; ];
public function __construct( $load = false ) { public function __construct( $load = false ) {
if ( ! self::$loaded ) { if ( ! self::$loaded && $load ) {
App::$userCPlinks[] = (object) self::$userLinks;
self::$loaded = true;
}
if ( App::$isLoggedIn ) { if ( App::$isLoggedIn ) {
App::$isMember = $this->groupHasMemberAccess( App::$activeGroup ); App::$isMember = $this->groupHasMemberAccess( App::$activeGroup );
if ( empty( App::$isMember ) ) { if ( empty( App::$isMember ) ) {
@ -144,13 +142,27 @@ class Members extends Plugin {
'replace' => ( App::$isLoggedIn && ( App::$isMember === 'monthly' ) ? '$1' : '' ), 'replace' => ( App::$isLoggedIn && ( App::$isMember === 'monthly' ) ? '$1' : '' ),
'enabled' => true, 'enabled' => true,
]; ];
}
parent::__construct( $load );
if ( $this->checkEnabled() && App::$isLoggedIn ) {
if ( ! self::$loaded && $load ) {
App::$userCPlinks[] = (object) self::$userLinks;
App::$topNavRightDropdown .= '<li><a href="{ROOT_URL}member/manage" class="dropdown-item"><i class="fa fa-fw fa-credit-card"></i> Subscriptions</a></li>';
}
}
if ( ! self::$loaded && $load ) {
self::$loaded = true;
}
$api_key = Config::getValue( 'memberships/stripeSecret' ); $api_key = Config::getValue( 'memberships/stripeSecret' );
if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) { if ( $api_key == 'sk_xxxxxxxxxxxxxxx' || empty($api_key) ) {
self::$stripe = false; self::$stripe = false;
} else { } else {
self::$stripe = new StripeClient( $api_key ); self::$stripe = new StripeClient( $api_key );
} }
parent::__construct( $load );
} }
public function groupHasMemberAccess( $activeGroup ) { public function groupHasMemberAccess( $activeGroup ) {
@ -169,6 +181,10 @@ class Members extends Plugin {
$products = new Products; $products = new Products;
$product = $products->findByPriceID( $membership->subscription_price_id ); $product = $products->findByPriceID( $membership->subscription_price_id );
if ( empty( $product ) ) {
Debug::error('Active membership on non-existent product');
return false;
}
if ( $product->stripe_price_monthly == $membership->subscription_price_id ) { if ( $product->stripe_price_monthly == $membership->subscription_price_id ) {
return 'monthly'; return 'monthly';
} }
@ -177,7 +193,6 @@ class Members extends Plugin {
public static function webhookSetup() { public static function webhookSetup() {
$root = Routes::getAddress(); $root = Routes::getAddress();
// $root = "https://stripe.joeykimsey.com/";
$response = self::$stripe->webhookEndpoints->create([ $response = self::$stripe->webhookEndpoints->create([
'enabled_events' => self::$webhookEvents, 'enabled_events' => self::$webhookEvents,
'url' => $root . 'api/stripe/webhook', 'url' => $root . 'api/stripe/webhook',
@ -185,13 +200,66 @@ class Members extends Plugin {
return $response; return $response;
} }
// public static function webhookSetup() { public static function webhookList() {
// $root = Routes::getAddress(); $response = self::$stripe->webhookEndpoints->all(['limit' => 25]);
// $root = "https://stripe.joeykimsey.com/"; return $response;
// $response = self::$stripe->webhookEndpoints->create([ }
// 'enabled_events' => self::$webhookEvents,
// 'url' => $root . 'api/stripe/webhook', public static function webhookRemove( $id ) {
// ]); $response = self::$stripe->webhookEndpoints->delete( $id, []);
// return $response; return $response;
// } }
public static function orphanAbandon( $id ) {
$response = self::$stripe->prices->update(
$id,
['lookup_key' => ""]
);
return $response;
}
public static function findOrphans() {
$orphans = [];
// any prices with keys not represented in our local db will show as an orphan
$result = self::$stripe->prices->search([
'query' => 'lookup_key:"membership-monthly"',
]);
$products = new Products;
if ( ! empty( $result->data ) ) {
$product = $products->findByPriceID( $result->data[0]->id );
if ( empty( $product ) ) {
$data = $result->data[0];
$child = new \stdClass;
$child->price_id = $data->id;
$child->product = $data->product;
$child->lookup_key = $data->lookup_key;
$child->amount = $data->unit_amount;
$orphans[] = $child;
}
}
$result = self::$stripe->prices->search([
'query' => 'lookup_key:"membership-yearly"',
]);
if ( ! empty( $result->data ) ) {
$product = $products->findByPriceID( $result->data[0]->id );
if ( empty( $product ) ) {
$data = $result->data[0];
$child = new \stdClass;
$child->price_id = $data->id;
$child->product = $data->product;
$child->lookup_key = $data->lookup_key;
$child->amount = $data->unit_amount;
$orphans[] = $child;
}
}
return $orphans;
}
} }

View File

@ -0,0 +1,35 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Stripe Webhook Generation</legend>
<hr>
{ADMIN_BREADCRUMBS}
<p>Orphans are stripe prices that have unique lookup_keys used by the membership system, but have no products currently saved.</p>
<table class="table table-striped">
<thead>
<tr>
<th style="width: 10%">price id</th>
<th style="width: 10%">amount</th>
<th style="width: 50%">lookup key</th>
<th style="width: 10%">url</th>
<th style="width: 10%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td>{price_id}</td>
<td>{amount}</td>
<td>{lookup_key}</td>
<td><a href="{ROOT_URL}admin/members/orphans/adopt/{price_id}" class="btn btn-sm btn-success"><i class="fa fa-fw fa-download"></i></a></td>
<td><a href="{ROOT_URL}admin/members/orphans/abandon/{price_id}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td class="text-center" colspan="5">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
</div>

View File

@ -45,7 +45,7 @@
<!-- Submit Button --> <!-- Submit Button -->
<div class="text-center"> <div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg center-block">Send</button> <button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg center-block">Save</button>
</div> </div>
</form> </form>
</div> </div>

View File

@ -0,0 +1,13 @@
<div class="context-main-bg context-main p-3">
<legend class="text-center">Membership Scripts</legend>
<hr>
{ADMIN_BREADCRUMBS}
<ul class="list-unstyled">
<li>
<a href="{ROOT_URL}admin/members/webhooks">Webhook Generation</a>
</li>
<li>
<a href="{ROOT_URL}admin/members/orphans">Orphan Products</a>
</li>
</ul>
</div>

View File

@ -1,10 +1,43 @@
<legend>WARNING: Regenerating existing webhooks makes joey sad, don't do iit!</legend> <div class="context-main-bg context-main p-3">
<legend class="text-center">Stripe Webhook Generation</legend>
<hr>
{ADMIN_BREADCRUMBS}
<table class="table table-striped">
<thead>
<tr>
<th style="width: 10%">id</th>
<th style="width: 10%">status</th>
<th style="width: 50%">enabled_events</th>
<th style="width: 10%">url</th>
<th style="width: 10%"></th>
</tr>
</thead>
<tbody>
{LOOP}
<tr>
<td>{id}</td>
<td>{status}</td>
<td>{enabled_events}</td>
<td>{url}</td>
<td><a href="{ROOT_URL}admin/members/webhooks/delete/{ID}" class="btn btn-sm btn-danger"><i class="fa fa-fw fa-trash"></i></a></td>
</tr>
{/LOOP}
{ALT}
<tr>
<td class="text-center" colspan="5">
No results to show.
</td>
</tr>
{/ALT}
</tbody>
</table>
<div class="text-center">
<h5>WARNING: Regenerating existing webhooks makes joey sad, don't do iit!</h5>
<p>The new webhooks will be generated using the base url: <strong>{urltouse}</strong></p>
<form action="" method="post" class="form-horizontal"> <form action="" method="post" class="form-horizontal">
<div class="form-group"> <div class="form-group">
<label for="submit" class="col-lg-3 control-label"></label> <button name="submit" value="submit" type="submit" class="btn btn-lg btn-danger">re-generate</button>
<div class="col-lg-3">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block ">Submit</button>
</div>
</div> </div>
</form> </form>
</div>
</div>

View File

@ -1,7 +1,7 @@
<div class="container"> <div class="container py-4 context-main-bg my-4">
<div class="row"> <h2 class="text-center">Are You Sure You Want to Cancel?</h2>
<div class="col-md-8 col-md-offset-2 text-center"> <hr>
<h2>Are You Sure You Want to Cancel?</h2> <div class="text-center">
<p class="lead"> <p class="lead">
Cancelling your subscription means you'll miss out on exclusive features, updates, and benefits. Cancelling your subscription means you'll miss out on exclusive features, updates, and benefits.
</p> </p>
@ -9,12 +9,11 @@
Consider staying to continue enjoying the full experience of our service. Consider staying to continue enjoying the full experience of our service.
</p> </p>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-3 offset-3">
<a href="{ROOT_URL}member/cancelconfirm/{cancelid}" class="btn btn-lg btn-danger btn-block">Cancel Subscription</a> <a href="{ROOT_URL}member/cancelconfirm/{cancelid}" class="btn btn-lg btn-outline-danger btn-block">Cancel Subscription</a>
</div>
<div class="col-md-6">
<a href="{ROOT_URL}member/manage" class="btn btn-lg btn-primary btn-block">Go Back</a>
</div> </div>
<div class="col-md-3">
<a href="{ROOT_URL}member/manage" class="btn btn-lg btn-primary btn-block atb-green-bg">Go Back</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,3 @@
<!-- Compare plans --> <!-- Compare plans -->
<div class="table-responsive pricing-container container pb-4" id="compare"> <div class="table-responsive pricing-container container pb-4" id="compare">
<h1 class="display-6 text-center my-4">Compare plans</h1> <h1 class="display-6 text-center my-4">Compare plans</h1>
@ -31,7 +30,7 @@
<td><i class="fa fa-fw fa-check atb-green"></i></td> <td><i class="fa fa-fw fa-check atb-green"></i></td>
</tr> </tr>
<tr> <tr>
<th scope="row" class="text-start">Share bookmarks abd folders</th> <th scope="row" class="text-start">Share bookmarks and folders</th>
<td><i class="fa fa-fw fa-check"></i></td> <td><i class="fa fa-fw fa-check"></i></td>
<td><i class="fa fa-fw fa-check atb-green"></i></td> <td><i class="fa fa-fw fa-check atb-green"></i></td>
<td><i class="fa fa-fw fa-check atb-green"></i></td> <td><i class="fa fa-fw fa-check atb-green"></i></td>
@ -88,9 +87,6 @@
<li>Access from any device</li> <li>Access from any device</li>
<li>Share access with anyone</li> <li>Share access with anyone</li>
</ul> </ul>
<a href="/register" class="mt-auto w-100 btn btn-lg atb-green-outline">
Sign-Up for Free
</a>
</div> </div>
</div> </div>
</div> </div>
@ -100,7 +96,7 @@
<h4 class="my-0 fw-normal">Monthly</h4> <h4 class="my-0 fw-normal">Monthly</h4>
</div> </div>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<h1 class="card-title pricing-card-title">&#36;4.99<small class="text-muted fw-light">/month</small></h1> <h1 class="card-title pricing-card-title">{prettyPriceMonthly}<small class="text-muted fw-light">/month</small></h1>
<ul class="list-unstyled mt-3 mb-4"> <ul class="list-unstyled mt-3 mb-4">
<li>Import/Export Features</li> <li>Import/Export Features</li>
<li>Integration with TempusTools App (WIP)</li> <li>Integration with TempusTools App (WIP)</li>
@ -108,7 +104,7 @@
<li>Direct control of Feature Development</li> <li>Direct control of Feature Development</li>
<li>Early Access to new features</li> <li>Early Access to new features</li>
</ul> </ul>
<a href="/member/signup/monthly" class="mt-auto w-100 btn btn-lg atb-green-bg"> <a href="/member/checkout/monthly" class="mt-auto w-100 btn btn-lg atb-green-bg">
Get started Get started
</a> </a>
</div> </div>
@ -120,11 +116,11 @@
<h4 class="my-0 fw-normal">Yearly</h4> <h4 class="my-0 fw-normal">Yearly</h4>
</div> </div>
<div class="card-body d-flex flex-column"> <div class="card-body d-flex flex-column">
<h1 class="card-title pricing-card-title">&#36;19.99<small class="text-muted fw-light">/year</small></h1> <h1 class="card-title pricing-card-title">{prettyPriceYearly}<small class="text-muted fw-light">/year</small></h1>
<ul class="list-unstyled mt-3 mb-4"> <ul class="list-unstyled mt-3 mb-4">
<li>Its cheaper if you like the product</li> <li>Its cheaper if you like the product</li>
</ul> </ul>
<a href="/member/signup/yearly" class="mt-auto w-100 btn btn-lg atb-green-bg"> <a href="/member/checkout/yearly" class="mt-auto w-100 btn btn-lg atb-green-bg">
Get started Get started
</a> </a>
</div> </div>

View File

@ -1,7 +1,8 @@
<div class="container py-4"> <div class="container py-4 context-main-bg my-4">
<h3 class="mb-4 text-center">Manage Memberships</h3>
<hr>
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-8"> <div class="col-md-8">
<legend>Memberships</legend>
<table class="table text-center context-main"> <table class="table text-center context-main">
<thead> <thead>
<tr> <tr>
@ -23,13 +24,13 @@
<td>{DTC=date}{current_period_start}{/DTC}</td> <td>{DTC=date}{current_period_start}{/DTC}</td>
<td>{DTC=date}{current_period_end}{/DTC}</td> <td>{DTC=date}{current_period_end}{/DTC}</td>
<td> <td>
<a href="{ROOT_URL}member/pause/{ID}" class="btn btn-sm btn-primary"> <a href="{ROOT_URL}member/pause/{ID}" class="btn btn-sm btn-outline-warning">
<i class="fa fa-fw fa-pause"></i> <i class="fa fa-fw fa-pause"></i>
</a> </a>
</td> </td>
<td> <td>
<a href="{ROOT_URL}member/cancel/{ID}" class="btn btn-sm btn-danger"> <a href="{ROOT_URL}member/cancel/{ID}" class="btn btn-sm btn-outline-danger">
<i class="fa fa-fw fa-trash"></i> <i class="fa fa-fw fa-ban"></i>
</a> </a>
</td> </td>
</tr> </tr>
@ -43,7 +44,7 @@
{/ALT} {/ALT}
</tbody> </tbody>
</table> </table>
<a href="{ROOT_URL}member/managepayment" class="btn btn-sm btn-primary"> <a href="{ROOT_URL}member/managepayment" class="btn btn-sm btn-primary atb-green-bg">
Manage Payment Method Manage Payment Method
</a> </a>
</div> </div>

View File

@ -11,7 +11,8 @@
Right now, this entire system was built and managed by myself. I have used my own version of this for years, but translating it to a publicly available product is not a 1-to-1 job. There may be bugs or issues encountered while you use the product. I can't guarantee a fix for every need in every case immediately, but I do actively keep track of bugs and work hard to ensure everyone has a great experience using the app. Right now, this entire system was built and managed by myself. I have used my own version of this for years, but translating it to a publicly available product is not a 1-to-1 job. There may be bugs or issues encountered while you use the product. I can't guarantee a fix for every need in every case immediately, but I do actively keep track of bugs and work hard to ensure everyone has a great experience using the app.
</p> </p>
<div class="text-center mt-4 pb-4"> <div class="text-center mt-4 pb-4">
{loggedin}<a href="/bugreport" class="btn btn-primary btn-lg px-5 atb-green-bg">Report a Bug</a>{/loggedin} {loggedin}<a href="/bugreport" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Report a Bug</a>{/loggedin}
<a href="/member/manage" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-bg">Manage Membership</a>
<a href="/contact" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Contact Us</a> <a href="/contact" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Contact Us</a>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
<div class="container"> <div class="container py-4 context-main-bg my-4">
<div class="row"> <h2 class="text-center">Are You Sure You Want to Pause?</h2>
<div class="col-md-8 col-md-offset-2 text-center"> <hr>
<h2>Are You Sure You Want to Pause?</h2> <div class="text-center">
<p class="lead"> <p class="lead">
Pausing your subscription means you'll miss out on exclusive features, updates, and benefits. Pausing your subscription means you'll miss out on exclusive features, updates, and benefits.
</p> </p>
@ -9,12 +9,11 @@
Consider staying to continue enjoying the full experience of our service. Consider staying to continue enjoying the full experience of our service.
</p> </p>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-3 offset-3">
<a href="{ROOT_URL}member/pauseconfirm/{pauseid}" class="btn btn-lg btn-danger btn-block">Pause Subscription</a> <a href="{ROOT_URL}member/pauseconfirm/{pauseid}" class="btn btn-lg btn-outline-danger btn-block">Pause Subscription</a>
</div>
<div class="col-md-6">
<a href="{ROOT_URL}member/manage" class="btn btn-lg btn-primary btn-block">Go Back</a>
</div> </div>
<div class="col-md-3">
<a href="{ROOT_URL}member/manage" class="btn btn-lg btn-primary btn-block atb-green-bg">Go Back</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,4 +1,14 @@
<div class="jumbotron text-center"> <div class="col-8 mx-auto p-4 rounded shadow-sm context-main-bg my-4">
<h1>Take your time, its not a sprint, its a marathon.</h1> <h2 class="text-center atb-green mb-4">Almost there!</h2>
<p>Its ok, no-one wants to checkout too fast, its embarrassing.</p> <p class="lead">
Take your time, its not a sprint, its a marathon.
Nno-one wants to checkout too fast, its embarrassing.
</p>
<p class="text-muted">
If you think this is a tool that you could really use and can't afford it, use the contact form below and tell me why you need it.
</p>
<div class="text-center mt-4 pb-4">
{loggedin}<a href="/bugreport" class="btn btn-primary btn-lg px-5 atb-green-bg">Report a Bug</a>{/loggedin}
<a href="/contact" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Contact Us</a>
</div>
</div> </div>

View File

@ -1,5 +1,14 @@
<div class="jumbotron text-center"> <div class="col-8 mx-auto p-4 rounded shadow-sm context-main-bg my-4">
<h1>Thanks for joining!</h1> <h2 class="text-center atb-green mb-4">Thanks for joining!</h2>
<p>Its people like you who keep the pixels on around here. <p class="lead">
Its people like me who tell you life is unfair and it could take up to an hour for your membership to activate.</p> Its people like you who keep the pixels on around here.
Its people like me who tell people like you that unfortunately, it could take up to an hour for your membership to activate.
</p>
<p class="text-muted">
With that said, its usually instant, and if its taking too long, just reach out to us via the contact form.
</p>
<div class="text-center mt-4 pb-4">
{loggedin}<a href="/bugreport" class="btn btn-primary btn-lg px-5 atb-green-bg">Report a Bug</a>{/loggedin}
<a href="/contact" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Contact Us</a>
</div>
</div> </div>

View File

@ -1,6 +1,6 @@
<div class="col-8 mx-auto p-4 rounded shadow-sm mb-5 context-main-bg mt-4 text-center"> <div class="col-8 mx-auto p-4 rounded shadow-sm mb-5 context-main-bg mt-4 text-center">
<div class="row"> <div class="row">
<h2 class="text-primary mb-4">Upgrade to a Yearly Plan</h2> <h2 class="mb-4 atb-green">Upgrade to a Yearly Plan</h2>
<p class="lead"> <p class="lead">
Save more and enjoy uninterrupted access to all features with our yearly plan! Save more and enjoy uninterrupted access to all features with our yearly plan!
</p> </p>
@ -10,12 +10,12 @@
</p> </p>
<div class="row"> <div class="row">
<div class="col-md-6"> <div class="col-md-6">
<a href="/member/checkout/yearly" class="btn btn-lg btn-success btn-block"> <a href="/member/checkout/upgrade" class="btn btn-lg btn-block atb-green-bg">
Upgrade to Yearly Upgrade to Yearly
</a> </a>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<a href="/member/upgrade" class="btn btn-lg btn-primary btn-block"> <a href="/member" class="btn btn-lg btn-block atb-green-outline">
Stay on Monthly Stay on Monthly
</a> </a>
</div> </div>

View File

@ -0,0 +1,13 @@
<div class="col-8 mx-auto p-4 rounded shadow-sm context-main-bg my-4">
<h2 class="text-center atb-green mb-4">Thanks for the vote of confidence!</h2>
<p class="lead">
While you were more profitable as a monthly user, I do appreciate the vote of confidence it takes to commit to 4x the cost at once.
</p>
<p class="text-muted">
There really is no more benefit than you saving money, so enjoy the features you have already come to know and enjoy.
</p>
<div class="text-center mt-4 pb-4">
{loggedin}<a href="/bugreport" class="btn btn-primary btn-lg px-5 atb-green-bg">Report a Bug</a>{/loggedin}
<a href="/contact" class="btn btn-outline-secondary btn-lg px-5 ms-3 atb-green-outline">Contact Us</a>
</div>
</div>

View File

@ -4,7 +4,7 @@
<div class="col-lg-6 mx-auto"> <div class="col-lg-6 mx-auto">
<p class="lead mb-4">Quickly add and manage an internet worth of bookmarks with AllTheBookmarks, my personal favorite app for keeping track of dead memes long after they outlive their hilarity.</p> <p class="lead mb-4">Quickly add and manage an internet worth of bookmarks with AllTheBookmarks, my personal favorite app for keeping track of dead memes long after they outlive their hilarity.</p>
<div class="d-grid gap-2 d-sm-flex justify-content-sm-center mb-5"> <div class="d-grid gap-2 d-sm-flex justify-content-sm-center mb-5">
<a href="#pricing" class="btn btn-primary atb-green-bg btn-lg px-4 me-sm-3"> <a href="#pricing" class="btn atb-green-bg btn-lg px-4 me-sm-3">
Buy Now (Too Fast) Buy Now (Too Fast)
</a> </a>
<a href="#features" class="btn btn-outline-secondary btn-lg px-4 atb-green-outline"> <a href="#features" class="btn btn-outline-secondary btn-lg px-4 atb-green-outline">
@ -247,7 +247,7 @@
<td><i class="fa fa-fw fa-check atb-green"></i></td> <td><i class="fa fa-fw fa-check atb-green"></i></td>
</tr> </tr>
<tr> <tr>
<th scope="row" class="text-start">Share bookmarks abd folders</th> <th scope="row" class="text-start">Share bookmarks and folders</th>
<td><i class="fa fa-fw fa-check"></i></td> <td><i class="fa fa-fw fa-check"></i></td>
<td><i class="fa fa-fw fa-check atb-green"></i></td> <td><i class="fa fa-fw fa-check atb-green"></i></td>
<td><i class="fa fa-fw fa-check atb-green"></i></td> <td><i class="fa fa-fw fa-check atb-green"></i></td>

View File

@ -15,7 +15,7 @@
</div> </div>
</fieldset> </fieldset>
<input type="hidden" name="token" value="{TOKEN}"> <input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-primary">Update</button> <button name="submit" value="submit" type="submit" class="btn btn-primary atb-green-bg">Update</button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -19,7 +19,7 @@
</div> </div>
</fieldset> </fieldset>
<input type="hidden" name="token" value="{TOKEN}"> <input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-primary">Update</button> <button name="submit" value="submit" type="submit" class="btn btn-primary atb-green-bg">Update</button>
</form> </form>
</div> </div>
</div> </div>

View File

@ -2,13 +2,13 @@
<h3 class="mb-4">Preferences</h3> <h3 class="mb-4">Preferences</h3>
<hr> <hr>
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-md-4"> <div class="col-md-6">
<form action="" method="post" class=""> <form action="" method="post" class="">
<fieldset> <fieldset>
{PREFERENCES_FORM} {PREFERENCES_FORM}
</fieldset> </fieldset>
<input type="hidden" name="token" value="{TOKEN}"> <input type="hidden" name="token" value="{TOKEN}">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block">Update</button><br> <button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block atb-green-bg">Update</button><br>
</form> </form>
</div> </div>
</div> </div>