This commit is contained in:
Joey Kimsey
2025-01-01 22:17:38 -05:00
parent ccc134d1b2
commit 1ef85c6c2c
65 changed files with 1200 additions and 215 deletions

View File

@ -18,6 +18,8 @@ use TheTempusProject\Hermes\Functions\Redirect;
use TheTempusProject\Bedrock\Functions\Session; use TheTempusProject\Bedrock\Functions\Session;
use TheTempusProject\Bedrock\Classes\Config; use TheTempusProject\Bedrock\Classes\Config;
use TheTempusProject\Models\Token; use TheTempusProject\Models\Token;
use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Houdini\Classes\Views;
class ApiController extends Controller { class ApiController extends Controller {
protected static $canAccessApplicationApi = false; protected static $canAccessApplicationApi = false;
@ -26,16 +28,15 @@ class ApiController extends Controller {
protected static $authToken; protected static $authToken;
public function __construct( $secure = true ) { public function __construct( $secure = true ) {
header('Content-Type: application/json; charset=utf-8');
parent::__construct(); parent::__construct();
$this->verifyApiRequest(); Template::setTemplate( 'api' );
if ( $secure && ! $this->canUseApi() ) {
Session::flash( 'error', 'You do not have permission to view this page.' );
return Redirect::home();
}
Template::noFollow(); Template::noFollow();
Template::noIndex(); Template::noIndex();
Template::addHeader( 'Content-Type: application/json; charset=utf-8' ); $res = $this->verifyApiRequest();
Template::setTemplate( 'api' ); if ( $secure && ! $this->canUseApi() ) {
exit( $res );
}
} }
protected function canUseApi() { protected function canUseApi() {
@ -72,16 +73,16 @@ class ApiController extends Controller {
} else { } else {
$secret = $this->getSecretToken(); $secret = $this->getSecretToken();
if ( empty( $secret ) ) { if ( empty( $secret ) ) {
return; return Views::simpleView( 'api.response', ['response' => json_encode( [ 'error' => 'invalid secret' ], true )]);
} }
$token = $tokens->findBySecret( $secret ); $token = $tokens->findBySecret( $secret );
} }
if ( empty( $token ) ) { if ( empty( $token ) ) {
return; return Views::simpleView( 'api.response', ['response' => json_encode( [ 'error' => 'invalid token' ], true )]);
} }
self::$authToken = $token; self::$authToken = $token;
if ( $token->expiresAt <= time() && empty( $secret ) ) { if ( $token->expiresAt <= time() && empty( $secret ) ) {
return; return Views::simpleView( 'api.response', ['response' => json_encode( [ 'error' => 'token expired' ], true )]);
} }
if ( $token->expiresAt <= time() ) { if ( $token->expiresAt <= time() ) {
self::$canAccessAuthenticationApi = true; self::$canAccessAuthenticationApi = true;

View File

@ -114,6 +114,7 @@ class Forms extends Check {
self::addHandler( 'install', __CLASS__, 'install' ); self::addHandler( 'install', __CLASS__, 'install' );
self::addHandler( 'adminCreateToken', __CLASS__, 'adminCreateToken' ); self::addHandler( 'adminCreateToken', __CLASS__, 'adminCreateToken' );
self::addHandler( 'apiLogin', __CLASS__, 'apiLogin' ); self::addHandler( 'apiLogin', __CLASS__, 'apiLogin' );
self::addHandler( 'updatePreference', __CLASS__, 'updatePreference' );
self::addHandler( 'installStart', __CLASS__, 'install', [ 'start' ] ); self::addHandler( 'installStart', __CLASS__, 'install', [ 'start' ] );
self::addHandler( 'installAgreement', __CLASS__, 'install', [ 'agreement' ] ); self::addHandler( 'installAgreement', __CLASS__, 'install', [ 'agreement' ] );
self::addHandler( 'installCheck', __CLASS__, 'install', [ 'check' ] ); self::addHandler( 'installCheck', __CLASS__, 'install', [ 'check' ] );
@ -650,4 +651,16 @@ class Forms extends Check {
} }
return true; return true;
} }
public static function updatePreference() {
if ( !Input::exists( 'prefName' ) ) {
self::addUserError( 'You must specify a name' );
return false;
}
if ( !Input::exists( 'prefValue' ) ) {
self::addUserError( 'You must specify a value' );
return false;
}
return true;
}
} }

View File

@ -201,6 +201,8 @@ class Preferences {
$tempPrefsArray = $this->normalizePreferenceArray( $name, $details ); $tempPrefsArray = $this->normalizePreferenceArray( $name, $details );
if ( isset( $populated[ $name ] ) ) { if ( isset( $populated[ $name ] ) ) {
$tempPrefsArray['value'] = $populated[$name]; $tempPrefsArray['value'] = $populated[$name];
} else {
$tempPrefsArray['value'] = $tempPrefsArray['default'];
} }
// $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' ) { if ( $tempPrefsArray['type'] == 'checkbox' ) {

View File

@ -27,15 +27,14 @@ class Login extends ApiController {
parent::__construct( false ); parent::__construct( false );
self::$tokens = new Token; self::$tokens = new Token;
self::$user = new User; self::$user = new User;
// Template::addHeader( 'Access-Control-Allow-Origin: *' ); Template::addHeader( 'Access-Control-Allow-Origin: *' );
// Template::addHeader( 'Content-Type: application/json; charset=utf-8' ); Template::addHeader( 'Content-Type: application/json; charset=utf-8' );
} }
public function index() { public function index() {
header('Access-Control-Allow-Origin: *'); if ( ! Forms::check( 'apiLogin' ) ) {
if ( !Forms::check( 'apiLogin' ) ) {
$responseType = 'error'; $responseType = 'error';
$response = 'malformed input1'; $response = 'malformed input';
return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]); return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
} }
$user = self::$user->authorize( Input::post( 'username' ), Input::post( 'password' ) ); $user = self::$user->authorize( Input::post( 'username' ), Input::post( 'password' ) );
@ -45,7 +44,7 @@ class Login extends ApiController {
return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]); return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $response ], true )]);
} }
$responseType = 'token'; $responseType = 'token';
$token = self::$tokens->findOrCreateUserToken( $user->ID ); $token = self::$tokens->findOrCreateUserToken( $user->ID, true );
return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $token ], true )]); return Views::view( 'api.response', ['response' => json_encode( [ $responseType => $token ], true )]);
} }
} }

View File

@ -36,12 +36,12 @@ class Usercp extends Controller {
Redirect::home(); Redirect::home();
} }
Template::noIndex(); Template::noIndex();
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
} }
public function email() { public function email() {
self::$title = 'Email Settings'; self::$title = 'Email Settings';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
if ( App::$activeUser->confirmed != '1' ) { if ( App::$activeUser->confirmed != '1' ) {
return Issues::add( 'notice', 'You need to confirm your email address before you can make modifications. If you would like to resend that confirmation link, please <a href="/register/resend">click here</a>', true ); return Issues::add( 'notice', 'You need to confirm your email address before you can make modifications. If you would like to resend that confirmation link, please <a href="/register/resend">click here</a>', true );
} }
@ -68,11 +68,15 @@ class Usercp extends Controller {
public function index() { public function index() {
self::$title = 'User Control Panel'; self::$title = 'User Control Panel';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
Views::view( 'profile', App::$activeUser ); Views::view( 'profile', App::$activeUser );
} }
public function password() { public function password() {
self::$title = 'Password Settings'; self::$title = 'Password Settings';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
if ( !Input::exists() ) { if ( !Input::exists() ) {
return Views::view( 'user_cp.password_change' ); return Views::view( 'user_cp.password_change' );
} }
@ -94,6 +98,8 @@ class Usercp extends Controller {
public function settings() { public function settings() {
self::$title = 'Preferences'; self::$title = 'Preferences';
$menu = Views::simpleView( 'nav.usercp', App::$userCPlinks );
Navigation::activePageSelect( $menu, null, true, true );
$prefs = new Preferences; $prefs = new Preferences;
$fields = App::$activePrefs; $fields = App::$activePrefs;
if ( Input::exists( 'submit' ) ) { if ( Input::exists( 'submit' ) ) {
@ -109,4 +115,39 @@ class Usercp extends Controller {
Components::set( 'PREFERENCES_FORM', $prefs->getFormHtml( $fields ) ); Components::set( 'PREFERENCES_FORM', $prefs->getFormHtml( $fields ) );
Views::view( 'user_cp.settings', App::$activeUser ); Views::view( 'user_cp.settings', App::$activeUser );
} }
public function updatePref() {
Template::setTemplate( 'api' );
if ( ! App::$isLoggedIn ) {
return Views::view( 'api.response', ['response' => json_encode( [ 'error' => 'Not Logged In' ], true )]);
}
if ( ! Forms::check( 'updatePreference' ) ) {
return Views::view( 'api.response', ['response' => json_encode( [ 'error' => Check::userErrors() ], true )]);
}
$name = Input::post( 'prefName' );
$value = Input::post('prefValue' );
if ( 'false' === $value ) {
$value = false;
} elseif ( 'true' === $value ) {
$value = true;
}
if ( empty( Preferences::get( $name ) ) ) {
return Views::view( 'api.response', ['response' => json_encode( [ 'error' => 'Unknown Preference' ], true )]);
}
$prefs = new Preferences;
$fields1 = $prefs->convertFormToArray( true, false );
$fields2 = [];
$fields3 = $fields1;
if ( isset( $fields1[ $name ] ) ) {
$fields2[ $name ] = $value;
$fields3[ $name ] = $value;
}
$result = self::$user->updatePrefs( $fields3, App::$activeUser->ID );
return Views::view( 'api.response', ['response' => json_encode( $result, true )]);
}
} }

View File

@ -133,9 +133,11 @@ document.addEventListener('DOMContentLoaded', function () {
toggleButton.addEventListener('click', function () { toggleButton.addEventListener('click', function () {
if (darkModeStylesheet.disabled) { if (darkModeStylesheet.disabled) {
toggleDarkModePref( true );
darkModeStylesheet.disabled = false; darkModeStylesheet.disabled = false;
localStorage.setItem('darkMode', 'enabled'); localStorage.setItem('darkMode', 'enabled');
} else { } else {
toggleDarkModePref( false );
darkModeStylesheet.disabled = true; darkModeStylesheet.disabled = true;
localStorage.setItem('darkMode', 'disabled'); localStorage.setItem('darkMode', 'disabled');
} }
@ -150,4 +152,13 @@ document.addEventListener('DOMContentLoaded', function () {
} }
}); });
}); });
function toggleDarkModePref( value ) {
var fields = {};
fields.prefName = 'darkMode';
fields.prefValue = value;
$.post( '/usercp/updatePref', fields ).done(function(response) {
// alert('Timer updated successfully!');
});
}
}); });

View File

@ -94,10 +94,15 @@ class Token extends DatabaseModel {
return false; return false;
} }
public function findOrCreateUserToken( $user_id ) { public function findOrCreateUserToken( $user_id, $refresh = false ) {
$test = $this->findUserToken( $user_id ); $test = $this->findUserToken( $user_id );
if ( ! empty( $test ) ) { if ( ! empty( $test ) ) {
return $test->token; if ( ! empty( $refresh ) ) {
$token = $this->refresh( $test->ID, 'user' );
} else {
$token = $test->token;
}
return $token;
} }
$expiration = Config::getValue( 'api/UserAccessTokenExpiration' ); $expiration = Config::getValue( 'api/UserAccessTokenExpiration' );

View File

@ -121,6 +121,11 @@ class User extends DatabaseModel {
'50', '50',
], ],
], ],
'darkMode' => [
'pretty' => 'Enable Dark-Mode viewing',
'type' => 'checkbox',
'default' => 'false',
],
]; ];
protected static $avatars; protected static $avatars;
protected static $preferences; protected static $preferences;

View File

@ -5,7 +5,7 @@
<div class="card-body"> <div class="card-body">
<ol class="list-unstyled"> <ol class="list-unstyled">
{LOOP} {LOOP}
<li><a href="{ROOT_URL}blog/post/{ID}">{title}</a></li> <li><a href="{ROOT_URL}blog/post/{ID}" class="text-decoration-none atb-green">{title}</a></li>
{/LOOP} {/LOOP}
{ALT} {ALT}
<li>No Posts to show</li> <li>No Posts to show</li>
@ -13,6 +13,6 @@
</ol> </ol>
</div> </div>
<div class="card-footer"> <div class="card-footer">
<a href="{ROOT_URL}blog">View All</a> <a href="{ROOT_URL}blog" class="text-decoration-none atb-green">View All</a>
</div> </div>
</div> </div>

View File

@ -2,7 +2,7 @@
<h4 class="fst-italic">Archives</h4> <h4 class="fst-italic">Archives</h4>
<ul class="list-unstyled mb-0"> <ul class="list-unstyled mb-0">
{LOOP} {LOOP}
<li>({count}) <a href="{ROOT_URL}blog/month/{month}/{year}">{monthText} {year}</a></li> <li>({count}) <a href="{ROOT_URL}blog/month/{month}/{year}" class="text-decoration-none atb-green">{monthText} {year}</a></li>
{/LOOP} {/LOOP}
{ALT} {ALT}
<li>None To Show</li> <li>None To Show</li>

View File

@ -22,13 +22,12 @@ class BookmarkFolders extends ApiController {
protected static $folders; protected static $folders;
public function __construct() { public function __construct() {
header('Access-Control-Allow-Origin: *');
parent::__construct(); parent::__construct();
self::$folders = new Folders; self::$folders = new Folders;
} }
public function create() { public function create() {
header('Access-Control-Allow-Origin: *');
$user = self::$authToken->createdBy; $user = self::$authToken->createdBy;
$payload = @file_get_contents('php://input'); $payload = @file_get_contents('php://input');
@ -54,7 +53,6 @@ class BookmarkFolders extends ApiController {
} }
public function list( $id = '' ) { public function list( $id = '' ) {
header('Access-Control-Allow-Origin: *');
$user = self::$authToken->createdBy; $user = self::$authToken->createdBy;
$folders = self::$folders->bySpecificUser( $user ); $folders = self::$folders->bySpecificUser( $user );

View File

@ -1,9 +1,10 @@
<?php <?php
/** /**
* app/controllers/extensions.php * app/plugins/bookmarks/controllers/extensions.php
* *
* This is the extensions controller. * This is the extensions controller.
* *
* @package TP Bookmarks
* @version 3.0 * @version 3.0
* @author Joey Kimsey <Joey@thetempusproject.com> * @author Joey Kimsey <Joey@thetempusproject.com>
* @link https://TheTempusProject.com * @link https://TheTempusProject.com
@ -15,10 +16,17 @@ use TheTempusProject\Classes\Controller;
use TheTempusProject\Houdini\Classes\Views; use TheTempusProject\Houdini\Classes\Views;
use TheTempusProject\Houdini\Classes\Issues; use TheTempusProject\Houdini\Classes\Issues;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Hermes\Functions\Route as Routes;
use TheTempusProject\Models\Token;
use TheTempusProject\Houdini\Classes\Components;
use TheTempusProject\Models\Folders;
use TheTempusProject\Houdini\Classes\Template;
use TheTempusProject\Bedrock\Functions\Input;
class Extensions extends Controller { class Extensions extends Controller {
public function index() { public function index() {
self::$title = 'Browser Extensions'; self::$title = 'Browser Extensions';
self::$pageDescription = 'Our extensions cover all major browsers with the notable exception of Safari. Chrome, Opera, Brave, Firefox, and Edge are all represented; giving users a simple way to add bookmarks and folders from any page.';
if ( App::$isLoggedIn ) { if ( App::$isLoggedIn ) {
Issues::add( 'success', 'We also have a simple solution to using the app from your mobile devices <a href="/extensions/mobile">here</a>.' ); Issues::add( 'success', 'We also have a simple solution to using the app from your mobile devices <a href="/extensions/mobile">here</a>.' );
} }
@ -27,39 +35,114 @@ class Extensions extends Controller {
public function mobile() { public function mobile() {
self::$title = 'Mobile Bookmarklet'; self::$title = 'Mobile Bookmarklet';
self::$pageDescription = 'When you find yourself on the go, sometimes an extension isn\'t an option. For those times, we have the mobile bookmarklet to allow you to add bookmarks from your mobile devices.';
if ( ! App::$isLoggedIn ) { if ( ! App::$isLoggedIn ) {
return Issues::add( 'error', 'Unfortunately you will need to sign in to generate the bookmarklet unique to your account.' ); Issues::add( 'notice', 'Since the bookmarklet is tied directly to your account, you will need to <a href="/home/login">log in</a> to generate the code for your account.' );
return Views::view( 'bookmarks.extensions.bookmarkletExplainer' );
} }
if ( Input::exists('privacy') ) {
$privacy = 'const privacy = "' . Input::post('privacy') . '";';
} else {
$privacy = 'const privacy = prompt("Enter privacy level (e.g., public/private):");';
}
Components::set( 'BK_JS_PRIVACY', $privacy );
if ( Input::exists('includeDescription') ) {
$description = 'const notes = prompt("Enter a description (optional):");';
} else {
$description = '';
}
Components::set( 'BK_JS_NOTES', $description );
if ( Input::exists('includeColor') ) {
$color = 'const color = "'.Input::post('color').'";';
} else {
$color = '';
}
Components::set( 'BK_JS_COLOR', $color );
if ( Input::exists('includeFolder') ) {
$folder = 'const folder = "'.Input::post('folder_id').'";';
} else {
$folder = '';
}
Components::set( 'BK_JS_FOLDER', $folder );
$this->setFolderSelect(Input::post('folder'));
$tokens = new Token;
$apiKey = $tokens->findOrCreateUserToken( App::$activeUser->ID, true );
$apiUrl = Routes::getAddress() . 'api/bookmarks/create';
Components::set( 'BK_API_KEY', $apiKey );
Components::set( 'BK_API_URL', $apiUrl );
Views::view( 'bookmarks.extensions.bookmarklet' ); Views::view( 'bookmarks.extensions.bookmarklet' );
} }
public function chrome() { public function chrome() {
self::$title = 'Chrome Extension'; self::$title = 'Chrome Extension';
self::$pageDescription = 'Our Chrome extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.chrome' ); Views::view( 'bookmarks.extensions.chrome' );
} }
public function firefox() { public function firefox() {
self::$title = 'Firefox Extension'; self::$title = 'Firefox Extension';
self::$pageDescription = 'Our Firefox extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.firefox' ); Views::view( 'bookmarks.extensions.firefox' );
} }
public function opera() { public function opera() {
self::$title = 'Opera Extension'; self::$title = 'Opera Extension';
self::$pageDescription = 'Our Opera extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.opera' ); Views::view( 'bookmarks.extensions.opera' );
} }
public function edge() { public function edge() {
self::$title = 'Edge Extension'; self::$title = 'Edge Extension';
self::$pageDescription = 'Our Edge extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.edge' ); Views::view( 'bookmarks.extensions.edge' );
} }
public function brave() { public function brave() {
self::$title = 'Brave Extension'; self::$title = 'Brave Extension';
self::$pageDescription = 'Our Brave extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.brave' ); Views::view( 'bookmarks.extensions.brave' );
} }
public function safari() { public function safari() {
self::$title = 'Safari Extension'; self::$title = 'Safari Extension';
self::$pageDescription = 'Our Safari extension allows you to quickly add new bookmarks or folders right from your browser\'s toolbar.';
Views::view( 'bookmarks.extensions.safari' ); Views::view( 'bookmarks.extensions.safari' );
} }
private function setFolderSelect( $folderID = null ) {
$folders = new Folders;
$options = $folders->simpleByUser();
$out = '';
$out .= '<select name="folder_id" id="folder_id" class="form-control">';
if ( isset( $options[0] ) ) {
$assocOptions = [];
foreach ( $options as $key => $value ) {
$assocOptions[$value] = $value;
}
$options = $assocOptions;
}
if ( ! empty( $options ) ) {
foreach ( $options as $fieldname => $value ) {
if ( $value == $folderID ) {
$selected = ' selected';
} else {
$selected = '';
}
$out .= '<option value="' . $value . '"' . $selected . '>' . $fieldname . '</option>';
}
} else {
$out .= '<option value="0" selected>No Folder</option>';
}
$out .= '</select>';
$folderSelect = Template::parse( $out );
Components::set( 'folderSelect', $folderSelect );
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,35 @@
javascript:(function() {
const apiKey = localStorage.getItem('notAnAuthToken');
const apiUrl = localStorage.getItem('api_url');
const name = prompt("Enter a name for the bookmark:");
const notes = prompt("Enter any notes (optional):");
const color = prompt("Enter a color (optional):");
const privacy = prompt("Enter privacy level (e.g., public/private):");
const folder = prompt("Enter a folder (optional):");
const url = window.location.href;
if (!name) {
alert("Name is required.");
return;
}
fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({ name, url, notes, color, privacy, folder })
})
.then(response => {
if (response.ok) {
alert("Bookmark saved successfully!");
} else {
alert("Failed to save bookmark. Please check your API key.");
}
})
.catch(error => {
console.error(error);
alert("An unknown error occurred while saving the bookmark.");
});
})();

View File

@ -20,12 +20,19 @@ document.addEventListener('DOMContentLoaded', () => {
toggleVisibility('dashShareButtonSwitch', 'btn-share'); toggleVisibility('dashShareButtonSwitch', 'btn-share');
// Retrieve the list of collapsed folderCard IDs from local storage // Retrieve the list of collapsed folderCard IDs from local storage
const collapsedFolders = JSON.parse(localStorage.getItem( 'collapsedFolders' )) || [];
const onDashboard = document.getElementById("dash_id");
let collapsedFolders;
if ( onDashboard ) {
collapsedFolders = JSON.parse(localStorage.getItem( 'collapsedDashFolders' + onDashboard.value )) || [];
} else {
collapsedFolders = JSON.parse(localStorage.getItem( 'collapsedFolders' )) || [];
}
// Collapse the elements stored in local storage when the page loads // Collapse the elements stored in local storage when the page loads
collapsedFolders.forEach((folderId) => { collapsedFolders.forEach((folderId) => {
const collapseElement = document.querySelector(`#Collapse${folderId}`); const collapseElement = document.querySelector(`#Collapse${folderId}`);
if (collapseElement) { if ( collapseElement ) {
collapseElement.classList.remove('show'); collapseElement.classList.remove('show');
} }
}); });
@ -40,19 +47,31 @@ document.addEventListener('DOMContentLoaded', () => {
// Listen for collapse and expand events // Listen for collapse and expand events
collapseElement.addEventListener('hidden.bs.collapse', () => { collapseElement.addEventListener('hidden.bs.collapse', () => {
let storageName;
// Add the folderCard ID to local storage when collapsed // Add the folderCard ID to local storage when collapsed
if (!collapsedFolders.includes(folderCardId)) { if (!collapsedFolders.includes(folderCardId)) {
collapsedFolders.push(folderCardId); collapsedFolders.push(folderCardId);
localStorage.setItem( 'collapsedFolders' , JSON.stringify(collapsedFolders)); if ( onDashboard ) {
storageName = 'collapsedDashFolders' + onDashboard.value;
} else {
storageName = 'collapsedFolders';
}
localStorage.setItem( storageName , JSON.stringify(collapsedFolders));
} }
}); });
collapseElement.addEventListener('shown.bs.collapse', () => { collapseElement.addEventListener('shown.bs.collapse', () => {
let storageName;
// Remove the folderCard ID from local storage when expanded // Remove the folderCard ID from local storage when expanded
const index = collapsedFolders.indexOf(folderCardId); const index = collapsedFolders.indexOf(folderCardId);
if (index > -1) { if (index > -1) {
collapsedFolders.splice(index, 1); collapsedFolders.splice(index, 1);
localStorage.setItem( 'collapsedFolders' , JSON.stringify(collapsedFolders)); if ( onDashboard ) {
storageName = 'collapsedDashFolders' + onDashboard.value;
} else {
storageName = 'collapsedFolders';
}
localStorage.setItem( storageName , JSON.stringify(collapsedFolders));
} }
}); });
}); });
@ -180,10 +199,11 @@ function updateDashLinkOrder() {
} }
function loadDashLinkOrder() { function loadDashLinkOrder() {
const onDashboard = document.getElementById("dash_id");
const storedOrder = localStorage.getItem("manageFolderOrder"); // Get the saved order const storedOrder = localStorage.getItem("manageFolderOrder"); // Get the saved order
const bookmarkSort = document.getElementById("bookmarkSort"); const bookmarkSort = document.getElementById("bookmarkSort");
if (!storedOrder || !bookmarkSort) return; // Exit if no saved order or no container if ( onDashboard || !storedOrder || !bookmarkSort ) return; // Exit if no saved order or no container
const orderArray = storedOrder.split(","); // Convert the saved order into an array const orderArray = storedOrder.split(","); // Convert the saved order into an array
const bookmarkCards = Array.from(document.querySelectorAll("#bookmarkSort .bookmark-card")); const bookmarkCards = Array.from(document.querySelectorAll("#bookmarkSort .bookmark-card"));

View File

@ -21,14 +21,14 @@
<div class="mb-3 row"> <div class="mb-3 row">
<label for="title" class="col-lg-5 col-form-label text-end">Description:</label> <label for="description" class="col-lg-5 col-form-label text-end">Description:</label>
<div class="col-lg-3"> <div class="col-lg-3">
<textarea class="form-control" name="description" maxlength="2000" rows="10" cols="50" id="description"></textarea> <textarea class="form-control" name="description" maxlength="2000" rows="10" cols="50" id="description"></textarea>
</div> </div>
</div> </div>
{folderSelect} {folderSelect}
<div class="mb-3 row"> <div class="mb-3 row">
<label for="title" class="col-lg-5 col-form-label text-end">Privacy</label> <label for="privacy" class="col-lg-5 col-form-label text-end">Privacy</label>
<div class="col-lg-3"> <div class="col-lg-3">
<select id="privacy" name="privacy" class="form-control"> <select id="privacy" name="privacy" class="form-control">
<option value="private">Private</option> <option value="private">Private</option>
@ -37,7 +37,7 @@
</div> </div>
</div> </div>
<div class="mb-3 row"> <div class="mb-3 row">
<label for="title" class="col-lg-5 col-form-label text-end">Color</label> <label for="color" class="col-lg-5 col-form-label text-end">Color</label>
<div class="col-lg-3" id="colorContainer"> <div class="col-lg-3" id="colorContainer">
{colorSelect} {colorSelect}
</div> </div>

View File

@ -5,6 +5,7 @@
<form method="post"> <form method="post">
<fieldset> <fieldset>
<input type="hidden" name="token" value="{TOKEN}"> <input type="hidden" name="token" value="{TOKEN}">
<input type="hidden" name="dash_id" id="dash_id" value="{ID}">
<input type="hidden" name="link_order" value="{link_order}" id="dashLinkOrder"> <input type="hidden" name="link_order" value="{link_order}" id="dashLinkOrder">
<div class="row g-3" id="bookmarkSort"> <div class="row g-3" id="bookmarkSort">
{folderPanels} {folderPanels}

View File

@ -8,30 +8,74 @@
Since you can't use extensions on mobile, you can log in from your mobile device and visit this page. Since you can't use extensions on mobile, you can log in from your mobile device and visit this page.
</p> </p>
<p> <p>
Below is a code snippet that you can copy, then create a new bookmark on your mobile device as you would normally. Below is a code snippet that you can copy. Copy the code, then create a new bookmark on your mobile device as you would normally.
Before saving the bookmark, change the name to something simple like "ATB Bookmarker" and paste thiis code as the url. Before saving the bookmark, change the name to something simple like "ATB Bookmarker" and paste this code as the url.
</p> </p>
<p> <p>
Once you have the bookmarklet saved, you can simply go to your mobile bookmarks while on a page and this snippet will grab the info you need and add it to you account. Once you have the bookmarklet saved, you can simply go to your mobile bookmarks while on a page and this snippet will grab the info you need and add it to you account.
</p> </p>
</div> </div>
<div class="context-second-bg p-4 border m-4">
<form method="post">
<fieldset>
<!-- Privacy -->
<div class="d-flex align-items-center border p-2 rounded mb-3 context-main-bg">
<div class="me-3 fw-bold">Default privacy:</div>
<div class=""> <div class="">
<select id="privacy" name="privacy" class="form-control">
<option value="private">Private</option>
<option value="public">Public</option>
</select>
</div>
</div>
<!-- Description -->
<div class="d-flex align-items-center border p-2 rounded mb-3 context-main-bg">
<div class="me-3 fw-bold">Ask for Description:</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="includeDescription" name="includeDescription"{editModeSwitch_IS_CHECKED}>
<label class="form-check-label" for="includeDescription"></label>
</div>
</div>
<!-- Color -->
<div class="d-flex align-items-center border p-2 rounded mb-3 context-main-bg">
<div class="me-3 fw-bold">Include a default color:</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="includeColor" name="includeColor"{editModeSwitch_IS_CHECKED}>
<label class="form-check-label" for="includeColor"></label>
</div>
<div class="">
{colorSelect}
</div>
</div>
<!-- Folder -->
<div class="d-flex align-items-center border p-2 rounded mb-3 context-main-bg">
<div class="me-3 fw-bold">Include a default folder:</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="includeFolder" name="includeFolder"{editModeSwitch_IS_CHECKED}>
<label class="form-check-label" for="includeFolder"></label>
</div>
<div class="">
{folderSelect}
</div>
</div>
</fieldset>
<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 atb-green-outline my-2">Refresh Code</button>
</div>
</form>
</div>
<div class="context-second-bg p-4">
<pre lang="javascript"> <pre lang="javascript">
javascript:(function() { javascript:(function() {
const apiKey = localStorage.getItem('notAnAuthToken'); const apiKey = "{BK_API_KEY}";
const apiUrl = localStorage.getItem('api_url'); const apiUrl = "{BK_API_URL}";
const name = prompt("Enter a name for the bookmark:");
const notes = prompt("Enter any notes (optional):");
const color = prompt("Enter a color (optional):");
const privacy = prompt("Enter privacy level (e.g., public/private):");
const folder = prompt("Enter a folder (optional):");
const url = window.location.href; const url = window.location.href;
const name = prompt("Enter a name for the bookmark:");
if (!apiKey) { {BK_JS_NOTES}
alert("You must sign in to obtain an auth token."); {BK_JS_COLOR}
return; {BK_JS_PRIVACY}
} {BK_JS_FOLDER}
if (!name) { if (!name) {
alert("Name is required."); alert("Name is required.");

View File

@ -0,0 +1,19 @@
<div class="col-10 offset-md-1 context-main-bg p-4 my-5">
<legend class="text-center">Mobile Bookmarklet</legend>
<hr>
<div class="col-8 offset-2 ">
<div class="h5">
<p>
You can quickly and easily add bookmarks to your AllTheBookmarks account from any mobile device.
Since you can't use extensions on mobile, you can log in from your mobile device and visit this page.
</p>
<p>
Once logged in, there will be a code snippet that you can copy. Copy the code, then create a new bookmark on your mobile device as you would normally.
Before saving the bookmark, change the name to something simple like "ATB Bookmarker" and paste this code as the url.
</p>
<p>
Once you have the bookmarklet saved, you can simply go to your mobile bookmarks while on a page and this snippet will grab the info you need and add it to you account.
</p>
</div>
</div>
</div>

View File

@ -0,0 +1,48 @@
<section class="container my-5">
<div class="card shadow-sm">
<div class="card-body context-main-bg">
<h2 class="card-title text-center mb-4">How to Export bookmarks in Edge</h2>
{tutorialCrumbs}
<hr>
<p class="card-text">
Exporting your bookmarks is lightning fast and simple inside of Edge.
</p>
<h5 class="mt-4">Step 1: open the main application menu </h5>
<p class="card-text">
Simply left-click these three dots on the right side of your main toolbar.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/settings.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon">
</div>
<h5>Step 2: Open the Bookmark Menu</h5>
<p class="card-text">
In the list, select <strong>Favorites</strong> and another menu should appear.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/favorites.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<h5>Step 3: Open the Favorites Options</h5>
<p class="card-text">
In this menu, on the top right click the three dots for a final dropdown.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/bookmark-options.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<h5>Step 4: Export your bookmarks</h5>
<p class="card-text">
From this list, select <strong>Export favorites</strong> and you should be taken to the import page.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/export.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<p class="card-text">
Finally, save the <strong>.html</strong> file somewhere easy to access when you use the <a href="/bookmarks/import" class="text-decoration-none atb-green">imports</a> feature.
</p>
</div>
</div>
</section>

View File

@ -0,0 +1,64 @@
<section class="container my-5">
<div class="card shadow-sm">
<div class="card-body context-main-bg">
<h2 class="card-title text-center mb-4">How to Import bookmarks in Edge</h2>
{tutorialCrumbs}
<hr>
<p class="card-text">
Importing your bookmarks from AllTheBookmarks is lightning fast and simple inside of Edge.
</p>
<h5 class="mt-4">Step 1: open the main application menu </h5>
<p class="card-text">
Simply left-click these three dots on the right side of your main toolbar.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/settings.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon">
</div>
<h5>Step 2: Open the Bookmark Menu</h5>
<p class="card-text">
In the list, select <strong>Favorites</strong> and another menu should appear.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/favorites.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<h5>Step 3: Open the Favorites Options</h5>
<p class="card-text">
In this menu, on the top right click the three dots for a final dropdown.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/bookmark-options.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<h5>Step 4: Open the Import Page</h5>
<p class="card-text">
From this list, select <strong>Import favorites</strong> and you should be taken to the import page.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/import.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<h5>Step 5: Open the Importer</h5>
<p class="card-text">
On this page, under the title <strong>Import from other browsers</strong> click the button labeled <strong>Choose what to import</strong>.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/import-menu.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<h5>Step 6: Begin Importing</h5>
<p class="card-text">
Under <strong>Import from</strong> make sure to select <strong>Favorites or bookmarks HTML file</strong>.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/import-select.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<p class="card-text">
Finally, select the <strong>.html</strong> file you saved when using the <a href="/bookmarks/export" class="text-decoration-none atb-green">exports</a> feature.
</p>
</div>
</div>
</section>

View File

@ -0,0 +1,36 @@
<section class="container my-5">
<div class="card shadow-sm">
<div class="card-body context-main-bg">
<h2 class="card-title text-center mb-4">How to pin the extension to your toolbar in Edge</h2>
{tutorialCrumbs}
<hr>
<div class="alert alert-success w-100 text-center" role="alert">
If you have not already installed the AllTheBookmarks Edge extension, you can find it in the
<a href="#" class="text-decoration-none text-primary" target="_blank">Microsoft Store</a>.
</div>
<p class="card-text">
Once installed, you can add the extension to your Edge toolbar with just a few easy steps.
</p>
<h5 class="mt-4">Step 1: Find the extensions icon on your toolbar.</h5>
<p class="card-text">
It should look like this:
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/extensions-icon.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon">
</div>
<h5>Step 2: Left click this icon and a list of your currently installed extensions should show.</h5>
<p class="card-text">
Next to AllTheBookmarks, on the right you should see an eye icon, click this.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/pin.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<p class="card-text">
Once pinned, you should see the AllTheBookmarks icon on the toolbar.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/pinned.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,37 @@
<section class="container my-5">
<div class="card shadow-sm">
<div class="card-body context-main-bg">
<h2 class="card-title text-center mb-4">How to Access Extension Settings in Edge</h2>
{tutorialCrumbs}
<hr>
<div class="alert alert-success w-100 text-center" role="alert">
If you have not already installed the AllTheBookmarks Edge extension, you can find it in the
<a href="#" class="text-decoration-none text-primary" target="_blank">Microsoft Store</a>.
</div>
<p class="card-text">
With Edge, accessing the extension settings couldn't be easier.
</p>
<h5 class="mt-4">Step 1: Locate the Extension Icon</h5>
<p class="card-text">
Simply locate the AllTheBookmarks icon on your toolbar and right-click the icon.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/options.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon">
</div>
<h5>Step 2: Open the Options</h5>
<p class="card-text">
From the dropdown menu that appears, select <strong>Extension Options</strong>. You should be greeted by a settings page very similar to the one below.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/edge/settings-page.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<p class="card-text">
(If you need help pinning the extension to your toolbar for easy access, we have a separate
<a href="/tutorials/edge/pin" class="text-decoration-none atb-green">tutorial</a> for that.)
</p>
</div>
</div>
</section>

View File

@ -5,7 +5,7 @@
{tutorialCrumbs} {tutorialCrumbs}
<hr> <hr>
<p class="card-text"> <p class="card-text">
Exporting your bookmarks is lightning fast and simple inside of firefox. Exporting your bookmarks is lightning fast and simple inside of Firefox.
</p> </p>
<h5 class="mt-4">Step 1: open the main application menu </h5> <h5 class="mt-4">Step 1: open the main application menu </h5>
@ -32,7 +32,7 @@
<img src="/app/plugins/bookmarks/images/firefox/manage-bookmarks.png" class="img-fluid rounded mb-4" alt="Extension settings page"> <img src="/app/plugins/bookmarks/images/firefox/manage-bookmarks.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div> </div>
<h5>Step 3: Open the Import Menu</h5> <h5>Step 4: Open the Export Menu</h5>
<p class="card-text"> <p class="card-text">
On this page, near the top left, you should see a button that says <strong>Import and Backup</strong>. Click this button and another menu should appear. On this page, near the top left, you should see a button that says <strong>Import and Backup</strong>. Click this button and another menu should appear.
</p> </p>

View File

@ -20,7 +20,7 @@
</div> </div>
<h5>Step 2: Left click this icon and a list of your currently installed extensions should show.</h5> <h5>Step 2: Left click this icon and a list of your currently installed extensions should show.</h5>
<p class="card-text"> <p class="card-text">
Next to AllTheBookmarks, on the right you should see a pin icon, click this. Next to AllTheBookmarks, on the right you should see a settings icon, click this then select <strong>Pin to Toolbar</strong>
</p> </p>
<div class="text-center"> <div class="text-center">
<img src="/app/plugins/bookmarks/images/firefox/pin.png" class="img-fluid rounded mb-4" alt="Extension settings page"> <img src="/app/plugins/bookmarks/images/firefox/pin.png" class="img-fluid rounded mb-4" alt="Extension settings page">

View File

@ -9,22 +9,40 @@
<a href="/firefox" class="text-decoration-none text-primary" target="_blank">Firefox Store</a>. <a href="/firefox" class="text-decoration-none text-primary" target="_blank">Firefox Store</a>.
</div> </div>
<p class="card-text"> <p class="card-text">
With Firefox, accessing the extension settings couldn't be easier. With Firefox, accessing the extension settings is a breeze.
</p> </p>
<h5 class="mt-4">Step 1: Locate the Extension Icon</h5> <h5 class="mt-4">Step 1: Locate the Extension Icon</h5>
<p class="card-text"> <p class="card-text">
Simply locate the AllTheBookmarks icon on your toolbar and right-click the icon. Simply locate the AllTheBookmarks icon on your toolbar and right-click the icon.
</p> </p>
<div class="text-center"> <div class="text-center">
<img src="/app/plugins/bookmarks/images/firefox/options.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon"> <img src="/app/plugins/bookmarks/images/firefox/icon.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon">
</div> </div>
<h5>Step 2: Open the Options</h5>
<h5>Step 2: Open the Add-on manage page</h5>
<p class="card-text"> <p class="card-text">
From the dropdown menu that appears, select <strong>Options</strong>. You should be greeted by a settings page very similar to the one below. From the dropdown menu that appears, select <strong>Manage Extension</strong>.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/firefox/manage.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<h5>Step 3: Open the extension setting page</h5>
<p class="card-text">
The extension management page should appear similar to this. In the top right, click these three dots and select <strong>Options</strong>.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/firefox/manage-select.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<p class="card-text">
You should be greeted by a settings page very similar to the one below.
</p> </p>
<div class="text-center"> <div class="text-center">
<img src="/app/plugins/bookmarks/images/firefox/settings-page.png" class="img-fluid rounded mb-4" alt="Extension settings page"> <img src="/app/plugins/bookmarks/images/firefox/settings-page.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div> </div>
<p class="card-text"> <p class="card-text">
(If you need help pinning the extension to your toolbar for easy access, we have a separate (If you need help pinning the extension to your toolbar for easy access, we have a separate
<a href="/tutorials/firefox/pin" class="text-decoration-none atb-green">tutorial</a> for that.) <a href="/tutorials/firefox/pin" class="text-decoration-none atb-green">tutorial</a> for that.)

View File

@ -0,0 +1,40 @@
<section class="container my-5">
<div class="card shadow-sm">
<div class="card-body context-main-bg">
<h2 class="card-title text-center mb-4">How to Export bookmarks in Opera</h2>
{tutorialCrumbs}
<hr>
<p class="card-text">
Exporting your bookmarks is lightning fast and simple inside of Opera.
</p>
<h5 class="mt-4">Step 1: open the main application menu </h5>
<p class="card-text">
Simply left-click these three dots in the bottom left of the browser window.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/sidebar.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon">
</div>
<h5>Step 2: Open the Bookmark Menu</h5>
<p class="card-text">
In the list, click the icon next to <strong>Bookmarks</strong> and another window should open.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/bookmarks.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<h5>Step 4: Export your bookmarks</h5>
<p class="card-text">
In the bottom right of the page, click <strong>Import / Export</strong> and select <strong>Export Bookmarks</strong>.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/export.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<p class="card-text">
Finally, save the <strong>.html</strong> file somewhere easy to access when you use the <a href="/bookmarks/import" class="text-decoration-none atb-green">imports</a> feature.
</p>
</div>
</div>
</section>

View File

@ -0,0 +1,48 @@
<section class="container my-5">
<div class="card shadow-sm">
<div class="card-body context-main-bg">
<h2 class="card-title text-center mb-4">How to Import bookmarks in Opera</h2>
{tutorialCrumbs}
<hr>
<p class="card-text">
Importing your bookmarks from AllTheBookmarks is lightning fast and simple inside of Opera.
</p>
<h5 class="mt-4">Step 1: open the main application menu </h5>
<p class="card-text">
Simply left-click these three dots in the bottom left of the browser window.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/sidebar.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon">
</div>
<h5>Step 2: Open the Bookmark Menu</h5>
<p class="card-text">
In the list, click the icon next to <strong>Bookmarks</strong> and another window should open.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/bookmarks.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<h5>Step 3: Open the Import Page</h5>
<p class="card-text">
In the bottom right of the page, click <strong>Import / Export</strong> and select <strong>Import Bookmarks</strong> and you should be taken to the import page.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/import.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<h5>Step 4: Begin Importing</h5>
<p class="card-text">
In this menu, click the dropdown and make sure to select <strong>Bookmarks HTML file</strong>.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/import-select.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<p class="card-text">
Finally, select the <strong>.html</strong> file you saved when using the <a href="/bookmarks/export" class="text-decoration-none atb-green">exports</a> feature.
</p>
</div>
</div>
</section>

View File

@ -0,0 +1,46 @@
<section class="container my-5">
<div class="card shadow-sm">
<div class="card-body context-main-bg">
<h2 class="card-title text-center mb-4">How to pin the extension to your toolbar in Opera</h2>
{tutorialCrumbs}
<hr>
<div class="alert alert-success w-100 text-center" role="alert">
If you have not already installed the AllTheBookmarks Opera extension, you can find it in the
<a href="#" class="text-decoration-none text-primary" target="_blank">Opera Store</a>.
</div>
<p class="card-text">
Once installed, you can add the extension to your Opera toolbar with just a few easy steps.
</p>
<h5 class="mt-4">Step 1: Find the extensions icon on your toolbar.</h5>
<p class="card-text">
It should look like this:
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/extensions.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon">
</div>
<h5 class="mt-4">Step 0: Reveal the extensions toolbar. ( This step may not be required for all users ).</h5>
<p class="card-text">
If you find that you are having trouble locating the extensions icon, double check that the additional toolbar is expanded.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/toolbar.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon">
</div>
<h5>Step 2: Left click this icon and a list of your currently installed extensions should show.</h5>
<p class="card-text">
Next to AllTheBookmarks, on the right you should see an eye icon, click this.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/pin.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<p class="card-text">
Once pinned, you should see the AllTheBookmarks icon on the toolbar.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/pinned.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,37 @@
<section class="container my-5">
<div class="card shadow-sm">
<div class="card-body context-main-bg">
<h2 class="card-title text-center mb-4">How to Access Extension Settings in Opera</h2>
{tutorialCrumbs}
<hr>
<div class="alert alert-success w-100 text-center" role="alert">
If you have not already installed the AllTheBookmarks Opera extension, you can find it in the
<a href="#" class="text-decoration-none text-primary" target="_blank">Opera Store</a>.
</div>
<p class="card-text">
With Opera, accessing the extension settings couldn't be easier.
</p>
<h5 class="mt-4">Step 1: Locate the Extension Icon</h5>
<p class="card-text">
Simply locate the AllTheBookmarks icon on your toolbar and right-click the icon.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/options.png" class="img-fluid rounded mb-4" alt="Right-clicking on the extension icon">
</div>
<h5>Step 2: Open the Options</h5>
<p class="card-text">
From the dropdown menu that appears, select <strong>Options</strong>. You should be greeted by a settings page very similar to the one below.
</p>
<div class="text-center">
<img src="/app/plugins/bookmarks/images/opera/settings-page.png" class="img-fluid rounded mb-4" alt="Extension settings page">
</div>
<p class="card-text">
(If you need help pinning the extension to your toolbar for easy access, we have a separate
<a href="/tutorials/opera/pin" class="text-decoration-none atb-green">tutorial</a> for that.)
</p>
</div>
</div>
</section>

View File

@ -8,7 +8,7 @@
id="comment" id="comment"
placeholder="Write your comment here..."></textarea> placeholder="Write your comment here..."></textarea>
</div> </div>
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary mb-3">Comment</button> <button name="submit" value="submit" type="submit" class="btn btn-lg atb-green-bg mb-3">Comment</button>
<input type="hidden" name="token" value="{TOKEN}"> <input type="hidden" name="token" value="{TOKEN}">
<input type="hidden" name="contentId" value="{CONTENT_ID}"> <input type="hidden" name="contentId" value="{CONTENT_ID}">
</form> </form>

View File

@ -28,7 +28,7 @@
</li> </li>
{/LOOP} {/LOOP}
{ALT} {ALT}
<li class="list-group-item"> <li class="list-group-item context-second-bg context-main mb-2">
<div class="text-center"> <div class="text-center">
<p class="mb-0">Be the first to comment.</p> <p class="mb-0">Be the first to comment.</p>
</div> </div>

View File

@ -33,7 +33,7 @@ class Reviews extends Controller {
public function __construct() { public function __construct() {
self::$title = 'Reviews - {SITENAME}'; self::$title = 'Reviews - {SITENAME}';
self::$pageDescription = 'On this page you can submit a reviews for a product.'; self::$pageDescription = 'This page allows you to add a new product review or update your existing reviews.';
if ( ! App::$isLoggedIn ) { if ( ! App::$isLoggedIn ) {
Session::flash( 'notice', 'You must be logged in to review products.' ); Session::flash( 'notice', 'You must be logged in to review products.' );
@ -48,18 +48,21 @@ class Reviews extends Controller {
} }
public function index() { public function index() {
$reviews = Views::simpleView( 'reviews.list', self::$reviews->byUser() ); Views::view( 'reviews.list', self::$reviews->byUser() );
Components::set( 'reviews', $reviews );
Views::view( 'reviews.list' );
} }
public function review( $slug = null ) { public function review( $slug = null ) {
$category = self::$categories->findBySlug( $slug ); $category = self::$categories->findBySlug( $slug );
if ( ! $category ) { if ( ! $category ) {
$categories = self::$categories->simpleUnreviewed();
if ( empty( $categories ) ) {
Session::flash( 'notice', 'There are no additional products to review at this time.' );
Redirect::to( 'reviews/index' );
}
$selectedCategory = '0'; $selectedCategory = '0';
$reviewCategorySelect = HoudiniForms::getSelectHtml( $reviewCategorySelect = HoudiniForms::getSelectHtml(
'review_category_id', 'review_category_id',
self::$categories->simple(), $categories,
$selectedCategory, $selectedCategory,
); );
Components::set( 'reviewCategorySelect', $reviewCategorySelect ); Components::set( 'reviewCategorySelect', $reviewCategorySelect );
@ -78,12 +81,76 @@ class Reviews extends Controller {
return Views::view( 'reviews.create' ); return Views::view( 'reviews.create' );
} }
$result = self::$reviews->create( Input::post('title'), Input::post('rating'), Input::post('review'), Input::post('review_category_id') ); $result = self::$reviews->create( Input::post('title'), Input::post('rating'), Input::post('review'), Input::post('review_category_id') );
if ( true === $result ) { if ( ! empty( $result ) ) {
Session::flash( 'success', 'Your review has been received.' ); Session::flash( 'success', 'Your review has been received.' );
Redirect::to( 'home/index' ); Redirect::to( 'reviews/index' );
} else { } else {
Issues::add( 'error', 'There was an unresolved error while submitting your review.' ); Issues::add( 'error', 'There was an unresolved error while submitting your review.' );
return Views::view( 'reviews.create' ); return Views::view( 'reviews.create' );
} }
} }
public function view( $id = null ) {
$review = $this->confirmOwner( $id );
Views::view( 'reviews.view', $review );
}
public function edit( $id = null ) {
$review = $this->confirmOwner( $id );
if ( ! Input::exists( 'submit' ) ) {
return Views::view( 'reviews.edit', $review );
}
if ( ! Forms::check( 'reviewEditPublic' ) ) {
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
return Views::view( 'reviews.edit', $review );
}
$result = self::$reviews->update(
$id,
Input::post('title'),
Input::post('rating'),
Input::post('review')
);
if ( $result ) {
Session::flash( 'success', 'Your review has been updated.' );
return Redirect::to( 'reviews/index' );
}
Issues::add( 'error', [ 'There was an error with your request.' => Check::userErrors() ] );
Views::view( 'reviews.edit', $review );
}
public function delete( $id = null ) {
$this->confirmOwner( $id );
if ( self::$reviews->delete( $id ) ) {
Session::flash( 'success', 'Review deleted' );
} else {
Session::flash( 'error', 'There was an error with your request.' );
}
return Redirect::to( 'reviews/index' );
}
private function confirmOwner( $id ) {
if ( ! App::$isLoggedIn ) {
Session::flash( 'error', 'You must be logged in to review products.' );
return Redirect::home();
}
if ( !Check::id( $id ) ) {
Session::flash( 'error', 'Unknown Review' );
return Redirect::to( 'reviews/index' );
}
$review = self::$reviews->findById( $id );
if ( empty( $review ) ) {
Session::flash( 'error', 'Unknown Review' );
return Redirect::to( 'reviews/index' );
}
if ( App::$activeUser->ID != $review->createdBy ) {
Session::flash( 'error', 'Unknown Review' );
return Redirect::to( 'reviews/index' );
}
return $review;
}
} }

View File

@ -25,6 +25,7 @@ class ReviewForms extends Forms {
self::addHandler( 'editReview', __CLASS__, 'editReview' ); self::addHandler( 'editReview', __CLASS__, 'editReview' );
self::addHandler( 'categoryCreate', __CLASS__, 'categoryCreate' ); self::addHandler( 'categoryCreate', __CLASS__, 'categoryCreate' );
self::addHandler( 'categoryEdit', __CLASS__, 'categoryEdit' ); self::addHandler( 'categoryEdit', __CLASS__, 'categoryEdit' );
self::addHandler( 'reviewEditPublic', __CLASS__, 'reviewEditPublic' );
} }
/** /**
@ -88,6 +89,21 @@ class ReviewForms extends Forms {
} }
return true; return true;
} }
public static function reviewEditPublic() {
if ( ! Input::exists( 'review' ) ) {
Check::addUserError( 'You must provide a review.' );
return false;
}
if ( ! Input::exists( 'title' ) ) {
Check::addUserError( 'You must provide a title.' );
return false;
}
if ( ! Input::exists( 'rating' ) ) {
Check::addUserError( 'You must provide a rating.' );
return false;
}
return true;
}
} }
new ReviewForms; new ReviewForms;

View File

@ -62,21 +62,19 @@ class Review extends DatabaseModel {
return self::$db->lastId(); return self::$db->lastId();
} }
public function update( $id, $title, $rating, $review, $review_category_id = '0' ) { public function update( $id, $title, $rating, $review, $review_category_id = null ) {
if ( ! $this->plugin->checkEnabled() ) { if ( ! $this->plugin->checkEnabled() ) {
Debug::info( 'Reviews are disabled in the config.' ); Debug::info( 'Reviews are disabled in the config.' );
return false; return false;
} }
if ( !Check::dataTitle( $slug ) ) {
Debug::info( 'Review Categories: illegal title.' );
return false;
}
$fields = [ $fields = [
'title' => $title, 'title' => $title,
'rating' => $rating, 'rating' => $rating,
'review' => $review, 'review' => $review
'review_category_id' => $review_category_id,
]; ];
if ( ! empty( $review_category_id ) ) {
$fields['review_category_id'] = $review_category_id;
}
if ( !self::$db->update( $this->tableName, $id, $fields ) ) { if ( !self::$db->update( $this->tableName, $id, $fields ) ) {
new CustomException( 'reviewUpdate' ); new CustomException( 'reviewUpdate' );
Debug::error( "Review: $id not updated: $fields" ); Debug::error( "Review: $id not updated: $fields" );
@ -133,6 +131,23 @@ class Review extends DatabaseModel {
return $this->filter( $reviews->results() ); return $this->filter( $reviews->results() );
} }
public function reviewedCategoriesByUser() {
$whereClause = ['createdBy', '=', App::$activeUser->ID];
$reviews = self::$db->action( 'SELECT ID,review_category_id', $this->tableName, $whereClause );
if ( !$reviews->count() ) {
Debug::info( 'No Reviews found.' );
return false;
}
$categoryIds = [];
foreach ($reviews->results() as $key => $result) {
if ( ! in_array( $result->review_category_id, $categoryIds ) ) {
$categoryIds[] = $result->review_category_id;
}
}
return $categoryIds;
}
public function byCategory( $id, $limit = null ) { public function byCategory( $id, $limit = null ) {
$whereClause = [ 'review_category_id', '=', $id ]; $whereClause = [ 'review_category_id', '=', $id ];
if ( empty( $limit ) ) { if ( empty( $limit ) ) {

View File

@ -17,6 +17,7 @@ use TheTempusProject\Bedrock\Functions\Check;
use TheTempusProject\Canary\Bin\Canary as Debug; use TheTempusProject\Canary\Bin\Canary as Debug;
use TheTempusProject\Classes\DatabaseModel; use TheTempusProject\Classes\DatabaseModel;
use TheTempusProject\Plugins\Reviews as Plugin; use TheTempusProject\Plugins\Reviews as Plugin;
use TheTempusProject\Models\Review;
use TheTempusProject\TheTempusProject as App; use TheTempusProject\TheTempusProject as App;
use TheTempusProject\Canary\Classes\CustomException; use TheTempusProject\Canary\Classes\CustomException;
@ -109,4 +110,29 @@ class ReviewCategory extends DatabaseModel {
} }
return $out; return $out;
} }
public function simpleUnreviewed() {
$categories = self::$db->get( $this->tableName, '*' );
if ( !$categories->count() ) {
Debug::warn( 'Could not find any categories' );
return false;
}
$categories = $categories->results();
$review = new Review;
$reviews = $review->reviewedCategoriesByUser();
$out = [];
foreach ( $categories as &$category ) {
if ( ! empty( $reviews) && in_array( $category->ID, $reviews ) ) {
continue;
}
$out[ $category->name ] = $category->ID;
}
return $out;
}
} }

View File

@ -1,35 +1,42 @@
<div class="container col-md-4 col-lg-4"> <div class="container py-4">
<div class="row"> <div class="row justify-content-center">
<div class="panel panel-primary"> <div class="col-md-8">
<div class="panel-heading"> {ADMIN_BREADCRUMBS}
<h3 class="panel-title">Review Category</h3> <div class="card shadow">
<!-- Card Header -->
<div class="card-header text-center bg-dark text-white">
<h3 class="card-title mb-0">Review Category: {name}</h3>
</div> </div>
<div class="panel-body">
<div class="row"> <!-- Card Body -->
<div class=""> <div class="card-body">
<table class="table table-user-primary"> <div class="row align-items-center">
<!-- Details -->
<table class="table table-borderless">
<tbody> <tbody>
<tr> <tr>
<td align="left" width="200"><b>Name</b></td> <th scope="row">Name:</th>
<td align="right">{name}</td> <td>{name}</td>
</tr> </tr>
<tr> <tr>
<td><b>Slug</b></td> <th scope="row">Slug:</th>
<td align="right">{slug}</td> <td>{slug}</td>
</tr> </tr>
<tr> <tr>
<td><b>Created</b></td> <th scope="row">Created:</th>
<td align="right">{DTC}{createdAt}{/DTC}</td> <td>{DTC}{createdAt}{/DTC}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
</div>
<div class="panel-footer"> <!-- Admin Controls -->
<div class="card-footer text-center">
<a href="{ROOT_URL}admin/reviews/categoryEdit/{ID}" class="btn btn-md btn-warning" role="button"><i class="fa fa-fw fa-pencil"></i></a> <a href="{ROOT_URL}admin/reviews/categoryEdit/{ID}" class="btn btn-md btn-warning" role="button"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{ROOT_URL}admin/reviews/categoryDelete/{ID}" class="btn btn-md btn-danger" role="button"><i class="fa fa-fw fa-trash"></i></a> <a href="{ROOT_URL}admin/reviews/categoryDelete/{ID}" class="btn btn-md btn-danger" role="button"><i class="fa fa-fw fa-trash"></i></a>
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>

View File

@ -1,22 +1,35 @@
<legend>Create Review Category</legend> <div class="context-main-bg context-main p-3">
<form action="" method="post" class="form-horizontal"> <legend class="text-center">Add Review Category</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form method="post">
<fieldset>
<!-- Name -->
<div class="mb-3 row">
<label for="name" class="col-lg-6 col-form-label text-end">Name:</label>
<div class="col-lg-2">
<input type="text" class="form-control" name="name" id="name" required>
</div>
</div>
<!-- Slug -->
<div class="mb-3 row">
<label for="slug" class="col-lg-6 col-form-label text-end">Slug:</label>
<div class="col-lg-2">
<input type="text" class="form-control" name="slug" id="slug" aria-describedby="slugHelp" required>
<small id="slugHelp" class="form-text text-muted">
Public links would be {ROOT_URL}reviews/YOUR_SLUG_HERE
</small>
</div>
</div>
<!-- Hidden Token -->
<input type="hidden" name="token" value="{TOKEN}"> <input type="hidden" name="token" value="{TOKEN}">
<div class="form-group">
<label for="name" class="col-lg-3 control-label">Name</label> <!-- Submit Button -->
<div class="col-lg-3"> <div class="text-center">
<input type="text" class="form-control" name="name" id="name"> <button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Create</button>
</div> </div>
</div> </fieldset>
<div class="form-group"> </form>
<label for="slug" class="col-lg-3 control-label">Slug (the public link for reviews would be {ROOT_URL}reviews/YOUR_SLUG_HERE)</label> </div>
<div class="col-lg-3">
<input type="text" class="form-control" name="slug" id="slug">
</div>
</div>
<div class="form-group">
<label for="submit" class="col-lg-3 control-label"></label>
<div class="col-lg-3">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block ">Submit</button>
</div>
</div>
</form>

View File

@ -1,22 +1,35 @@
<legend>Edit Review Category</legend> <div class="context-main-bg context-main p-3">
<form action="" method="post" class="form-horizontal"> <legend class="text-center">Edit Review Category</legend>
<hr>
{ADMIN_BREADCRUMBS}
<form method="post">
<fieldset>
<!-- Name -->
<div class="mb-3 row">
<label for="name" class="col-lg-6 col-form-label text-end">Name:</label>
<div class="col-lg-2">
<input type="text" class="form-control" name="name" id="name" value="{name}" required>
</div>
</div>
<!-- Slug -->
<div class="mb-3 row">
<label for="slug" class="col-lg-6 col-form-label text-end">Slug:</label>
<div class="col-lg-2">
<input type="text" class="form-control" name="slug" id="slug" aria-describedby="slugHelp" value="{slug}" required>
<small id="slugHelp" class="form-text text-muted">
Public links would be {ROOT_URL}reviews/YOUR_SLUG_HERE
</small>
</div>
</div>
<!-- Hidden Token -->
<input type="hidden" name="token" value="{TOKEN}"> <input type="hidden" name="token" value="{TOKEN}">
<div class="form-group">
<label for="name" class="col-lg-3 control-label">Name</label> <!-- Submit Button -->
<div class="col-lg-3"> <div class="text-center">
<input type="text" class="form-control" name="name" id="name" value="{name}"> <button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg">Create</button>
</div> </div>
</div> </fieldset>
<div class="form-group"> </form>
<label for="slug" class="col-lg-3 control-label">Slug (the public link for reviews would be {ROOT_URL}reviews/YOUR_SLUG_HERE)</label> </div>
<div class="col-lg-3">
<input type="text" class="form-control" name="slug" id="slug" value="{slug}">
</div>
</div>
<div class="form-group">
<label for="submit" class="col-lg-3 control-label"></label>
<div class="col-lg-3">
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block ">Submit</button>
</div>
</div>
</form>

View File

@ -1,28 +1,33 @@
<div class="container col-md-4 col-lg-4"> <div class="container py-4">
<div class="row"> <div class="row justify-content-center">
<div class="panel panel-primary"> <div class="col-md-8">
<div class="panel-heading"> {ADMIN_BREADCRUMBS}
<h3 class="panel-title">Review</h3> <div class="card shadow">
<!-- Card Header -->
<div class="card-header text-center bg-dark text-white">
<h3 class="card-title mb-0">Review: {title}</h3>
</div> </div>
<div class="panel-body">
<div class="row"> <!-- Card Body -->
<div class=""> <div class="card-body">
<table class="table table-user-primary"> <div class="row align-items-center">
<!-- Details -->
<table class="table table-borderless">
<tbody> <tbody>
<tr> <tr>
<td align="left" width="200"><b>Title</b></td> <th scope="row">Title:</th>
<td align="right">{title}</td> <td>{title}</td>
</tr> </tr>
<tr> <tr>
<td><b>Rating</b></td> <th scope="row">Rating:</th>
<td align="right">{rating}</td> <td>{rating}</td>
</tr> </tr>
<tr> <tr>
<td><b>Created</b></td> <th scope="row">Created:</th>
<td align="right">{DTC}{createdAt}{/DTC}</td> <td>{DTC}{createdAt}{/DTC}</td>
</tr> </tr>
<tr> <tr>
<td align="center" colspan="2"><b>Review</b></td> <th scope="row" colspan="2">Review:</th>
</tr> </tr>
<tr> <tr>
<td colspan="2">{review}</td> <td colspan="2">{review}</td>
@ -31,12 +36,14 @@
</table> </table>
</div> </div>
</div> </div>
</div>
<div class="panel-footer"> <!-- Admin Controls -->
<div class="card-footer text-center">
<a href="{ROOT_URL}admin/reviews/reviewApprove/{ID}" class="btn btn-sm btn-success" role="button"><i class="fa fa-fw fa-info-circle"></i></a> <a href="{ROOT_URL}admin/reviews/reviewApprove/{ID}" class="btn btn-sm btn-success" role="button"><i class="fa fa-fw fa-info-circle"></i></a>
<a href="{ROOT_URL}admin/reviews/reviewHide/{ID}" class="btn btn-sm btn-info" role="button"><i class="fa fa-fw fa-eye-closed"></i></a> <a href="{ROOT_URL}admin/reviews/reviewHide/{ID}" class="btn btn-sm btn-info" role="button"><i class="fa fa-fw fa-eye-closed"></i></a>
<a href="{ROOT_URL}admin/reviews/reviewDelete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="fa fa-fw fa-trash"></i></a> <a href="{ROOT_URL}admin/reviews/reviewDelete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="fa fa-fw fa-trash"></i></a>
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>

View File

@ -0,0 +1,49 @@
<div class="container py-4">
<div class="row justify-content-center">
<div class="col-md-8">
{ADMIN_BREADCRUMBS}
<div class="card shadow">
<!-- Card Header -->
<div class="card-header text-center bg-dark text-white">
<h3 class="card-title mb-0">Review: {title}</h3>
</div>
<!-- Card Body -->
<div class="card-body">
<div class="row align-items-center">
<!-- Details -->
<table class="table table-borderless">
<tbody>
<tr>
<th scope="row">Title:</th>
<td>{title}</td>
</tr>
<tr>
<th scope="row">Rating:</th>
<td>{rating}</td>
</tr>
<tr>
<th scope="row">Created:</th>
<td>{DTC}{createdAt}{/DTC}</td>
</tr>
<tr>
<th scope="row" colspan="2">Review:</th>
</tr>
<tr>
<td colspan="2">{review}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Admin Controls -->
<div class="card-footer text-center">
<a href="{ROOT_URL}admin/reviews/reviewApprove/{ID}" class="btn btn-sm btn-success" role="button"><i class="fa fa-fw fa-info-circle"></i></a>
<a href="{ROOT_URL}admin/reviews/reviewHide/{ID}" class="btn btn-sm btn-info" role="button"><i class="fa fa-fw fa-eye-closed"></i></a>
<a href="{ROOT_URL}admin/reviews/reviewDelete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="fa fa-fw fa-trash"></i></a>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,16 +1,18 @@
<div class="container mt-5"> <div class="col-8 mx-auto p-4 rounded shadow-sm mb-5 context-main-bg mt-4 container">
<h2>Write a Review</h2> <h2 class="text-center mb-4">Share a Review</h2>
<hr>
<p>We thank you for for taking the time to review our products. We do not edit or modify reviews in any way. You, as the customer have the ability to modify your own reviews.</p>
<p>We read each and every review. You and the admin team both have the ability to comment on reviews privately. Neither your reviews or comments will be publicly shared without your permission.</p>
<form id="review-form" method="post"> <form id="review-form" method="post">
<div class="form-group"> <!-- Review Category -->
<label for="review_category_id">Review Category:</label> <div class="mb-3">
<label for="review_category_id" class="form-label">Review Category:</label>
{reviewCategorySelect} {reviewCategorySelect}
</div> </div>
<div class="form-group">
<label for="title">Title your review:</label> <!-- Rating -->
<input type="text" class="form-control" id="title" name="title" required> <div class="mb-3">
</div> <label for="star-rating" class="form-label">Rating:</label>
<div class="form-group">
<label>Rating:</label>
<div class="star-rating"> <div class="star-rating">
<span class="fa fa-star" data-rating="1"></span> <span class="fa fa-star" data-rating="1"></span>
<span class="fa fa-star" data-rating="2"></span> <span class="fa fa-star" data-rating="2"></span>
@ -20,10 +22,31 @@
<input type="hidden" name="rating" class="rating-value" value="0"> <input type="hidden" name="rating" class="rating-value" value="0">
</div> </div>
</div> </div>
<div class="form-group">
<label for="review">Your Review:</label> <!-- Review Title -->
<textarea class="form-control" id="review" name="review" rows="5" required></textarea> <div class="mb-3">
<label for="title" class="form-label">Review Title:</label>
<input type="text" class="form-control" id="title" name="title" aria-describedby="titleHelp" required>
<small id="titleHelp" class="form-text text-muted">
No need to overthink it, something as simple as "My Review" is fine.
</small>
</div>
<!-- Review -->
<div class="mb-3">
<label for="review" class="form-label">Your Review:</label>
<textarea class="form-control" name="review" id="review" rows="5" maxlength="2000" aria-describedby="reviewHelp" required></textarea>
<small id="reviewHelp" class="form-text text-muted">
(max: 2000 characters)
</small>
</div>
<!-- Hidden Token -->
<input type="hidden" name="token" value="{TOKEN}">
<!-- Submit Button -->
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg atb-green-bg">Submit</button>
</div> </div>
<button name="submit" value="submit" type="submit" class="btn btn-lg btn-primary center-block ">Submit</button>
</form> </form>
</div> </div>

View File

@ -0,0 +1,44 @@
<div class="col-8 mx-auto p-4 rounded shadow-sm mb-5 context-main-bg mt-4 container">
<h2 class="text-center mb-4">Edit Your Review</h2>
<hr>
<form id="review-form" method="post">
<!-- Review Title -->
<div class="mb-3">
<label for="title" class="form-label">Review Title:</label>
<input type="text" class="form-control" id="title" name="title" aria-describedby="titleHelp" value="{title}" required>
<small id="titleHelp" class="form-text text-muted">
No need to overthink it, something as simple as "My Review" is fine.
</small>
</div>
<!-- Rating -->
<div class="mb-3">
<label for="star-rating" class="form-label">Rating:</label>
<div class="star-rating">
<span class="fa fa-star" data-rating="1"></span>
<span class="fa fa-star" data-rating="2"></span>
<span class="fa fa-star" data-rating="3"></span>
<span class="fa fa-star" data-rating="4"></span>
<span class="fa fa-star" data-rating="5"></span>
<input type="hidden" name="rating" class="rating-value" value="{rating}">
</div>
</div>
<!-- Review -->
<div class="mb-3">
<label for="review" class="form-label">Your Review:</label>
<textarea class="form-control" name="review" id="review" rows="5" maxlength="2000" aria-describedby="reviewHelp" required>{review}</textarea>
<small id="reviewHelp" class="form-text text-muted">
(max: 2000 characters)
</small>
</div>
<!-- Hidden Token -->
<input type="hidden" name="token" value="{TOKEN}">
<!-- Submit Button -->
<div class="text-center">
<button type="submit" name="submit" value="submit" class="btn btn-primary btn-lg atb-green-bg">Submit</button>
</div>
</form>
</div>

View File

@ -1,4 +1,4 @@
<div class="col-8 mx-auto p-4 rounded shadow-sm mb-5 context-main-bg mt-4 context-main"> <div class="col-8 mx-auto p-4 rounded shadow-sm mb-5 context-main-bg mt-4 context-main text-center">
<legend>Reviews</legend> <legend>Reviews</legend>
<hr> <hr>
<p> <p>
@ -7,12 +7,11 @@
<p> <p>
On this page you can find <strong>your</strong> reviews to see any responses or make edits. On this page you can find <strong>your</strong> reviews to see any responses or make edits.
</p> </p>
<table class="table context-main"> <table class="table context-main text-center">
<thead> <thead>
<tr> <tr>
<th style="width: 60%">Title</th> <th style="width: 60%">Title</th>
<th style="width: 10%">Rating</th> <th style="width: 20%">Rating</th>
<th style="width: 10%"></th>
<th style="width: 10%"></th> <th style="width: 10%"></th>
<th style="width: 10%"></th> <th style="width: 10%"></th>
</tr> </tr>
@ -20,19 +19,20 @@
<tbody> <tbody>
{LOOP} {LOOP}
<tr> <tr>
<td><a href="{ROOT_URL}admin/reviews/reviewView/{ID}" class="btn btn-sm btn-primary" role="button">{title}</a></td> <td><a href="{ROOT_URL}reviews/view/{ID}" class="text-decoration-none atb-green" role="button">{title}</a></td>
<td align="center">{rating}</td> <td>{rating}</td>
<td><a href="{ROOT_URL}admin/reviews/reviewEdit/{ID}" class="btn btn-sm btn-info" role="button"><i class="fa fa-fw fa-pencil"></i></a></td> <td><a href="{ROOT_URL}reviews/edit/{ID}" class="btn btn-sm btn-warning" role="button"><i class="fa fa-fw fa-pencil"></i></a></td>
<td><a href="{ROOT_URL}admin/reviews/reviewDelete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="fa fa-fw fa-trash"></i></a></td> <td><a href="{ROOT_URL}reviews/delete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="fa fa-fw fa-trash"></i></a></td>
</tr> </tr>
{/LOOP} {/LOOP}
{ALT} {ALT}
<tr> <tr>
<td align="center" colspan="6"> <td colspan="4">
No results to show. No results to show.
</td> </td>
</tr> </tr>
{/ALT} {/ALT}
</tbody> </tbody>
</table> </table>
<a href="{ROOT_URL}reviews/review" class="btn btn-md atb-green-bg" role="button">Add Review</a>
</div> </div>

View File

@ -0,0 +1,47 @@
<div class="container py-4">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<!-- Card Header -->
<div class="card-header text-center bg-dark text-white">
<h3 class="card-title mb-0">Review: {title}</h3>
</div>
<!-- Card Body -->
<div class="card-body">
<div class="row align-items-center">
<!-- Details -->
<table class="table table-borderless">
<tbody>
<tr>
<th scope="row">Title:</th>
<td>{title}</td>
</tr>
<tr>
<th scope="row">Rating:</th>
<td>{rating}</td>
</tr>
<tr>
<th scope="row">Created:</th>
<td>{DTC}{createdAt}{/DTC}</td>
</tr>
<tr>
<th scope="row" colspan="2">Review:</th>
</tr>
<tr>
<td colspan="2">{review}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Admin Controls -->
<div class="card-footer text-center">
<a href="{ROOT_URL}reviews/edit/{ID}" class="btn btn-sm btn-warning" role="button"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{ROOT_URL}reviews/delete/{ID}" class="btn btn-sm btn-danger" role="button"><i class="fa fa-fw fa-trash"></i></a>
</div>
</div>
</div>
</div>
</div>

View File

@ -41,7 +41,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 btn-primary btn-lg">Save</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>

View File

@ -41,8 +41,6 @@ class DefaultLoader extends Loader {
Components::set( 'JQUERY_CDN', self::JQUERY_CDN ); Components::set( 'JQUERY_CDN', self::JQUERY_CDN );
Components::set( 'FONT_AWESOME_URL', self::FONT_AWESOME_URL ); Components::set( 'FONT_AWESOME_URL', self::FONT_AWESOME_URL );
} }
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main.css">' );
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main-dark.css" id="dark-mode-stylesheet" disabled>' );
$this->addJs( '<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{ROOT_URL}app/js/main.js"></script>' ); $this->addJs( '<script language="JavaScript" crossorigin="anonymous" type="text/javascript" src="{ROOT_URL}app/js/main.js"></script>' );
Components::setIfNull( 'LOGO', Config::getValue( 'main/logo' ) ?? TP_DEFAULT_LOGO ); Components::setIfNull( 'LOGO', Config::getValue( 'main/logo' ) ?? TP_DEFAULT_LOGO );
Components::setIfNull( 'COPY', Views::simpleView( 'footer.copy') ); Components::setIfNull( 'COPY', Views::simpleView( 'footer.copy') );
@ -55,12 +53,21 @@ class DefaultLoader extends Loader {
* Top-Nav * Top-Nav
*/ */
if ( App::$isLoggedIn ) { if ( App::$isLoggedIn ) {
if ( ! empty( App::$activePrefs['darkMode'] ) ) {
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main.css">' );
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main-dark.css" id="dark-mode-stylesheet">' );
} else {
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main.css">' );
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main-dark.css" id="dark-mode-stylesheet" disabled>' );
}
Components::set( 'topNavRightDropdown', Template::parse( App::$topNavRightDropdown ) ); Components::set( 'topNavRightDropdown', Template::parse( App::$topNavRightDropdown ) );
Components::set( 'STATUS', Views::simpleView( 'nav.statusLoggedIn' ) ); Components::set( 'STATUS', Views::simpleView( 'nav.statusLoggedIn' ) );
Components::set( 'USERNAME', \ucfirst( App::$activeUser->username ) ); Components::set( 'USERNAME', \ucfirst( App::$activeUser->username ) );
Components::set( 'AVATAR', App::$activeUser->avatar ); Components::set( 'AVATAR', App::$activeUser->avatar );
} else { } else {
Components::set( 'STATUS', Views::simpleView( 'nav.statusLoggedOut' ) ); Components::set( 'STATUS', Views::simpleView( 'nav.statusLoggedOut' ) );
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main.css">' );
$this->addCss( '<link rel="stylesheet" href="{ROOT_URL}app/css/main-dark.css" id="dark-mode-stylesheet" disabled>' );
} }
Components::set( 'topNavRight', Template::parse( App::$topNavRight . '{STATUS}' ) ); Components::set( 'topNavRight', Template::parse( App::$topNavRight . '{STATUS}' ) );
Components::set( 'topNavLeft', Views::simpleView( 'nav.main', Navigation::getMenuLinks( App::MAIN_MENU_NAME ) ) ); Components::set( 'topNavLeft', Views::simpleView( 'nav.main', Navigation::getMenuLinks( App::MAIN_MENU_NAME ) ) );

View File

@ -46,7 +46,7 @@
<div class=""> <div class="">
<input type="checkbox" class="form-check-input" name="terms" id="terms" value="1" required> <input type="checkbox" class="form-check-input" name="terms" id="terms" value="1" required>
<label for="terms" class="form-check-label"> <label for="terms" class="form-check-label">
I have read and agree to the <a href="/home/terms" class="text-primary">Terms of Service</a> I have read and agree to the <a href="/home/terms" class="text-decoration-none atb-green">Terms of Service</a>
</label> </label>
</div> </div>
<div class="terms mt-2 mx-auto"> <div class="terms mt-2 mx-auto">
@ -59,7 +59,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">Sign up</button> <button type="submit" name="submit" value="submit" class="btn atb-green-bg btn-lg">Sign up</button>
</div> </div>
</fieldset> </fieldset>
</form> </form>