Просмотр исходного кода

[minor] initial commit of Bookmark plugin

leet1994 5 лет назад
Родитель
Сommit
603afd71dc

+ 4 - 0
.gitignore

@@ -156,6 +156,10 @@ api/plugins/*
 !api/plugins/misc/speedTest/telemetry.php
 !api/plugins/misc/speedTest/telemetry_settings.php
 !api/plugins/misc/speedTest/speedtest_worker.min.js
+!api/plugins/bookmark.php
+!api/plugins/api/bookmark.php
+!api/plugins/config/bookmark.php
+!api/plugins/js/bookmark.js
 
 # =========================
 # Custom files

+ 189 - 0
api/plugins/api/bookmark.php

@@ -0,0 +1,189 @@
+<?php
+
+$app->get('/plugins/bookmark/settings', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$GLOBALS['api']['response']['data'] = $Bookmark->_getSettings();
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+
+$app->get('/plugins/bookmark/page', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$GLOBALS['api']['response']['data'] = $Bookmark->_getPage();
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+
+$app->get('/plugins/bookmark/settings_tab_editor_bookmark_tabs', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$GLOBALS['api']['response']['data'] = $Bookmark->_getSettingsTabEditorBookmarkTabsPage();
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+
+$app->get('/plugins/bookmark/settings_tab_editor_bookmark_categories', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$GLOBALS['api']['response']['data'] = $Bookmark->_getSettingsTabEditorBookmarkCategoriesPage();
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+
+// TABS
+$app->get('/plugins/bookmark/tabs', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$GLOBALS['api']['response']['data'] = $Bookmark->_getTabs();
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->get('/plugins/bookmark/tabs/{id}', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$GLOBALS['api']['response']['data'] = $Bookmark->_getTabByIdCheckUser($args['id']);
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->post('/plugins/bookmark/tabs', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$Bookmark->_addTab($Bookmark->apiData($request));
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->put('/plugins/bookmark/tabs', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$Bookmark->_updateTabOrder($Bookmark->apiData($request));
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->put('/plugins/bookmark/tabs/{id}', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$Bookmark->_updateTab($args['id'], $Bookmark->apiData($request));
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->delete('/plugins/bookmark/tabs/{id}', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$Bookmark->_deleteTab($args['id']);
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json')
+		->withStatus($GLOBALS['responseCode']);
+});
+
+// CATEGORIES
+$app->get('/plugins/bookmark/categories', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$GLOBALS['api']['response']['data'] = $Bookmark->_getTabs();
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->post('/plugins/bookmark/categories', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$Bookmark->_addCategory($Bookmark->apiData($request));
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json;charset=UTF-8')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->put('/plugins/bookmark/categories', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$Bookmark->_updateCategoryOrder($Bookmark->apiData($request));
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->put('/plugins/bookmark/categories/{id}', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$Bookmark->_updateCategory($args['id'], $Bookmark->apiData($request));
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json')
+		->withStatus($GLOBALS['responseCode']);
+});
+$app->delete('/plugins/bookmark/categories/{id}', function ($request, $response, $args) {
+	$Bookmark = new Bookmark();
+	if ($Bookmark->_checkRequest($request) && $Bookmark->checkRoute($request)) {
+		if ($Bookmark->qualifyRequest(1, true)) {
+			$Bookmark->_deleteCategory($args['id']);
+		}
+	}
+	$response->getBody()->write(jsonE($GLOBALS['api']));
+	return $response
+		->withHeader('Content-Type', 'application/json')
+		->withStatus($GLOBALS['responseCode']);
+});

+ 1068 - 0
api/plugins/bookmark.php

@@ -0,0 +1,1068 @@
+<?php
+// PLUGIN INFORMATION
+$GLOBALS['plugins'][]['Bookmark'] = array( // Plugin Name
+	'name' => 'Bookmark', // Plugin Name
+	'author' => 'leet1994', // Who wrote the plugin
+	'category' => 'Utilities', // One to Two Word Description
+	'link' => '', // Link to plugin info
+	'license' => 'personal,business', // License Type use , for multiple
+	'idPrefix' => 'BOOKMARK', // html element id prefix
+	'configPrefix' => 'BOOKMARK', // config file prefix for array items without the hypen
+	'dbPrefix' => 'BOOKMARK', // db prefix
+	'version' => '0.1.0', // SemVer of plugin
+	'image' => 'plugins/images/bookmark.png', // 1:1 non transparent image for plugin
+	'settings' => true, // does plugin need a settings page? true or false
+	'homepage' => false // Is plugin for use on homepage? true or false
+);
+
+// Logo image under Public Domain from https://openclipart.org/detail/182527/open-book
+
+class Bookmark extends Organizr
+{
+	public function writeLog($type = 'error', $message = null, $username = null)
+	{
+		parent::writeLog($type, "Plugin 'Bookmark': " . $message, $username);
+	}
+
+	public function _checkRequest($request)
+	{
+		$result = false;
+
+		if ($this->config['BOOKMARK-enabled'] && $this->hasDB()) {
+			/*
+			$response = [
+				array(
+					'function' => 'query',
+					'query' => 'DROP TABLE IF EXISTS `BOOKMARK-categories`'
+				),
+				array(
+					'function' => 'query',
+					'query' => 'DROP TABLE IF EXISTS `BOOKMARK-tabs`'
+				)
+			];
+			$this->processQueries($response);
+			//*/
+
+			if (!$this->_checkDatabaseTablesExist()) {
+				$this->_createDatabaseTables();
+			}
+			$result = true;
+		}
+
+		return $result;
+	}
+
+	protected function _checkDatabaseTablesExist()
+	{
+		$response = [
+			array(
+				'function' => 'fetchSingle',
+				'query' => array(
+					"SELECT `name` FROM `sqlite_master` WHERE `type` = 'table' AND `name` = 'BOOKMARK-categories'"
+				),
+				'key' => 'BOOKMARK-categories'
+			),
+			array(
+				'function' => 'fetchSingle',
+				'query' => array(
+					"SELECT `name` FROM `sqlite_master` WHERE `type` = 'table' AND `name` = 'BOOKMARK-tabs'"
+				),
+				'key' => 'BOOKMARK-tabs'
+			),
+		];
+
+		$data = $this->processQueries($response);
+		return ($data["BOOKMARK-categories"] != false && $data["BOOKMARK-tabs"] != false);
+	}
+
+	protected function _createDatabaseTables()
+	{
+		$response = [
+			array(
+				'function' => 'query',
+				'query' => 'CREATE TABLE `BOOKMARK-categories` (
+					`id`	INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+					`order`	INTEGER,
+					`category`	TEXT UNIQUE,
+					`category_id`	INTEGER,
+					`default` INTEGER
+				);'
+			),
+			array(
+				'function' => 'query',
+				'query' => 'CREATE TABLE `BOOKMARK-tabs` (
+					`id`	INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
+					`order`	INTEGER,
+					`category_id`	INTEGER,
+					`name`	TEXT,
+					`url`	TEXT,
+					`enabled`	INTEGER,
+					`group_id`	INTEGER,
+					`image`	TEXT,
+					`background_color` TEXT,
+					`text_color` TEXT
+				);'
+			)
+		];
+		$this->processQueries($response);
+	}
+
+	public function _getSettings()
+	{
+		return array(
+			'custom' => '
+				<div class="row">
+                    <div class="col-lg-12">
+                        <div class="panel panel-info">
+                            <div class="panel-heading">
+								<span lang="en">Notice</span>
+                            </div>
+                            <div class="panel-wrapper collapse in" aria-expanded="true">
+                                <div class="panel-body">
+									<ul class="list-icons">
+										<li><i class="fa fa-chevron-right text-info"></i> Add tab that points to <i>api/v2/plugins/bookmark/page</i> and set it\'s type to <i>Organizr</i>.</li>
+										<li><i class="fa fa-chevron-right text-info"></i> Create Bookmark categories in the new area in <i>Tab Editor</i>.</li>
+										<li><i class="fa fa-chevron-right text-info"></i> Create Bookmark tabs in the new area in <i>Tab Editor</i>.</li>
+										<li><i class="fa fa-chevron-right text-info"></i> Open your custom Bookmark page via menu.</li>
+                                    </ul>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+				</div>
+			'
+		);
+	}
+
+	public function _getPage()
+	{
+		$script = '
+<script>
+	var styles = `
+		#BOOKMARK-wrapper {
+			display: flex;
+			flex-direction: column;
+			justify-content: flex-start;
+		}
+		.BOOKMARK-category {
+			text-align: center;
+			margin-bottom: 40px;
+		}
+		.BOOKMARK-category-title {
+			font-weight: 500;
+			color: #ddd;
+			font-size: large;
+		}
+		.BOOKMARK-category-content {
+			width: 80%;
+			margin: 0 auto;
+			display: flex;
+			flex-flow: row wrap;
+			justify-content: center;
+		}
+		.BOOKMARK-tab {
+			display: inline-flex;
+			justify-content: space-between;
+			align-items: center;
+			margin: 10px 10px 0 10px;
+			height: 50px;
+			width: 200px;
+			overflow: hidden;
+			border: 1px solid;
+			border-radius: 5px;
+		}
+		.BOOKMARK-tab img {
+			width: 100px;
+			max-width: 50px;
+			height: 100%;
+			flex-grow: 33;
+			object-fit: contain;
+			padding: 5px;
+		}
+		.BOOKMARK-tab span {
+			flex-grow: 67;
+			padding: 0 5px;
+			color: white;
+			text-align: left;
+			font-weight: 500;
+		}
+	`;
+
+	var styleSheet = document.createElement("style");
+	styleSheet.type = "text/css";
+	styleSheet.innerText = styles;
+	document.head.appendChild(styleSheet);
+
+
+</script>';
+
+		$bookmarks = '<div id="BOOKMARK-wrapper">';
+		foreach ($this->_getAllCategories() as $category) {
+			$tabs = $this->_getRelevantTabsForCategory($category['category_id']);
+			if (count($tabs) == 0) continue;
+			$bookmarks .= '<div class="BOOKMARK-category">
+				<div class="BOOKMARK-category-title">
+					' . $category['category'] . '
+				</div>
+				<div class="BOOKMARK-category-content">';
+			foreach ($tabs as $tab) {
+				$bookmarks .= '<a href="'.$tab['url'].'" target="_SELF">
+					<div class="BOOKMARK-tab"
+						style="border-color: '.$this->adjustBrightness($tab['background_color'], 0.3).'; background: linear-gradient(90deg, '.$this->adjustBrightness($tab['background_color'], -0.3).' 0%, '.$tab['background_color'].' 70%, '.$this->adjustBrightness($tab['background_color'], 0.1).' 100%);">
+						'.$this->_iconPrefix($tab['image']).'
+						<span style="color: '.$tab['text_color'].';">'.$tab['name'].'</span>
+					</div>
+				</a>';
+			}
+			$bookmarks .= '</div></div>';
+		}
+		$bookmarks .= '</div>';
+
+		return $script . $bookmarks;
+	}
+
+	protected function _iconPrefix($source)
+	{
+		$tabIcon = explode("::", $source);
+		$icons = array(
+			"materialize" => "mdi mdi-",
+			"fontawesome" => "fa fa-",
+			"themify" => "ti-",
+			"simpleline" => "icon-",
+			"weathericon" => "wi wi-",
+			"alphanumeric" => "fa-fw",
+		);
+		if (is_array($tabIcon) && count($tabIcon) == 2) {
+			if ($tabIcon[0] !== 'url' && $tabIcon[0] !== 'alphanumeric') {
+				return '<i class="' . $icons[$tabIcon[0]] . $tabIcon[1] . ' fa-fw"></i>';
+			} else if ($tabIcon[0] == 'alphanumeric') {
+				return '<i class="fa-fw">' . $tabIcon[1] . '</i>';
+			} else {
+				return '<img class="fa-fw" src="' . $tabIcon[1] . '" alt="tabIcon" />';
+			}
+		} else {
+			return '<img class="fa-fw" src="' . $source . '" alt="tabIcon" />';
+		}
+	}
+
+	protected function _getAllCategories()
+	{
+		$response = [
+			array(
+				'function' => 'fetchAll',
+				'query' => 'SELECT * FROM `BOOKMARK-categories` ORDER BY `order` ASC'
+			)
+		];
+		return $this->processQueries($response);
+	}
+
+	protected function _getRelevantTabsForCategory($category_id)
+	{
+		$response = [
+			array(
+				'function' => 'fetchAll',
+				'query' => array(
+					"SELECT * FROM `BOOKMARK-tabs` WHERE `enabled`='1' AND `category_id`=? AND `group_id`>=? ORDER BY `order` ASC",
+					$category_id,
+					$this->getUserLevel()
+				)
+			)
+		];
+		return $this->processQueries($response);
+	}
+
+	public function _getTabs()
+	{
+		$response = [
+			array(
+				'function' => 'fetchAll',
+				'query' => 'SELECT * FROM `BOOKMARK-tabs` ORDER BY `order` ASC',
+				'key' => 'tabs'
+			),
+			array(
+				'function' => 'fetchAll',
+				'query' => 'SELECT * FROM `BOOKMARK-categories` ORDER BY `order` ASC',
+				'key' => 'categories'
+			),
+			array(
+				'function' => 'fetchAll',
+				'query' => 'SELECT * FROM `groups` ORDER BY `group_id` ASC',
+				'key' => 'groups'
+			)
+		];
+		return $this->processQueries($response);
+	}
+
+	// Tabs
+	public function _getSettingsTabEditorBookmarkTabsPage()
+	{
+		$iconSelectors = '
+			$(".bookmarkTabIconIconList").select2({
+				ajax: {
+					url: \'api/v2/icon\',
+					data: function (params) {
+						var query = {
+							search: params.term,
+							page: params.page || 1
+						}
+						return query;
+					},
+					processResults: function (data, params) {
+						params.page = params.page || 1;
+						return {
+							results: data.response.data.results,
+							pagination: {
+								more: (params.page * 20) < data.response.data.total
+							}
+						};
+					},
+					//cache: true
+				},
+				placeholder: \'Search for an icon\',
+				templateResult: formatIcon,
+				templateSelection: formatIcon
+			});
+
+			$(".bookmarkTabIconImageList").select2({
+				 ajax: {
+					url: \'api/v2/image/select\',
+					data: function (params) {
+						var query = {
+							search: params.term,
+							page: params.page || 1
+						}
+						return query;
+					},
+					processResults: function (data, params) {
+						params.page = params.page || 1;
+						return {
+							results: data.response.data.results,
+							pagination: {
+								more: (params.page * 20) < data.response.data.total
+							}
+						};
+					},
+					//cache: true
+				},
+				placeholder: \'Search for an image\',
+				templateResult: formatImage,
+				templateSelection: formatImage
+			});
+		';
+		return '
+		<script>
+		buildBookmarkTabEditor();
+		!function(a){function f(a,b){if(!(a.originalEvent.touches.length>1)){a.preventDefault();var c=a.originalEvent.changedTouches[0],d=document.createEvent("MouseEvents");d.initMouseEvent(b,!0,!0,window,1,c.screenX,c.screenY,c.clientX,c.clientY,!1,!1,!1,!1,0,null),a.target.dispatchEvent(d)}}if(a.support.touch="ontouchend"in document,a.support.touch){var e,b=a.ui.mouse.prototype,c=b._mouseInit,d=b._mouseDestroy;b._touchStart=function(a){var b=this;!e&&b._mouseCapture(a.originalEvent.changedTouches[0])&&(e=!0,b._touchMoved=!1,f(a,"mouseover"),f(a,"mousemove"),f(a,"mousedown"))},b._touchMove=function(a){e&&(this._touchMoved=!0,f(a,"mousemove"))},b._touchEnd=function(a){e&&(f(a,"mouseup"),f(a,"mouseout"),this._touchMoved||f(a,"click"),e=!1)},b._mouseInit=function(){var b=this;b.element.bind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),c.call(b)},b._mouseDestroy=function(){var b=this;b.element.unbind({touchstart:a.proxy(b,"_touchStart"),touchmove:a.proxy(b,"_touchMove"),touchend:a.proxy(b,"_touchEnd")}),d.call(b)}}}(jQuery);
+		$( \'#bookmarkTabEditorTable\' ).sortable({
+			stop: function () {
+				$(\'input.order\').each(function(idx) {
+					$(this).val(idx + 1);
+				});
+				var newTabs = $( "#submit-bookmark-tabs-form" ).serializeToJSON();
+				newBookmarkTabsGlobal = newTabs;
+				$(\'.saveBookmarkTabOrderButton\').removeClass(\'hidden\');
+				//submitTabOrder(newTabs);
+			}
+		});
+		$( \'#bookmarkTabEditorTable\' ).disableSelection();
+		' . $iconSelectors . '
+		</script>
+		<div class="panel bg-org panel-info">
+			<div class="panel-heading">
+				<span lang="en">Bookmark Tab Editor</span>
+				<button type="button" class="btn btn-info btn-circle pull-right popup-with-form m-r-5" href="#new-bookmark-tab-form" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
+				<button onclick="submitBookmarkTabOrder(newBookmarkTabsGlobal)" class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right animated loop-animation rubberBand m-r-20 saveBookmarkTabOrderButton hidden" type="button"><span class="btn-label"><i class="fa fa-save"></i></span><span lang="en">Save Tab Order</span></button>
+			</div>
+			<div class="table-responsive">
+				<form id="submit-bookmark-tabs-form" onsubmit="return false;">
+					<table class="table table-hover manage-u-table">
+						<thead>
+							<tr>
+								<th width="70" class="text-center">#</th>
+								<th lang="en">NAME</th>
+								<th lang="en">CATEGORY</th>
+								<th lang="en">GROUP</th>
+								<th lang="en" style="text-align:center">ACTIVE</th>
+								<th lang="en" style="text-align:center">EDIT</th>
+								<th lang="en" style="text-align:center">DELETE</th>
+							</tr>
+						</thead>
+						<tbody id="bookmarkTabEditorTable">
+							<td class="text-center" colspan="12"><i class="fa fa-spin fa-spinner"></i></td>
+						</tbody>
+					</table>
+				</form>
+			</div>
+		</div>
+		<form id="new-bookmark-tab-form" class="mfp-hide white-popup-block mfp-with-anim">
+			<h1 lang="en">Add New Tab</h1>
+			<fieldset style="border:0;">
+				<div class="form-group">
+					<label class="control-label" for="new-bookmark-tab-form-inputNameNew" lang="en">Tab Name</label>
+					<input type="text" class="form-control" id="new-bookmark-tab-form-inputNameNew" name="name" required="" autofocus>
+				</div>
+				<div class="form-group">
+					<label class="control-label" for="new-bookmark-tab-form-inputURLNew" lang="en">Tab URL</label>
+					<input type="text" class="form-control" id="new-bookmark-tab-form-inputURLNew" name="url"  required="">
+				</div>
+				<div class="row">
+					<div class="form-group col-lg-4">
+						<label class="control-label" for="new-bookmark-tab-form-chooseImage" lang="en">Choose Image</label>
+						<select class="form-control bookmarkTabIconImageList" id="new-bookmark-tab-form-chooseImage" name="chooseImage"><option lang="en">Select or type Image</option></select>
+					</div>
+					<div class="form-group col-lg-4">
+						<label class="control-label" for="new-bookmark-tab-form-chooseIcon" lang="en">Choose Icon</label>
+						<select class="form-control bookmarkTabIconIconList" id="new-bookmark-tab-form-chooseIcon" name="chooseIcon"><option lang="en">Select or type Icon</option></select>
+					</div>
+					<div class="form-group col-lg-4">
+						<label class="control-label" for="new-bookmark-tab-form-chooseBlackberry" lang="en">Choose Blackberry Theme Icon</label>
+						<button id="new-bookmark-tab-form-chooseBlackberry" class="btn btn-xs btn-primary waves-effect waves-light form-control" onclick="showBlackberryThemes(\'new-bookmark-tab-form-inputImageNew\');" type="button">
+							<i class="fa fa-search"></i>&nbsp; <span lang="en">Choose</span>
+						</button>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label" for="new-bookmark-tab-form-inputImageNew" lang="en">Tab Image</label>
+					<input type="text" class="form-control" id="new-bookmark-tab-form-inputImageNew" name="image" required="">
+				</div>
+				<div class="row">
+					<div class="form-group col-lg-4">
+						<label class="control-label" for="new-bookmark-tab-form-inputBackgroundColorNew" lang="en">Background Color</label>
+						<input type="text" class="form-control" id="new-bookmark-tab-form-inputBackgroundColorNew" name="background_color" required="">
+					</div>
+					<div class="form-group col-lg-4">
+						<label class="control-label" for="new-bookmark-tab-form-inputTextColorNew" lang="en">Text Color</label>
+						<input type="text" class="form-control" id="new-bookmark-tab-form-inputTextColorNew" name="text_color" required="">
+					</div>
+				</div>
+			</fieldset>
+			<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none addNewBookmarkTab" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Add Tab</span></button>
+			<div class="clearfix"></div>
+		</form>
+		<form id="edit-bookmark-tab-form" class="mfp-hide white-popup-block mfp-with-anim">
+			<input type="hidden" name="id" value="x">
+			<span class="hidden" id="originalBookmarkTabName"></span>
+			<h1 lang="en">Edit Tab</h1>
+			<fieldset style="border:0;">
+				<div class="form-group">
+					<label class="control-label" for="edit-bookmark-tab-form-inputName" lang="en">Tab Name</label>
+					<input type="text" class="form-control" id="edit-bookmark-tab-form-inputName" name="name" required="" autofocus>
+				</div>
+				<div class="form-group">
+					<label class="control-label" for="edit-bookmark-tab-form-inputURL" lang="en">Tab URL</label>
+					<input type="text" class="form-control" id="edit-bookmark-tab-form-inputURL" name="url"  required="">
+				</div>
+				<div class="row">
+					<div class="form-group col-lg-4">
+						<label class="control-label" for="edit-bookmark-tab-form-chooseImage" lang="en">Choose Image</label>
+						<select class="form-control bookmarkTabIconImageList" id="edit-bookmark-tab-form-chooseImage" name="chooseImage"><option lang="en">Select or type Image</option></select>
+					</div>
+					<div class="form-group col-lg-4">
+						<label class="control-label" for="edit-bookmark-tab-form-chooseIcon" lang="en">Choose Icon</label>
+						<select class="form-control bookmarkTabIconIconList" id="edit-bookmark-tab-form-chooseIcon" name="chooseIcon"><option lang="en">Select or type Icon</option></select>
+					</div>
+					<div class="form-group col-lg-4">
+						<label class="control-label" for="edit-bookmark-tab-form-chooseBlackberry" lang="en">Choose Blackberry Theme Icon</label>
+						<button id="edit-bookmark-tab-form-chooseBlackberry" class="btn btn-xs btn-primary waves-effect waves-light form-control" onclick="showBlackberryThemes(\'edit-bookmark-tab-form-inputImage\');" type="button">
+							<i class="fa fa-search"></i>&nbsp; <span lang="en">Choose</span>
+						</button>
+					</div>
+				</div>
+				<div class="form-group">
+					<label class="control-label" for="edit-bookmark-tab-form-inputImage" lang="en">Tab Image</label>
+					<input type="text" class="form-control" id="edit-bookmark-tab-form-inputImage" name="image"  required="">
+				</div>
+				<div class="row">
+					<div class="form-group col-lg-4">
+						<label class="control-label" for="new-bookmark-tab-form-inputBackgroundColor" lang="en">Background Color</label>
+						<input type="text" class="form-control" id="new-bookmark-tab-form-inputBackgroundColor" name="background_color" required="">
+					</div>
+					<div class="form-group col-lg-4">
+						<label class="control-label" for="new-bookmark-tab-form-inputTextColor" lang="en">Text Color</label>
+						<input type="text" class="form-control" id="new-bookmark-tab-form-inputTextColor" name="text_color" required="">
+					</div>
+				</div>
+			</fieldset>
+			<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none editBookmarkTab" type="button"><span class="btn-label"><i class="fa fa-check"></i></span><span lang="en">Edit Tab</span></button>
+			<div class="clearfix"></div>
+		</form>
+		';
+	}
+
+	public function _isBookmarkTabNameTaken($name, $id = null)
+	{
+		if ($id) {
+			$response = [
+				array(
+					'function' => 'fetchAll',
+					'query' => array(
+						'SELECT * FROM `BOOKMARK-tabs` WHERE `name` LIKE ? AND `id` != ?',
+						$name,
+						$id
+					)
+				),
+			];
+		} else {
+			$response = [
+				array(
+					'function' => 'fetchAll',
+					'query' => array(
+						'SELECT * FROM `BOOKMARK-tabs` WHERE `name` LIKE ?',
+						$name
+					)
+				),
+			];
+		}
+		return $this->processQueries($response);
+	}
+
+	public function _getNextBookmarkTabOrder()
+	{
+		$response = [
+			array(
+				'function' => 'fetchSingle',
+				'query' => array(
+					'SELECT `order` from `BOOKMARK-tabs` ORDER BY `order` DESC'
+				)
+			),
+		];
+		return $this->processQueries($response);
+	}
+
+	public function _getBookmarkTabById($id)
+	{
+		$response = [
+			array(
+				'function' => 'fetch',
+				'query' => array(
+					'SELECT * FROM `BOOKMARK-tabs` WHERE `id` = ?',
+					$id
+				)
+			),
+		];
+		return $this->processQueries($response);
+	}
+
+	public function _getTabByIdCheckUser($id)
+	{
+		$tabInfo = $this->_getBookmarkTabById($id);
+		if ($tabInfo) {
+			if ($this->qualifyRequest($tabInfo['group_id'], true)) {
+				return $tabInfo;
+			}
+		} else {
+			$this->setAPIResponse('error', 'id not found', 404);
+			return false;
+		}
+	}
+
+	public function _deleteTab($id)
+	{
+		$response = [
+			array(
+				'function' => 'query',
+				'query' => array(
+					'DELETE FROM `BOOKMARK-tabs` WHERE id = ?',
+					$id
+				)
+			),
+		];
+		$tabInfo = $this->_getBookmarkTabById($id);
+		if ($tabInfo) {
+			$this->writeLog('success', 'Tab Delete Function -  Deleted Tab [' . $tabInfo['name'] . ']', $this->user['username']);
+			$this->setAPIResponse('success', 'Tab deleted', 204);
+			return $this->processQueries($response);
+		} else {
+			$this->setAPIResponse('error', 'id not found', 404);
+			return false;
+		}
+	}
+
+	public function _addTab($array)
+	{
+		if (!$array) {
+			$this->setAPIResponse('error', 'no data was sent', 422);
+			return null;
+		}
+		$array = $this->checkKeys($this->getTableColumnsFormatted('BOOKMARK-tabs'), $array);
+		$array['group_id'] = ($array['group_id']) ?? $this->getDefaultGroupId();
+		$array['category_id'] = ($array['category_id']) ?? $this->_getDefaultBookmarkCategoryId();
+		$array['enabled'] = ($array['enabled']) ?? 0;
+		$array['order'] = ($array['order']) ?? $this->_getNextBookmarkTabOrder() + 1;
+		if (array_key_exists('name', $array)) {
+			if ($this->_isBookmarkTabNameTaken($array['name'])) {
+				$this->setAPIResponse('error', 'Tab name: ' . $array['name'] . ' is already taken', 409);
+				return false;
+			}
+		} else {
+			$this->setAPIResponse('error', 'Tab name was not supplied', 422);
+			return false;
+		}
+		if (!array_key_exists('url', $array)) {
+			$this->setAPIResponse('error', 'Tab url was not supplied', 422);
+			return false;
+		}
+		if (!array_key_exists('image', $array)) {
+			$this->setAPIResponse('error', 'Tab image was not supplied', 422);
+			return false;
+		}
+		if (array_key_exists('background_color', $array)) {
+			if(!$this->_checkColorHexCode($array['background_color'])){
+				$this->setAPIResponse('error', 'Tab background color is invalid', 422);
+				return false;
+			}
+		}
+		else{
+			$this->setAPIResponse('error', 'Tab background color was not supplied', 422);
+			return false;
+		}
+		if (array_key_exists('text_color', $array)) {
+			if(!$this->_checkColorHexCode($array['text_color'])){
+				$this->setAPIResponse('error', 'Tab text color is invalid', 422);
+				return false;
+			}
+		}
+		else{
+			$this->setAPIResponse('error', 'Tab text color was not supplied', 422);
+			return false;
+		}
+		$response = [
+			array(
+				'function' => 'query',
+				'query' => array(
+					'INSERT INTO [BOOKMARK-tabs]',
+					$array
+				)
+			),
+		];
+		$this->setAPIResponse(null, 'Tab added');
+		$this->writeLog('success', 'Tab Editor Function -  Added Tab for [' . $array['name'] . ']', $this->user['username']);
+		return $this->processQueries($response);
+	}
+
+	public function _updateTab($id, $array)
+	{
+		if (!$id || $id == '') {
+			$this->setAPIResponse('error', 'id was not set', 422);
+			return null;
+		}
+		if (!$array) {
+			$this->setAPIResponse('error', 'no data was sent', 422);
+			return null;
+		}
+		$tabInfo = $this->_getBookmarkTabById($id);
+		if ($tabInfo) {
+			$array = $this->checkKeys($tabInfo, $array);
+		} else {
+			$this->setAPIResponse('error', 'No tab info found', 404);
+			return false;
+		}
+		if (array_key_exists('name', $array)) {
+			if ($this->_isBookmarkTabNameTaken($array['name'], $id)) {
+				$this->setAPIResponse('error', 'Tab name: ' . $array['name'] . ' is already taken', 409);
+				return false;
+			}
+		}
+		if (array_key_exists('background_color', $array)) {
+			if(!$this->_checkColorHexCode($array['background_color'])){
+				$this->setAPIResponse('error', 'Tab background color is invalid', 422);
+				return false;
+			}
+		}
+		if (array_key_exists('text_color', $array)) {
+			if(!$this->_checkColorHexCode($array['text_color'])){
+				$this->setAPIResponse('error', 'Tab text color is invalid', 422);
+				return false;
+			}
+		}
+		$response = [
+			array(
+				'function' => 'query',
+				'query' => array(
+					'UPDATE `BOOKMARK-tabs` SET',
+					$array,
+					'WHERE id = ?',
+					$id
+				)
+			),
+		];
+		$this->setAPIResponse(null, 'Tab info updated');
+		$this->writeLog('success', 'Tab Editor Function -  Edited Tab Info for [' . $tabInfo['name'] . ']', $this->user['username']);
+		return $this->processQueries($response);
+	}
+
+	public function _updateTabOrder($array)
+	{
+		if (count($array) >= 1) {
+			foreach ($array as $tab) {
+				if (count($tab) !== 2) {
+					$this->setAPIResponse('error', 'data is malformed', 422);
+					break;
+				}
+				$id = $tab['id'] ?? null;
+				$order = $tab['order'] ?? null;
+				if ($id && $order) {
+					$response = [
+						array(
+							'function' => 'query',
+							'query' => array(
+								'UPDATE `BOOKMARK-tabs` set `order` = ? WHERE `id` = ?',
+								$order,
+								$id
+							)
+						),
+					];
+					$this->processQueries($response);
+					$this->setAPIResponse(null, 'Tab Order updated');
+				} else {
+					$this->setAPIResponse('error', 'data is malformed', 422);
+				}
+			}
+		} else {
+			$this->setAPIResponse('error', 'data is empty or not in array', 422);
+			return false;
+		}
+	}
+
+	// Categories
+	public function _getSettingsTabEditorBookmarkCategoriesPage()
+	{
+		return '
+	<script>
+	buildBookmarkCategoryEditor();
+	$( \'#bookmarkCategoryEditorTable\' ).sortable({
+		stop: function () {
+			var inputs = $(\'input.order\');
+			var nbElems = inputs.length;
+			inputs.each(function(idx) {
+				$(this).val(idx + 1);
+			});
+			submitBookmarkCategoryOrder();
+		}
+	});
+	</script>
+	<div class="panel bg-org panel-info">
+		<div class="panel-heading">
+			<span lang="en">Bookmark Category Editor</span>
+			<button type="button" class="btn btn-success btn-circle pull-right popup-with-form m-r-5" href="#new-bookmark-category-form" data-effect="mfp-3d-unfold"><i class="fa fa-plus"></i> </button>
+		</div>
+		<div class="table-responsive">
+			<form id="submit-bookmark-categories-form" onsubmit="return false;">
+				<table class="table table-hover manage-u-table">
+					<thead>
+						<tr>
+							<th lang="en">NAME</th>
+							<th lang="en" style="text-align:center">TABS</th>
+							<th lang="en" style="text-align:center">DEFAULT</th>
+							<th lang="en" style="text-align:center">EDIT</th>
+							<th lang="en" style="text-align:center">DELETE</th>
+						</tr>
+					</thead>
+					<tbody id="bookmarkCategoryEditorTable"><td class="text-center" colspan="6"><i class="fa fa-spin fa-spinner"></i></td></tbody>
+				</table>
+			</form>
+		</div>
+	</div>
+	<form id="new-bookmark-category-form" class="mfp-hide white-popup-block mfp-with-anim">
+		<h1 lang="en">Add New Bookmark Category</h1>
+		<fieldset style="border:0;">
+			<div class="form-group">
+				<label class="control-label" for="new-bookmark-category-form-inputNameNew" lang="en">Category Name</label>
+				<input type="text" class="form-control" id="new-bookmark-category-form-inputNameNew" name="category" required="" autofocus>
+			</div>
+		</fieldset>
+		<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none addNewBookmarkCategory" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Add Category</span></button>
+		<div class="clearfix"></div>
+	</form>
+	<form id="edit-bookmark-category-form" class="mfp-hide white-popup-block mfp-with-anim">
+		<input type="hidden" name="id" value="">
+		<h1 lang="en">Edit Category</h1>
+		<fieldset style="border:0;">
+			<div class="form-group">
+				<label class="control-label" for="edit-bookmark-category-form-inputName" lang="en">Category Name</label>
+				<input type="text" class="form-control" id="edit-bookmark-category-form-inputName" name="category" required="" autofocus>
+			</div>
+		</fieldset>
+		<button class="btn btn-sm btn-info btn-rounded waves-effect waves-light pull-right row b-none editBookmarkCategory" type="button"><span class="btn-label"><i class="fa fa-plus"></i></span><span lang="en">Edit Category</span></button>
+		<div class="clearfix"></div>
+	</form>
+	';
+	}
+
+	public function _getDefaultBookmarkCategoryId()
+	{
+		$response = [
+			array(
+				'function' => 'fetchSingle',
+				'query' => array(
+					'SELECT `category_id` FROM `BOOKMARK-categories` WHERE `default` = 1'
+				)
+			),
+		];
+		return $this->processQueries($response);
+	}
+
+	public function _getNextBookmarkCategoryOrder()
+	{
+		$response = [
+			array(
+				'function' => 'fetchSingle',
+				'query' => array(
+					'SELECT `order` from `BOOKMARK-categories` ORDER BY `order` DESC'
+				)
+			),
+		];
+		return $this->processQueries($response);
+	}
+
+	public function _getNextBookmarkCategoryId()
+	{
+		$response = [
+			array(
+				'function' => 'fetchSingle',
+				'query' => array(
+					'SELECT `category_id` from `BOOKMARK-categories` ORDER BY `category_id` DESC'
+				)
+			),
+		];
+		return $this->processQueries($response);
+	}
+
+	public function _isBookmarkCategoryNameTaken($name, $id = null)
+	{
+		if ($id) {
+			$response = [
+				array(
+					'function' => 'fetchAll',
+					'query' => array(
+						'SELECT * FROM `BOOKMARK-categories` WHERE `category` LIKE ? AND `id` != ?',
+						$name,
+						$id
+					)
+				),
+			];
+		} else {
+			$response = [
+				array(
+					'function' => 'fetchAll',
+					'query' => array(
+						'SELECT * FROM `BOOKMARK-categories` WHERE `category` LIKE ?',
+						$name
+					)
+				),
+			];
+		}
+		return $this->processQueries($response);
+	}
+
+	public function _getBookmarkCategoryById($id)
+	{
+		$response = [
+			array(
+				'function' => 'fetch',
+				'query' => array(
+					'SELECT * FROM `BOOKMARK-categories` WHERE `id` = ?',
+					$id
+				)
+			),
+		];
+		return $this->processQueries($response);
+	}
+
+	public function _clearBookmarkCategoryDefault()
+	{
+		$response = [
+			array(
+				'function' => 'query',
+				'query' => array(
+					'UPDATE `BOOKMARK-categories` SET `default` = 0'
+				)
+			),
+		];
+		return $this->processQueries($response);
+	}
+
+	public function _addCategory($array)
+	{
+		if (!$array) {
+			$this->setAPIResponse('error', 'no data was sent', 422);
+			return null;
+		}
+		$array = $this->checkKeys($this->getTableColumnsFormatted('BOOKMARK-categories'), $array);
+		$array['default'] = ($array['default']) ?? 0;
+		$array['order'] = ($array['order']) ?? $this->_getNextBookmarkCategoryOrder() + 1;
+		$array['category_id'] = ($array['category_id']) ?? $this->_getNextBookmarkCategoryId() + 1;
+		if (array_key_exists('category', $array)) {
+			if ($this->_isBookmarkCategoryNameTaken($array['category'])) {
+				$this->setAPIResponse('error', 'Category name: ' . $array['category'] . ' is already taken', 409);
+				return false;
+			}
+		} else {
+			$this->setAPIResponse('error', 'Category name was not supplied', 422);
+			return false;
+		}
+		$response = [
+			array(
+				'function' => 'query',
+				'query' => array(
+					'INSERT INTO [BOOKMARK-categories]',
+					$array
+				)
+			),
+		];
+		$this->setAPIResponse(null, 'Category added');
+		$this->writeLog('success', 'Category Editor Function -  Added Category for [' . $array['category'] . ']', $this->user['username']);
+		$result = $this->processQueries($response);
+		$this->_correctDefaultCategory();
+		return $result;
+	}
+
+	public function _updateCategory($id, $array)
+	{
+		if (!$id || $id == '') {
+			$this->setAPIResponse('error', 'id was not set', 422);
+			return null;
+		}
+		if (!$array) {
+			$this->setAPIResponse('error', 'no data was sent', 422);
+			return null;
+		}
+		$categoryInfo = $this->_getBookmarkCategoryById($id);
+		if ($categoryInfo) {
+			$array = $this->checkKeys($categoryInfo, $array);
+		} else {
+			$this->setAPIResponse('error', 'No category info found', 404);
+			return false;
+		}
+		if (array_key_exists('category', $array)) {
+			if ($this->_isBookmarkCategoryNameTaken($array['category'], $id)) {
+				$this->setAPIResponse('error', 'Category name: ' . $array['category'] . ' is already taken', 409);
+				return false;
+			}
+		}
+		if (array_key_exists('default', $array)) {
+			if ($array['default']) {
+				$this->_clearBookmarkCategoryDefault();
+			}
+		}
+		$response = [
+			array(
+				'function' => 'query',
+				'query' => array(
+					'UPDATE `BOOKMARK-categories` SET',
+					$array,
+					'WHERE id = ?',
+					$id
+				)
+			),
+		];
+		$this->setAPIResponse(null, 'Category info updated');
+		$this->writeLog('success', 'Category Editor Function -  Edited Category Info for [' . $categoryInfo['category'] . ']', $this->user['username']);
+		$result = $this->processQueries($response);
+		$this->_correctDefaultCategory();
+		return $result;
+	}
+
+	public function _updateCategoryOrder($array)
+	{
+		if (count($array) >= 1) {
+			foreach ($array as $category) {
+				if (count($category) !== 2) {
+					$this->setAPIResponse('error', 'data is malformed', 422);
+					break;
+				}
+				$id = $category['id'] ?? null;
+				$order = $category['order'] ?? null;
+				if ($id && $order) {
+					$response = [
+						array(
+							'function' => 'query',
+							'query' => array(
+								'UPDATE `BOOKMARK-categories` set `order` = ? WHERE `id` = ?',
+								$order,
+								$id
+							)
+						),
+					];
+					$this->processQueries($response);
+					$this->setAPIResponse(null, 'Category Order updated');
+				} else {
+					$this->setAPIResponse('error', 'data is malformed', 422);
+				}
+			}
+		} else {
+			$this->setAPIResponse('error', 'data is empty or not in array', 422);
+			return false;
+		}
+	}
+
+	public function _deleteCategory($id)
+	{
+		$response = [
+			array(
+				'function' => 'query',
+				'query' => array(
+					'DELETE FROM `BOOKMARK-categories` WHERE id = ?',
+					$id
+				)
+			),
+		];
+		$categoryInfo = $this->_getBookmarkCategoryById($id);
+		if ($categoryInfo) {
+			$this->writeLog('success', 'Category Delete Function -  Deleted Category [' . $categoryInfo['category'] . ']', $this->user['username']);
+			$this->setAPIResponse('success', 'Category deleted', 204);
+			$result = $this->processQueries($response);
+			$this->_correctDefaultCategory();
+			return $result;
+		} else {
+			$this->setAPIResponse('error', 'id not found', 404);
+			return false;
+		}
+	}
+
+	protected function _correctDefaultCategory(){
+		if($this->_getDefaultBookmarkCategoryId() == null){
+			$response = [
+				array(
+					'function' => 'query',
+					'query' => 'UPDATE `BOOKMARK-categories` SET `default` = 1 WHERE `category_id` = (SELECT `category_id` FROM `BOOKMARK-categories` ORDER BY `category_id` ASC LIMIT 0,1)'
+				)
+			];
+			return $this->processQueries($response);
+		}
+	}
+
+	protected function _checkColorHexCode($hex){
+		return preg_match('/^\#([0-9a-fA-F]{3}){1,2}$/', $hex);
+	}
+
+	/**
+	 * Increases or decreases the brightness of a color by a percentage of the current brightness.
+	 *
+	 * @param   string  $hexCode        Supported formats: `#FFF`, `#FFFFFF`, `FFF`, `FFFFFF`
+	 * @param   float   $adjustPercent  A number between -1 and 1. E.g. 0.3 = 30% lighter; -0.4 = 40% darker.
+	 *
+	 * @return  string
+	 *
+	 * @author  maliayas
+	 * @link https://stackoverflow.com/questions/3512311/how-to-generate-lighter-darker-color-with-php
+	 */
+	protected function adjustBrightness($hexCode, $adjustPercent) {
+		$hexCode = ltrim($hexCode, '#');
+
+		if (strlen($hexCode) == 3) {
+			$hexCode = $hexCode[0] . $hexCode[0] . $hexCode[1] . $hexCode[1] . $hexCode[2] . $hexCode[2];
+		}
+
+		$hexCode = array_map('hexdec', str_split($hexCode, 2));
+
+		foreach ($hexCode as & $color) {
+			$adjustableLimit = $adjustPercent < 0 ? $color : 255 - $color;
+			$adjustAmount = ceil($adjustableLimit * $adjustPercent);
+
+			$color = str_pad(dechex($color + $adjustAmount), 2, '0', STR_PAD_LEFT);
+		}
+
+		return '#' . implode($hexCode);
+	}
+}

+ 4 - 0
api/plugins/config/bookmark.php

@@ -0,0 +1,4 @@
+<?php
+return array(
+    'BOOKMARK-enabled' => false
+);

+ 548 - 0
api/plugins/js/bookmark.js

@@ -0,0 +1,548 @@
+/* BOOKMARK JS FILE */
+// FUNCTIONS
+$(document).on('click', '#BOOKMARK-settings-button', function() {
+    ajaxloader(".content-wrap","in");
+    organizrAPI2('GET','api/v2/plugins/bookmark/settings').success(function(data) {
+        var response = data.response;
+        $('#BOOKMARK-settings-items').html(buildFormGroup(response.data));
+    }).fail(function(xhr) {
+        console.error("Organizr Function: API Connection Failed");
+    });
+    ajaxloader();
+});
+
+bookmarkLaunch()
+function bookmarkLaunch(){
+    if(typeof activeInfo == 'undefined'){
+        setTimeout(function () {
+            bookmarkLaunch();
+        }, 1000);
+    }else{
+        if(activeInfo.plugins["BOOKMARK-enabled"] == true){
+            bookmarkTabsLaunch();
+            bookmarkCategoriesLaunch();
+            pageLoad();
+        }
+    }
+}
+
+// TAB MANAGEMENT
+function bookmarkTabsLaunch(){
+    var menuList = `<li onclick="changeSettingsMenu('Settings::Tab Editor::Bookmark Tabs');loadSettingsPage2('api/v2/plugins/bookmark/settings_tab_editor_bookmark_tabs','#settings-tab-editor-tabs','Tab Editor');" role="presentation"><a id="settings-tab-editor-tabs-anchor" href="#settings-tab-editor-tabs" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="true"><span class="visible-xs"><i class="ti-layout-tab-v"></i></span><span class="hidden-xs" lang="en">Bookmark Tabs</span></a></li>`;
+    $('#settings-main-tab-editor .nav-tabs').append(menuList);
+}
+
+function buildBookmarkTabEditor(){
+	organizrAPI2('GET','api/v2/plugins/bookmark/tabs').success(function(data) {
+        try {
+            var response = data.response;
+        }catch(e) {
+	        organizrCatchError(e,data);
+        }
+		$('#bookmarkTabEditorTable').html(buildBookmarkTabEditorItem(response.data));
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr);
+	});
+}
+
+function buildBookmarkTabEditorItem(array){
+	var tabList = '';
+	$.each(array.tabs, function(i,v) {
+		tabList += `
+		<tr class="bookmarkTabEditor" data-order="`+v.order+`" data-original-order="`+v.order+`" data-id="`+v.id+`" data-group-id="`+v.group_id+`" data-category-id="`+v.category_id+`" data-name="`+v.name+`" data-url="`+v.url+`" data-image="`+v.image+`">
+			<input type="hidden" class="form-control" name="tab[`+v.id+`].id" value="`+v.id+`">
+			<input type="hidden" class="form-control order" name="tab[`+v.id+`].order" value="`+v.order+`">
+			<input type="hidden" class="form-control" name="tab[`+v.id+`].originalOrder" value="`+v.order+`">
+			<td style="text-align:center" class="text-center el-element-overlay">
+				<div class="el-card-item p-0">
+					<div class="el-card-avatar el-overlay-1 m-0">
+						<div class="bookmarkTabEditorIcon">`+iconPrefix(v.image)+`</div>
+						<div class="el-overlay bg-org">
+							<ul class="el-info">
+								<i class="fa fa-bars"></i>
+							</ul>
+						</div>
+					</div>
+				</div>
+			</td>
+			<td><span class="tooltip-info" data-toggle="tooltip" data-placement="right" title="" data-original-title="`+v.url+`">`+v.name+`</span></td>
+            `+buildBookmarkTabCategorySelect(array.categories,v.id, v.category_id)+`
+			`+buildBookmarkTabGroupSelect(array.groups,v.id, v.group_id)+`
+			<td style="text-align:center"><input type="checkbox" class="js-switch bookmarkEnabledSwitch" data-size="small" data-color="#99d683" data-secondary-color="#f96262" name="tab[`+v.id+`].enabled" value="true" `+tof(v.enabled,'c')+`/><input type="hidden" class="form-control" name="tab[`+v.id+`].enabled" value="false"></td>
+			<td style="text-align:center"><button type="button" class="btn btn-info btn-outline btn-circle btn-lg m-r-5 editBookmarkTabButton popup-with-form" onclick="editBookmarkTabForm('`+v.id+`')" href="#edit-bookmark-tab-form" data-effect="mfp-3d-unfold"><i class="ti-pencil-alt"></i></button></td>
+			<td style="text-align:center"><button type="button" class="btn btn-danger btn-outline btn-circle btn-lg m-r-5 bookmarkDeleteTab"><i class="ti-trash"></i></button></td>
+		</tr>
+		`;
+	});
+	return tabList;
+}
+
+function buildBookmarkTabGroupSelect(array, tabID, groupID){
+	var groupSelect = '';
+	var selected = '';
+	$.each(array, function(i,v) {
+		selected = '';
+		if(v.group_id == groupID){
+			selected = 'selected';
+		}
+		groupSelect += '<option '+selected+' value="'+v.group_id+'">'+v.group+'</option>';
+	});
+	return '<td><select name="tab['+tabID+'].group_id" class="form-control bookmarkTabGroupSelect">'+groupSelect+'</select></td>';
+}
+
+function buildBookmarkTabCategorySelect(array,tabID, categoryID){
+	var categorySelect = '';
+	var selected = '';
+	$.each(array, function(i,v) {
+		selected = '';
+		if(v.category_id == categoryID){
+			selected = 'selected';
+		}
+		categorySelect += '<option '+selected+' value="'+v.category_id+'">'+v.category+'</option>';
+	});
+	return '<td><select name="tab['+tabID+'].category_id" class="form-control bookmarkTabCategorySelect">'+categorySelect+'</select></td>';
+}
+
+function editBookmarkTabForm(id){
+	organizrAPI2('GET','api/v2/plugins/bookmark/tabs/' + id,true).success(function(data) {
+		try {
+			let response = data.response;
+			console.log(response);
+			$('#edit-bookmark-tab-form [name=name]').val(response.data.name);
+			$('#originalBookmarkTabName').html(response.data.name);
+			$('#edit-bookmark-tab-form [name=url]').val(response.data.url);
+			$('#edit-bookmark-tab-form [name=image]').val(response.data.image);
+			$('#edit-bookmark-tab-form [name=background_color]').val(response.data.background_color);
+			$('#edit-bookmark-tab-form [name=text_color]').val(response.data.text_color);
+			$('#edit-bookmark-tab-form [name=id]').val(response.data.id);
+			if( response.data.url.indexOf('/?v') > 0){
+				$('#edit-bookmark-tab-form [name=url]').prop('disabled', 'true');
+			}else{
+				$('#edit-bookmark-tab-form [name=url]').prop('disabled', null);
+			}
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr, 'Tab Error');
+	});
+}
+
+// CHANGE ENABLED TAB
+$(document).on("change", ".bookmarkEnabledSwitch", function () {
+	var id = $(this).parent().parent().attr("data-id");
+	var enabled = $(this).prop("checked") ? 1 : 0;
+	var callbacks = $.Callbacks();
+	organizrAPI2('PUT','api/v2/plugins/bookmark/tabs/' + id, {"enabled":enabled},true).success(function(data) {
+		try {
+			var response = data.response;
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+		message('Tab Enable Updated',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+		if(callbacks){ callbacks.fire(); }
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr, 'Tab Enable Error');
+	});
+});
+// CHANGE TAB GROUP
+$(document).on("change", ".bookmarkTabGroupSelect", function (event) {
+	var id = $(this).parent().parent().attr("data-id");
+	var groupID = $(this).find("option:selected").val();
+	var callbacks = $.Callbacks();
+	organizrAPI2('PUT','api/v2/plugins/bookmark/tabs/' + id, {"group_id":groupID},true).success(function(data) {
+		try {
+			var response = data.response;
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+		message('Tab Group Updated',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+		if(callbacks){ callbacks.fire(); }
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr, 'Tab Group Error');
+	});
+});
+// CHANGE TAB CATEGORY
+$(document).on("change", ".bookmarkTabCategorySelect", function () {
+	var id = $(this).parent().parent().attr("data-id");
+	var categoryID = $(this).find("option:selected").val();
+	console.log("CategoryID: " + categoryID);
+	var callbacks = $.Callbacks();
+	organizrAPI2('PUT','api/v2/plugins/bookmark/tabs/' + id, {"category_id":categoryID},true).success(function(data) {
+		try {
+			var response = data.response;
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+		message('Tab Category Updated',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+		if(callbacks){ callbacks.fire(); }
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr, 'Tab Category Error');
+	});
+});
+//DELETE TAB
+$(document).on("click", ".bookmarkDeleteTab", function () {
+    var tab = $(this);
+    swal({
+        title: window.lang.translate('Delete ') + tab.parent().parent().attr("data-name") + '?',
+        icon: "warning",
+        buttons: {
+            cancel: window.lang.translate('No'),
+            confirm: window.lang.translate('Yes'),
+        },
+        dangerMode: true,
+        confirmButtonColor: "#DD6B55"
+    }).then(function(willDelete) {
+        if (willDelete) {
+	        var id = tab.parent().parent().attr("data-id");
+	        var callbacks = $.Callbacks();
+	        callbacks.add( buildBookmarkTabEditor );
+	        organizrAPI2('DELETE','api/v2/plugins/bookmark/tabs/' + id, null,true).success(function(data) {
+		        message('Tab Deleted','',activeInfo.settings.notifications.position,"#FFF","success","5000");
+		        if(callbacks){ callbacks.fire(); }
+	        }).fail(function(xhr) {
+		        OrganizrApiError(xhr, 'Tab Deleted Error');
+	        });
+        }
+    });
+});
+//EDIT TAB
+$(document).on("click", ".editBookmarkTab", function () {
+    var originalTabName = $('#originalBookmarkTabName').html();
+    var tabInfo = $('#edit-bookmark-tab-form').serializeToJSON();
+    if (typeof tabInfo.id == 'undefined' || tabInfo.id == '') {
+        message('Edit Tab Error',' Could not get Tab ID',activeInfo.settings.notifications.position,'#FFF','error','5000');
+	    return false;
+    }
+    if (typeof tabInfo.name == 'undefined' || tabInfo.name == '') {
+        message('Edit Tab Error',' Please set a Tab Name',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	    return false;
+    }
+    if (typeof tabInfo.image == 'undefined' || tabInfo.image == '') {
+        message('Edit Tab Error',' Please set a Tab Image',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	    return false;
+    }
+    if (typeof tabInfo.url == 'undefined' || tabInfo.url == '') {
+        message('Edit Tab Error',' Please set a Tab URL',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	    return false;
+	}
+	if (typeof tabInfo.background_color == 'undefined' || tabInfo.background_color == '') {
+        message('Edit Tab Error',' Please set a Background Color',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	    return false;
+	}
+	if (typeof tabInfo.text_color == 'undefined' || tabInfo.text_color == '') {
+        message('Edit Tab Error',' Please set a Text Color',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+	    return false;
+    }
+    if(tabInfo.id !== '' && tabInfo.tabName !== '' && tabInfo.tabImage !== '' && tabInfo.background_color !== '' && tabInfo.text_color !== ''){
+	    var callbacks = $.Callbacks();
+	    callbacks.add( buildBookmarkTabEditor );
+	    organizrAPI2('PUT','api/v2/plugins/bookmark/tabs/' + tabInfo.id,tabInfo,true).success(function(data) {
+		    try {
+			    var response = data.response;
+			    console.log(response);
+		    }catch(e) {
+			    organizrCatchError(e,data);
+		    }
+		    message('Tab Updated',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+		    if(callbacks){ callbacks.fire(); }
+		    clearForm('#edit-bookmark-tab-form');
+		    $.magnificPopup.close();
+	    }).fail(function(xhr) {
+		    OrganizrApiError(xhr, 'Tab Error');
+	    });
+    }
+});
+//ADD NEW TAB
+$(document).on("click", ".addNewBookmarkTab", function () {
+	var tabInfo = $('#new-bookmark-tab-form').serializeToJSON();
+	var order = parseInt($('#bookmarkTabEditorTable').find('tr[data-order]').last().attr('data-order')) + 1;
+	tabInfo['order'] = isNaN(order) ? 1 : order;
+
+	if (typeof tabInfo.name == 'undefined' || tabInfo.name == '') {
+		message('Edit Tab Error',' Please set a Tab Name',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+		return false;
+	}
+	if (typeof tabInfo.image == 'undefined' || tabInfo.image == '') {
+		message('Edit Tab Error',' Please set a Tab Image',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+		return false;
+	}
+	if ((typeof tabInfo.url == 'undefined' || tabInfo.url == '')) {
+		message('Edit Tab Error',' Please set a Tab URL',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+		return false;
+	}
+	if (typeof tabInfo.background_color == 'undefined' || tabInfo.background_color == '') {
+		message('Edit Tab Error',' Please set a Background Color',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+		return false;
+	}
+	if (typeof tabInfo.text_color == 'undefined' || tabInfo.text_color == '') {
+		message('Edit Tab Error',' Please set a Text Color',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+		return false;
+	}
+    if(tabInfo.order !== '' && tabInfo.name !== '' && tabInfo.url !== '' && tabInfo.image !== '' && tabInfo.background_color !== '' && tabInfo.text_color !== ''){
+        var callbacks = $.Callbacks();
+        callbacks.add( buildBookmarkTabEditor );
+	    organizrAPI2('POST','api/v2/plugins/bookmark/tabs',tabInfo,true).success(function(data) {
+		    try {
+			    var response = data.response;
+			    $('.bookmarkTabIconImageList').val(null).trigger('change');
+			    $('.bookmarkTabIconIconList').val(null).trigger('change');
+		    }catch(e) {
+			    organizrCatchError(e,data);
+		    }
+		    message('Tab Created',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+		    if(callbacks){ callbacks.fire(); }
+		    clearForm('#new-bookmark-tab-form');
+		    $.magnificPopup.close();
+	    }).fail(function(xhr) {
+		    OrganizrApiError(xhr, 'Tab Error');
+	    });
+    }
+});
+// CHANGE TAB ORDER
+function submitBookmarkTabOrder(newTabs){
+	var data = [];
+	var process = false;
+	$.each(newTabs.tab, function(i,v) {
+		if(v.originalOrder == v.order){
+			delete newTabs.tab[i];
+		}else{
+			let temp = {
+				"order":v.order,
+				"id":v.id
+			}
+			data.push(temp);
+			process = true;
+		}
+	})
+	if(!process){
+		message('Tab Order Warning','Order was not changed - Submission not needed',activeInfo.settings.notifications.position,"#FFF","warning","5000");
+		$('.saveBookmarkTabOrderButton').addClass('hidden');
+		return false;
+	}
+	var callbacks = $.Callbacks();
+	callbacks.add( buildBookmarkTabEditor );
+	organizrAPI2('PUT','api/v2/plugins/bookmark/tabs',data,true).success(function(data) {
+		try {
+			var response = data.response;
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+		message('Tab Order Updated',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+		if(callbacks){ callbacks.fire(); }
+		$('.saveBookmarkTabOrderButton').addClass('hidden');
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr, 'Update Error');
+	});
+}
+
+$(document).on('change', "#new-bookmark-tab-form-chooseImage", function (e) {
+    var newIcon = $('#new-bookmark-tab-form-chooseImage').val();
+    if(newIcon !== 'Select or type Icon'){
+        $('#new-bookmark-tab-form-inputImageNew').val(newIcon);
+    }
+});
+$(document).on('change', "#edit-bookmark-tab-form-chooseImage", function (e) {
+    var newIcon = $('#edit-bookmark-tab-form-chooseImage').val();
+    if(newIcon !== 'Select or type Icon'){
+        $('#edit-bookmark-tab-form-inputImage').val(newIcon);
+    }
+});
+$(document).on('change', "#new-bookmark-tab-form-chooseIcon", function (e) {
+    var newIcon = $('#new-bookmark-tab-form-chooseIcon').val();
+    if(newIcon !== 'Select or type Icon'){
+        $('#new-bookmark-tab-form-inputImageNew').val(newIcon);
+    }
+});
+$(document).on('change', "#edit-bookmark-tab-form-chooseIcon", function (e) {
+    var newIcon = $('#edit-bookmark-tab-form-chooseIcon').val();
+    if(newIcon !== 'Select or type Icon'){
+        $('#edit-bookmark-tab-form-inputImage').val(newIcon);
+    }
+});
+
+// CATEGORY MANAGEMENT
+function bookmarkCategoriesLaunch(){
+    var menuList = `<li onclick="changeSettingsMenu('Settings::Tab Editor::Bookmark Categories');loadSettingsPage2('api/v2/plugins/bookmark/settings_tab_editor_bookmark_categories','#settings-tab-editor-tabs','Tab Editor');" role="presentation"><a id="settings-tab-editor-tabs-anchor" href="#settings-tab-editor-tabs" aria-controls="home" role="tab" data-toggle="tab" aria-expanded="true"><span class="visible-xs"><i class="ti-layout-tab-v"></i></span><span class="hidden-xs" lang="en">Bookmark Categories</span></a></li>`;
+    $('#settings-main-tab-editor .nav-tabs').append(menuList);
+}
+
+function buildBookmarkCategoryEditor(){
+	organizrAPI2('GET','api/v2/plugins/bookmark/tabs').success(function(data) {
+        try {
+            var response = data.response;
+        }catch(e) {
+	        organizrCatchError(e,data);
+        }
+		$('#bookmarkCategoryEditorTable').html(buildBookmarkCategoryEditorItem(response.data));
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr);
+	});
+}
+
+function buildBookmarkCategoryEditorItem(array){
+	var categoryList = '';
+	$.each(array.categories, function(i,v) {
+		var tabCount = array.tabs.reduce(function (n, category) {
+		    return n + (category.category_id == v.category_id);
+		}, 0);
+		var disabledDefault = (v.default == 1) ? 'disabled' : '';
+		var disabledDelete = (tabCount > 0) ? 'disabled' : '';
+		var defaultIcon = (v.default == 1) ? 'icon-user-following' : 'icon-user-follow';
+		var defaultColor = (v.default == 1) ? 'btn-info disabled' : 'btn-warning';
+		categoryList += `
+		<tr class="bookmarkCategoryEditor" data-id="`+v.id+`" data-order="`+v.order+`" data-category-id="`+v.category_id+`" data-name="`+v.category+`" data-default="`+tof(v.default)+`" data-tab-count="`+tabCount+`">
+			<input type="hidden" class="form-control order" name="category[`+v.id+`].order" value="`+v.order+`">
+			<input type="hidden" class="form-control" name="category[`+v.id+`].originalOrder" value="`+v.order+`">
+			<input type="hidden" class="form-control" name="category[`+v.id+`].name" value="`+v.category+`">
+			<input type="hidden" class="form-control" name="category[`+v.id+`].id" value="`+v.id+`">
+			<td>`+v.category+`</td>
+			<td style="text-align:center">`+tabCount+`</td>
+			<td style="text-align:center"><button type="button" class="btn `+defaultColor+` btn-outline btn-circle btn-lg m-r-5 changeDefaultBookmarkCategory" `+disabledDefault+`><i class="`+defaultIcon+`"></i></button></td>
+			<td style="text-align:center"><button type="button" class="btn btn-info btn-outline btn-circle btn-lg m-r-5 editBookmarkCategoryButton popup-with-form" href="#edit-bookmark-category-form" data-effect="mfp-3d-unfold"><i class="ti-pencil-alt"></i></button></td>
+			<td style="text-align:center"><button type="button" class="btn btn-danger btn-outline btn-circle btn-lg m-r-5 deleteBookmarkCategory" `+disabledDelete+`><i class="ti-trash"></i></button></td>
+		</tr>
+		`;
+	});
+	return categoryList;
+}
+
+//ADD NEW CATEGORY
+$(document).on("click", ".addNewBookmarkCategory", function () {
+	var categoryInfo = $('#new-bookmark-category-form').serializeToJSON();
+	var order = parseInt($('#bookmarkCategoryEditorTable').find('tr[data-order]').last().attr('data-order')) + 1;
+	categoryInfo['order'] = isNaN(order) ? 1 : order;
+
+	if (typeof categoryInfo.category == 'undefined' || categoryInfo.category == '') {
+		message('Edit Tab Error',' Please set a Category Name',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+		return false;
+	}
+	if(categoryInfo.category !== ''){
+		var callbacks = $.Callbacks();
+		callbacks.add( buildBookmarkCategoryEditor );
+		organizrAPI2('POST','api/v2/plugins/bookmark/categories',categoryInfo,true).success(function(data) {
+			try {
+				var response = data.response;
+				console.log(response);
+			}catch(e) {
+				organizrCatchError(e,data);
+			}
+			message('Category Added',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+			if(callbacks){ callbacks.fire(); }
+			clearForm('#new-bookmark-category-form');
+			$.magnificPopup.close();
+		}).fail(function(xhr) {
+			OrganizrApiError(xhr, 'Category Error');
+		});
+	}
+});
+//DELETE CATEGORY
+$(document).on("click", ".deleteBookmarkCategory", function () {
+    var category = $(this);
+    swal({
+        title: window.lang.translate('Delete ')+category.parent().parent().attr("data-name")+'?',
+        icon: "warning",
+        buttons: {
+            cancel: window.lang.translate('No'),
+            confirm: window.lang.translate('Yes'),
+        },
+        dangerMode: true,
+        confirmButtonColor: "#DD6B55"
+    }).then(function(willDelete) {
+        if (willDelete) {
+	        var id = category.parent().parent().attr("data-id");
+	        var callbacks = $.Callbacks();
+	        callbacks.add( buildBookmarkCategoryEditor );
+	        organizrAPI2('DELETE','api/v2/plugins/bookmark/categories/' + id, null,true).success(function(data) {
+		        message('Category Deleted','',activeInfo.settings.notifications.position,"#FFF","success","5000");
+		        if(callbacks){ callbacks.fire(); }
+	        }).fail(function(xhr) {
+		        OrganizrApiError(xhr, 'Category Deleted Error');
+	        });
+        }
+    });
+});
+//EDIT CATEGORY GET ID
+$(document).on("click", ".editBookmarkCategoryButton", function () {
+    $('#edit-bookmark-category-form [name=category]').val($(this).parent().parent().attr("data-name"));
+    $('#edit-bookmark-category-form [name=id]').val($(this).parent().parent().attr("data-id"));
+});
+//EDIT CATEGORY
+$(document).on("click", ".editBookmarkCategory", function () {
+	var categoryInfo = $('#edit-bookmark-category-form').serializeToJSON();
+	if (typeof categoryInfo.id == 'undefined' || categoryInfo.id == '') {
+		message('Edit Tab Error',' Could not get Category ID',activeInfo.settings.notifications.position,'#FFF','error','5000');
+		return false;
+	}
+	if (typeof categoryInfo.category == 'undefined' || categoryInfo.category == '') {
+		message('Edit Tab Error',' Please set a Category Name',activeInfo.settings.notifications.position,'#FFF','warning','5000');
+		return false;
+	}
+	if(categoryInfo.id !== '' && categoryInfo.category !== ''){
+		var callbacks = $.Callbacks();
+		callbacks.add( buildBookmarkCategoryEditor );
+		organizrAPI2('PUT','api/v2/plugins/bookmark/categories/' + categoryInfo.id,categoryInfo,true).success(function(data) {
+			try {
+				var response = data.response;
+				console.log(response);
+			}catch(e) {
+				organizrCatchError(e,data);
+			}
+			message('Category Updated',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+			if(callbacks){ callbacks.fire(); }
+			clearForm('#edit-bookmark-category-form');
+			$.magnificPopup.close();
+		}).fail(function(xhr) {
+			OrganizrApiError(xhr, 'Category Error');
+		});
+	}
+});
+//CHANGE DEFAULT CATEGORY
+$(document).on("click", ".changeDefaultBookmarkCategory", function () {
+	var id = $(this).parent().parent().attr("data-id");
+	var callbacks = $.Callbacks();
+	callbacks.add( buildBookmarkCategoryEditor );
+	organizrAPI2('PUT','api/v2/plugins/bookmark/categories/' + id, {"default":1},true).success(function(data) {
+		try {
+			var response = data.response;
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+		message('Default Category Updated',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+		if(callbacks){ callbacks.fire(); }
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr, 'Default Cateogry Error');
+	});
+});
+// CHANGE CATEGORY ORDER
+function submitBookmarkCategoryOrder(){
+	var data = [];
+	var categories = $( "#submit-bookmark-categories-form" ).serializeToJSON();
+	var callbacks = $.Callbacks();
+	callbacks.add( buildCategoryEditor );
+	$.each(categories.category, function(i,v) {
+		if(v.originalOrder == v.order){
+			delete categories.category[i];
+		}else{
+			let temp = {
+				"order":v.order,
+				"id":v.id
+			}
+			data.push(temp);
+		}
+	})
+	organizrAPI2('PUT','api/v2/plugins/bookmark/categories',data,true).success(function(data) {
+		try {
+			var response = data.response;
+		}catch(e) {
+			organizrCatchError(e,data);
+		}
+		message('Category Order Updated',response.message,activeInfo.settings.notifications.position,"#FFF","success","5000");
+		if(callbacks){ callbacks.fire(); }
+		$('.saveTabOrderButton').addClass('hidden');
+	}).fail(function(xhr) {
+		OrganizrApiError(xhr, 'Update Error');
+	});
+}
+
+// TAB MANAGEMENT

BIN
plugins/images/bookmark.png