new: [userSettings] Added complete support of user settings

Including support of bookmarks, sidebar behavior and theming
pull/72/head
Sami Mokaddem 2021-10-18 13:28:26 +02:00
parent a2e3ad76dd
commit 78180fa90f
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
21 changed files with 463 additions and 67 deletions

View File

@ -132,7 +132,12 @@ class AppController extends Controller
$this->set('ajax', $this->request->is('ajax')); $this->set('ajax', $this->request->is('ajax'));
$this->request->getParam('prefix'); $this->request->getParam('prefix');
$this->set('baseurl', Configure::read('App.fullBaseUrl')); $this->set('baseurl', Configure::read('App.fullBaseUrl'));
$this->set('bsTheme', Configure::read('Cerebrate')['ui.bsTheme']);
if (!empty($user) && !empty($user->user_settings_by_name_with_fallback['ui.bsTheme']['value'])) {
$this->set('bsTheme', $user->user_settings_by_name_with_fallback['ui.bsTheme']['value']);
} else {
$this->set('bsTheme', Configure::read('Cerebrate')['ui.bsTheme']);
}
if ($this->modelClass == 'Tags.Tags') { if ($this->modelClass == 'Tags.Tags') {
$this->set('metaGroup', !empty($this->isAdmin) ? 'Administration' : 'Cerebrate'); $this->set('metaGroup', !empty($this->isAdmin) ? 'Administration' : 'Cerebrate');

View File

@ -13,7 +13,7 @@ class UserSettingsNavigation extends BaseNavigation
if (!empty($request->getQuery('Users_id'))) { if (!empty($request->getQuery('Users_id'))) {
$user_id = h($request->getQuery('Users_id')); $user_id = h($request->getQuery('Users_id'));
$linkData = [ $linkData = [
'label' => __('View user ({0})', h($user_id)), 'label' => __('View user [{0}]', h($user_id)),
'url' => sprintf('/users/view/%s', h($user_id)) 'url' => sprintf('/users/view/%s', h($user_id))
]; ];
return $linkData; return $linkData;
@ -24,7 +24,7 @@ class UserSettingsNavigation extends BaseNavigation
if (!empty($request->getQuery('Users_id'))) { if (!empty($request->getQuery('Users_id'))) {
$user_id = h($request->getQuery('Users_id')); $user_id = h($request->getQuery('Users_id'));
$linkData = [ $linkData = [
'label' => __('Edit user ({0})', h($user_id)), 'label' => __('Edit user [{0}]', h($user_id)),
'url' => sprintf('/users/edit/%s', h($user_id)) 'url' => sprintf('/users/edit/%s', h($user_id))
]; ];
return $linkData; return $linkData;

View File

@ -5,31 +5,83 @@ require_once(APP . 'Controller' . DS . 'Component' . DS . 'Navigation' . DS . 'b
class UsersNavigation extends BaseNavigation class UsersNavigation extends BaseNavigation
{ {
public function addRoutes()
{
$this->bcf->addRoute('Users', 'settings', [
'label' => __('User settings'),
'url' => '/users/settings/',
'icon' => 'user-cog'
]);
}
public function addParents()
{
// $this->bcf->addParent('Users', 'settings', 'Users', 'view');
}
public function addLinks() public function addLinks()
{ {
$bcf = $this->bcf; $bcf = $this->bcf;
$request = $this->request; $request = $this->request;
$this->bcf->addLink('Users', 'view', 'UserSettings', 'index', function ($config) use ($bcf, $request) { $passedData = $this->request->getParam('pass');
if (!empty($this->passedData[0])) { $this->bcf->addLink('Users', 'view', 'UserSettings', 'index', function ($config) use ($bcf, $request, $passedData) {
$user_id = $this->passedData[0]; if (!empty($passedData[0])) {
$user_id = $passedData[0];
$linkData = [ $linkData = [
'label' => __('User Setting ({0})', h($user_id)), 'label' => __('Account settings', h($user_id)),
'url' => sprintf('/users/settings/%s', h($user_id))
];
return $linkData;
}
return [];
});
$this->bcf->addLink('Users', 'view', 'UserSettings', 'index', function ($config) use ($bcf, $request, $passedData) {
if (!empty($passedData[0])) {
$user_id = $passedData[0];
$linkData = [
'label' => __('User Setting [{0}]', h($user_id)),
'url' => sprintf('/user-settings/index?Users.id=%s', h($user_id)) 'url' => sprintf('/user-settings/index?Users.id=%s', h($user_id))
]; ];
return $linkData; return $linkData;
} }
return []; return [];
}); });
$this->bcf->addLink('Users', 'edit', 'UserSettings', 'index', function ($config) use ($bcf, $request) { $this->bcf->addLink('Users', 'edit', 'UserSettings', 'index', function ($config) use ($bcf, $request, $passedData) {
if (!empty($this->passedData[0])) { if (!empty($passedData[0])) {
$user_id = $this->passedData[0]; $user_id = $passedData[0];
$linkData = [ $linkData = [
'label' => __('User Setting ({0})', h($user_id)), 'label' => __('Account settings', h($user_id)),
'url' => sprintf('/users/settings/%s', h($user_id))
];
return $linkData;
}
return [];
});
$this->bcf->addLink('Users', 'edit', 'UserSettings', 'index', function ($config) use ($bcf, $request, $passedData) {
if (!empty($passedData[0])) {
$user_id = $passedData[0];
$linkData = [
'label' => __('User Setting [{0}]', h($user_id)),
'url' => sprintf('/user-settings/index?Users.id=%s', h($user_id)) 'url' => sprintf('/user-settings/index?Users.id=%s', h($user_id))
]; ];
return $linkData; return $linkData;
} }
return []; return [];
}); });
$this->bcf->addLink('Users', 'settings', 'Users', 'view', function ($config) use ($bcf, $request, $passedData) {
if (!empty($passedData[0])) {
$user_id = $passedData[0];
$linkData = [
'label' => __('View user', h($user_id)),
'url' => sprintf('/users/view/%s', h($user_id))
];
return $linkData;
}
return [];
});
$this->bcf->addSelfLink('Users', 'settings', [
'label' => __('Account settings')
]);
} }
} }

View File

@ -68,7 +68,7 @@ class NavigationComponent extends Component
public function getUserBookmarks(): array public function getUserBookmarks(): array
{ {
$userSettingTable = TableRegistry::getTableLocator()->get('UserSettings'); $userSettingTable = TableRegistry::getTableLocator()->get('UserSettings');
$setting = $userSettingTable->getSettingByName($this->request->getAttribute('identity'), 'ui.sidebar.bookmarks'); $setting = $userSettingTable->getSettingByName($this->request->getAttribute('identity'), 'ui.bookmarks');
$bookmarks = is_null($setting) ? [] : json_decode($setting->value, true); $bookmarks = is_null($setting) ? [] : json_decode($setting->value, true);
$links = array_map(function($bookmark) { $links = array_map(function($bookmark) {
@ -316,11 +316,11 @@ class BreadcrumbFactory
$this->endpoints[$sourceController][$sourceAction]['after'] = $parents; $this->endpoints[$sourceController][$sourceAction]['after'] = $parents;
} }
public function addSelfLink(string $controller, string $action) public function addSelfLink(string $controller, string $action, array $options=[])
{ {
$this->addLink($controller, $action, $controller, $action, [ $this->addLink($controller, $action, $controller, $action, array_merge($options, [
'selfLink' => true 'selfLink' => true,
]); ]));
} }
public function addLink(string $sourceController, string $sourceAction, string $targetController, string $targetAction, $overrides = []) public function addLink(string $sourceController, string $sourceAction, string $targetController, string $targetAction, $overrides = [])

View File

@ -117,7 +117,7 @@ class UserSettingsController extends AppController
$this->render('view'); $this->render('view');
} }
public function setSetting($settingsName) public function setSetting($settingsName = false)
{ {
if (!$this->request->is('get')) { if (!$this->request->is('get')) {
$setting = $this->UserSettings->getSettingByName($this->ACL->getUser(), $settingsName); $setting = $this->UserSettings->getSettingByName($this->ACL->getUser(), $settingsName);
@ -137,6 +137,39 @@ class UserSettingsController extends AppController
$this->set('settingName', $settingsName); $this->set('settingName', $settingsName);
} }
public function saveSetting()
{
if ($this->request->is('post')) {
$data = $this->ParamHandler->harvestParams([
'name',
'value'
]);
$setting = $this->UserSettings->getSettingByName($this->ACL->getUser(), $data['name']);
if (is_null($setting)) { // setting not found, create it
$result = $this->UserSettings->createSetting($this->ACL->getUser(), $data['name'], $data['value']);
} else {
$result = $this->UserSettings->editSetting($this->ACL->getUser(), $data['name'], $data['value']);
}
$success = !empty($result);
$message = $success ? __('Setting saved') : __('Could not save setting');
$this->CRUD->setResponseForController('setSetting', $success, $message, $result);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
}
}
public function getBookmarks($forSidebar=false)
{
$bookmarks = $this->UserSettings->getSettingByName($this->ACL->getUser(), $this->UserSettings->BOOKMARK_SETTING_NAME);
$bookmarks = json_decode($bookmarks['value'], true);
$this->set('user_id', $this->ACL->getUser()->id);
$this->set('bookmarks', $bookmarks);
$this->set('forSidebar', $forSidebar);
$this->render('/element/UserSettings/saved-bookmarks');
}
public function saveBookmark() public function saveBookmark()
{ {
if (!$this->request->is('get')) { if (!$this->request->is('get')) {
@ -152,4 +185,19 @@ class UserSettingsController extends AppController
$this->set('user_id', $this->ACL->getUser()->id); $this->set('user_id', $this->ACL->getUser()->id);
} }
public function deleteBookmark()
{
if (!$this->request->is('get')) {
$result = $this->UserSettings->deleteBookmark($this->ACL->getUser(), $this->request->getData());
$success = !empty($result);
$message = $success ? __('Bookmark deleted') : __('Could not delete bookmark');
$this->CRUD->setResponseForController('deleteBookmark', $success, $message, $result);
$responsePayload = $this->CRUD->getResponsePayload();
if (!empty($responsePayload)) {
return $responsePayload;
}
}
$this->set('user_id', $this->ACL->getUser()->id);
}
} }

View File

@ -151,6 +151,11 @@ class UsersController extends AppController
public function settings() public function settings()
{ {
$this->set('user', $this->ACL->getUser()); $this->set('user', $this->ACL->getUser());
$all = $this->Users->UserSettings->getSettingsFromProviderForUser($this->ACL->getUser()['id'], true);
$this->set('settingsProvider', $all['settingsProvider']);
$this->set('settings', $all['settings']);
$this->set('settingsFlattened', $all['settingsFlattened']);
$this->set('notices', $all['notices']);
} }
public function register() public function register()

View File

@ -6,11 +6,14 @@ use App\Model\Entity\AppModel;
use Cake\ORM\Entity; use Cake\ORM\Entity;
use Authentication\PasswordHasher\DefaultPasswordHasher; use Authentication\PasswordHasher\DefaultPasswordHasher;
require_once(APP . 'Model' . DS . 'Table' . DS . 'SettingProviders' . DS . 'UserSettingsProvider.php');
use App\Settings\SettingsProvider\UserSettingsProvider;
class User extends AppModel class User extends AppModel
{ {
protected $_hidden = ['password', 'confirm_password']; protected $_hidden = ['password', 'confirm_password'];
protected $_virtual = ['user_settings_by_name']; protected $_virtual = ['user_settings_by_name', 'user_settings_by_name_with_fallback'];
protected function _getUserSettingsByName() protected function _getUserSettingsByName()
{ {
@ -23,6 +26,22 @@ class User extends AppModel
return $settingsByName; return $settingsByName;
} }
protected function _getUserSettingsByNameWithFallback()
{
if (!isset($this->SettingsProvider)) {
$this->SettingsProvider = new UserSettingsProvider();
}
$settingsByNameWithFallback = [];
if (!empty($this->user_settings)) {
foreach ($this->user_settings as $i => $setting) {
$settingsByNameWithFallback[$setting->name] = $setting->value;
}
}
$settingsProvider = $this->SettingsProvider->getSettingsConfiguration($settingsByNameWithFallback);
$settingsFlattened = $this->SettingsProvider->flattenSettingsConfiguration($settingsProvider);
return $settingsFlattened;
}
protected function _setPassword(string $password) : ?string protected function _setPassword(string $password) : ?string
{ {
if (strlen($password) > 0) { if (strlen($password) > 0) {

View File

@ -0,0 +1,50 @@
<?php
namespace App\Settings\SettingsProvider;
use Cake\ORM\TableRegistry;
require_once(APP . 'Model' . DS . 'Table' . DS . 'SettingProviders' . DS . 'BaseSettingsProvider.php');
use App\Settings\SettingsProvider\BaseSettingsProvider;
class UserSettingsProvider extends BaseSettingsProvider
{
protected function generateSettingsConfiguration()
{
return [
__('Appearance') => [
__('User Interface') => [
'ui.bsTheme' => [
'description' => 'The Bootstrap theme to use for the application',
'default' => 'default',
'name' => 'UI Theme',
'options' => (function () {
$instanceTable = TableRegistry::getTableLocator()->get('Instance');
$themes = $instanceTable->getAvailableThemes();
return array_combine($themes, $themes);
})(),
'severity' => 'info',
'type' => 'select'
],
'ui.sidebar.expanded' => [
'name' => __('Sidebar expanded'),
'type' => 'boolean',
'description' => __('Should the left navigation sidebar expanded and locked.'),
'default' => false,
'severity' => 'info',
],
'ui.sidebar.include_bookmarks' => [
'name' => __('Include bookmarks in the sidebar'),
'type' => 'boolean',
'description' => __('Should bookmarks links included in the sidebar.'),
'default' => false,
'severity' => 'info',
],
]
],
__('Account Security') => [
]
];
}
}

View File

@ -6,8 +6,13 @@ use App\Model\Table\AppTable;
use Cake\ORM\Table; use Cake\ORM\Table;
use Cake\Validation\Validator; use Cake\Validation\Validator;
require_once(APP . 'Model' . DS . 'Table' . DS . 'SettingProviders' . DS . 'UserSettingsProvider.php');
use App\Settings\SettingsProvider\UserSettingsProvider;
class UserSettingsTable extends AppTable class UserSettingsTable extends AppTable
{ {
protected $BOOKMARK_SETTING_NAME = 'ui.bookmarks';
public function initialize(array $config): void public function initialize(array $config): void
{ {
parent::initialize($config); parent::initialize($config);
@ -16,6 +21,8 @@ class UserSettingsTable extends AppTable
'Users' 'Users'
); );
$this->setDisplayField('name'); $this->setDisplayField('name');
$this->SettingsProvider = new UserSettingsProvider();
} }
public function validationDefault(Validator $validator): Validator public function validationDefault(Validator $validator): Validator
@ -27,6 +34,35 @@ class UserSettingsTable extends AppTable
return $validator; return $validator;
} }
public function getSettingsFromProviderForUser($user_id, $full = false): array
{
$settingsTmp = $this->getSettingsForUser($user_id)->toArray();
$settings = [];
foreach ($settingsTmp as $setting) {
$settings[$setting->name] = $setting->value;
}
if (empty($full)) {
return $settings;
} else {
$settingsProvider = $this->SettingsProvider->getSettingsConfiguration($settings);
$settingsFlattened = $this->SettingsProvider->flattenSettingsConfiguration($settingsProvider);
$notices = $this->SettingsProvider->getNoticesFromSettingsConfiguration($settingsProvider, $settings);
return [
'settings' => $settings,
'settingsProvider' => $settingsProvider,
'settingsFlattened' => $settingsFlattened,
'notices' => $notices,
];
}
}
public function getSettingsForUser($user_id)
{
return $this->find()->where([
'user_id' => $user_id,
])->all();
}
public function getSettingByName($user, $name) public function getSettingByName($user, $name)
{ {
return $this->find()->where([ return $this->find()->where([
@ -60,8 +96,7 @@ class UserSettingsTable extends AppTable
public function saveBookmark($user, $data) public function saveBookmark($user, $data)
{ {
$bookmarkSettingName = 'ui.sidebar.bookmarks'; $setting = $this->getSettingByName($user, $this->BOOKMARK_SETTING_NAME);
$setting = $this->getSettingByName($user, $bookmarkSettingName);
$bookmarkData = [ $bookmarkData = [
'label' => $data['bookmark_label'], 'label' => $data['bookmark_label'],
'name' => $data['bookmark_name'], 'name' => $data['bookmark_name'],
@ -69,12 +104,34 @@ class UserSettingsTable extends AppTable
]; ];
if (is_null($setting)) { // setting not found, create it if (is_null($setting)) { // setting not found, create it
$bookmarksData = json_encode([$bookmarkData]); $bookmarksData = json_encode([$bookmarkData]);
$result = $this->createSetting($user, $bookmarkSettingName, $bookmarksData); $result = $this->createSetting($user, $this->BOOKMARK_SETTING_NAME, $bookmarksData);
} else { } else {
$bookmarksData = json_decode($setting->value); $bookmarksData = json_decode($setting->value);
$bookmarksData[] = $bookmarkData; $bookmarksData[] = $bookmarkData;
$bookmarksData = json_encode($bookmarksData); $bookmarksData = json_encode($bookmarksData);
$result = $this->editSetting($user, $bookmarkSettingName, $bookmarksData); $result = $this->editSetting($user, $this->BOOKMARK_SETTING_NAME, $bookmarksData);
}
return $result;
}
public function deleteBookmark($user, $data)
{
$setting = $this->getSettingByName($user, $this->BOOKMARK_SETTING_NAME);
$bookmarkData = [
'name' => $data['bookmark_name'],
'url' => $data['bookmark_url'],
];
if (is_null($setting)) { // Can't delete something that doesn't exist
return null;
} else {
$bookmarksData = json_decode($setting->value, true);
foreach ($bookmarksData as $i => $savedBookmark) {
if ($savedBookmark['name'] == $bookmarkData['name'] && $savedBookmark['url'] == $bookmarkData['url']) {
unset($bookmarksData[$i]);
}
}
$bookmarksData = json_encode($bookmarksData);
$result = $this->editSetting($user, $this->BOOKMARK_SETTING_NAME, $bookmarksData);
} }
return $result; return $result;
} }

View File

@ -652,21 +652,21 @@ class BoostrapTable extends BootstrapGeneric {
$key = $field; $key = $field;
} }
$cellValue = Hash::get($row, $key); $cellValue = Hash::get($row, $key);
$html .= $this->genCell($cellValue, $field, $row); $html .= $this->genCell($cellValue, $field, $row, $i);
} }
} else { // indexed array } else { // indexed array
foreach ($row as $cellValue) { foreach ($row as $cellValue) {
$html .= $this->genCell($cellValue, $field, $row); $html .= $this->genCell($cellValue, $field, $row, $i);
} }
} }
$html .= $this->closeNode('tr'); $html .= $this->closeNode('tr');
return $html; return $html;
} }
private function genCell($value, $field=[], $row=[]) private function genCell($value, $field=[], $row=[], $i=0)
{ {
if (isset($field['formatter'])) { if (isset($field['formatter'])) {
$cellContent = $field['formatter']($value, $row); $cellContent = $field['formatter']($value, $row, $i);
} else if (isset($field['element'])) { } else if (isset($field['element'])) {
$cellContent = $this->btHelper->getView()->element($field['element'], [ $cellContent = $this->btHelper->getView()->element($field['element'], [
'data' => [$value], 'data' => [$value],

View File

@ -1,30 +1,57 @@
<?php <?php
// $Parsedown = new Parsedown(); $bookmarks = !empty($loggedUser->user_settings_by_name['ui.bookmarks']['value']) ? json_decode($loggedUser->user_settings_by_name['ui.bookmarks']['value'], true) : []
// echo $Parsedown->text($md);
?> ?>
<h2><?= __('Home') ?></h2> <h3>
<?= $this->Bootstrap->icon('bookmark', [
'class' => ['fa-fw']
]); ?>
<?= __('Bookmarks') ?>
</h3>
<div class="row"> <div class="row">
<?php foreach ($statistics as $modelName => $statistics): ?> <? if (!empty($bookmarks)): ?>
<ul class="col-sm-12 col-md-10 col-l-8 col-xl-8 mb-3">
<?php foreach ($bookmarks as $bookmark) : ?>
<li class="list-group-item">
<a href="<?= h($bookmark['url']) ?>" class="link-primary w-bold">
<?= h($bookmark['label']) ?>
</a>
<span class="ms-3 fw-light"><?= h($bookmark['name']) ?></span>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p class="fw-light"><?= __('No bookmarks') ?></p>
<?php endif; ?>
</div>
<h3>
<?= $this->Bootstrap->icon('chart-bar', [
'class' => ['fa-fw']
]); ?>
<?= __('Activity') ?>
</h3>
<div class="row">
<?php foreach ($statistics as $modelName => $statistics) : ?>
<div class="col-sm-6 col-md-5 col-l-4 col-xl-3 mb-3"> <div class="col-sm-6 col-md-5 col-l-4 col-xl-3 mb-3">
<?php <?php
$exploded = explode('.', $modelName); $exploded = explode('.', $modelName);
$modelForDisplay = $exploded[count($exploded)-1]; $modelForDisplay = $exploded[count($exploded) - 1];
$panelTitle = $this->Html->link( $panelTitle = $this->Html->link(
h($modelForDisplay), h($modelForDisplay),
$this->Url->build([ $this->Url->build([
'controller' => $modelForDisplay, 'controller' => $modelForDisplay,
'action' => 'index', 'action' => 'index',
]), ]),
['class' => 'text-white text-decoration-none fw-light stretched-link'] ['class' => 'text-white text-decoration-none fw-light stretched-link']
); );
echo $this->element('widgets/highlight-panel', [ echo $this->element('widgets/highlight-panel', [
'titleHtml' => $panelTitle, 'titleHtml' => $panelTitle,
'number' => $statistics['amount'], 'number' => $statistics['amount'],
'variation' => $statistics['variation'] ?? '', 'variation' => $statistics['variation'] ?? '',
'chartData' => $statistics['timeline'] ?? [] 'chartData' => $statistics['timeline'] ?? []
]); ]);
?> ?>
</div> </div>
<?php endforeach ?> <?php endforeach ?>
</div> </div>

View File

@ -1,7 +1,7 @@
<?php <?php
echo $this->element('genericElements/Form/genericForm', [ echo $this->element('genericElements/Form/genericForm', [
'data' => [ 'data' => [
'description' => __('Authkeys are used for API access. A user can have more than one authkey, so if you would like to use separate keys per tool that queries Cerebrate, add additional keys. Use the comment field to make identifying your keys easier.'), 'description' => __('Application setting form'),
'fields' => [ 'fields' => [
[ [
'field' => 'name', 'field' => 'name',

View File

@ -0,0 +1,25 @@
<?php
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'title' => __('Delete a bookmark'),
'description' => __('Specify the name and the URL of the bookmark to be deleted from your user profile.'),
'model' => 'UserSettings',
'fields' => [
[
'field' => 'bookmark_name',
'label' => __('Name'),
'placeholder' => __('Home page'),
],
[
'field' => 'bookmark_url',
'label' => __('URL'),
'placeholder' => '/instance/home',
],
],
'submit' => [
'action' => $this->request->getParam('action')
]
],
]);
?>
</div>

View File

@ -0,0 +1,17 @@
<?php
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'description' => __('User settings form'),
'fields' => [
[
'field' => 'name',
],
[
'field' => 'value'
],
],
'submit' => [
'action' => $this->request->getParam('action')
]
]
]);

View File

@ -11,6 +11,7 @@ $variantFromSeverity = [
'warning' => 'warning', 'warning' => 'warning',
'info' => 'info', 'info' => 'info',
]; ];
$this->set('variantFromSeverity', $variantFromSeverity);
$includeScrollspy = !empty($includeScrollspy); $includeScrollspy = !empty($includeScrollspy);
$groupedContent = []; $groupedContent = [];

View File

@ -0,0 +1,50 @@
<?php
$forSidebar = !empty($forSidebar);
$table = $this->Bootstrap->table([
'hover' => false,
], [
'fields' => [
['key' => 'label', 'label' => __('Label')],
['key' => 'name', 'label' => __('Name')],
['key' => 'url', 'label' => __('URL'), 'formatter' => function ($value, $row) {
return sprintf('<span class="font-monospace">%s</span>', h($value));
}],
['key' => 'action', 'label' => __('Action'), 'formatter' => function ($value, $row, $index) {
return $this->Bootstrap->button([
'icon' => 'trash',
'variant' => 'danger',
'size' => 'sm',
'params' => [
'onclick' => sprintf('deleteBookmark(window.bookmarks[%s])', $index),
]
]);
}],
],
'items' => $bookmarks,
'caption' => empty($bookmarks) ? __('No bookmark saved') : ''
]);
?>
<?php if (!empty($forSidebar)) : ?>
<li class="bookmarks">
<?php foreach ($bookmarks as $parentName => $entry) : ?>
<?= $this->element('layouts/sidebar/bookmark-entry', [
'entry' => $entry,
])
?>
<?php endforeach; ?>
<?= $this->element('layouts/sidebar/bookmark-add') ?>
</li>
<?php else : ?>
<div class="bookmark-table-container m-2">
<button class="btn btn-primary mb-2" onclick="openSaveBookmarkModal()">
<?= __('Create bookmark') ?>
</button>
<?= $table ?>
</div>
<script>
window.bookmarks = <?= json_encode($bookmarks) ?>;
</script>
<?php endif; ?>

View File

@ -15,7 +15,7 @@ use Cake\Routing\Router;
</div> </div>
</h6> </h6>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item" href="<?= Router::url(['controller' => 'users', 'action' => 'view', 'plugin' => null]) ?>"> <a class="dropdown-item" href="<?= Router::url(['controller' => 'users', 'action' => 'view', 'plugin' => null, h($this->request->getAttribute('identity')['id'])]) ?>">
<i class="me-1 <?= $this->FontAwesome->getClass('user-circle') ?>"></i> <i class="me-1 <?= $this->FontAwesome->getClass('user-circle') ?>"></i>
<?= __('My Account') ?> <?= __('My Account') ?>
</a> </a>
@ -24,7 +24,7 @@ use Cake\Routing\Router;
href="<?= Router::url(['controller' => 'users', 'action' => 'settings', 'plugin' => null, h($this->request->getAttribute('identity')['id'])]) ?>" href="<?= Router::url(['controller' => 'users', 'action' => 'settings', 'plugin' => null, h($this->request->getAttribute('identity')['id'])]) ?>"
> >
<i class="me-1 <?= $this->FontAwesome->getClass('user-cog') ?>"></i> <i class="me-1 <?= $this->FontAwesome->getClass('user-cog') ?>"></i>
<?= __('Settings') ?> <?= __('Account Settings') ?>
</a> </a>
<div class="dropdown-divider"></div> <div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-outline-danger" href="<?= Router::url(['controller' => 'users', 'action' => 'logout', 'plugin' => null]) ?>"> <a class="dropdown-item dropdown-item-outline-danger" href="<?= Router::url(['controller' => 'users', 'action' => 'logout', 'plugin' => null]) ?>">

View File

@ -1,20 +1,20 @@
<?php
$bookmarkIncluded = $loggedUser->user_settings_by_name_with_fallback['ui.sidebar.include_bookmarks']['value'];
?>
<div class="sidebar-wrapper d-flex flex-column"> <div class="sidebar-wrapper d-flex flex-column">
<div class="sidebar-scroll"> <div class="sidebar-scroll">
<div class="sidebar-content"> <div class="sidebar-content">
<ul class="sidebar-elements"> <ul class="sidebar-elements">
<?php foreach ($menu as $category => $categorized) : ?> <?php foreach ($menu as $category => $categorized) : ?>
<?php if ($category == '__bookmarks') : ?> <?php if ($category == '__bookmarks') : ?>
<?= $this->element('layouts/sidebar/category', ['label' => __('Bookmarks'), 'class' => 'bookmark-categ']) ?> <?php if ($bookmarkIncluded) : ?>
<li class="bookmarks"> <?= $this->element('layouts/sidebar/category', ['label' => __('Bookmarks'), 'class' => 'bookmark-categ']) ?>
<?php foreach ($categorized as $parentName => $entry) : ?> <?= $this->element('UserSettings/saved-bookmarks', [
<?= $this->element('layouts/sidebar/bookmark-entry', [ 'bookmarks' => $categorized,
'entry' => $entry, 'forSidebar' => true,
]) ]) ?>
?> <?php endif; ?>
<?php endforeach; ?> <?php else: ?>
<?= $this->element('layouts/sidebar/bookmark-add') ?>
</li>
<?php else : ?>
<?= $this->element('layouts/sidebar/category', ['label' => $category]) ?> <?= $this->element('layouts/sidebar/category', ['label' => $category]) ?>
<?php foreach ($categorized as $parentName => $parent) : ?> <?php foreach ($categorized as $parentName => $parent) : ?>
<?= $this->element('layouts/sidebar/entry', [ <?= $this->element('layouts/sidebar/entry', [

View File

@ -5,6 +5,7 @@ echo $this->Bootstrap->button([
'title' => __('Add new bookmark'), 'title' => __('Add new bookmark'),
'variant' => 'primary', 'variant' => 'primary',
'size' => 'sm', 'size' => 'sm',
'class' => 'mb-1',
'params' => [ 'params' => [
'id' => 'btn-add-bookmark', 'id' => 'btn-add-bookmark',
] ]

View File

@ -17,7 +17,7 @@ use Cake\Core\Configure;
$cakeDescription = 'Cerebrate'; $cakeDescription = 'Cerebrate';
$sidebarOpen = $loggedUser->user_settings_by_name['ui.sidebar.expanded']->value; $sidebarOpen = $loggedUser->user_settings_by_name_with_fallback['ui.sidebar.expanded']['value'];
?> ?>
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>

View File

@ -152,6 +152,49 @@ function focusSearchResults(evt) {
} }
} }
function openSaveBookmarkModal(bookmark_url = '') {
const url = '/user-settings/saveBookmark';
UI.submissionModal(url).then(([modalFactory, ajaxApi]) => {
const $input = modalFactory.$modal.find('input[name="bookmark_url"]')
$input.val(bookmark_url)
})
}
function deleteBookmark(bookmark, forSidebar=false) {
const url = '/user-settings/deleteBookmark'
AJAXApi.quickFetchAndPostForm(url, {
bookmark_name: bookmark.name,
bookmark_url: bookmark.url,
}, {
provideFeedback: true,
statusNode: $('.bookmark-table-container'),
}).then((apiResult) => {
const url = `/userSettings/getBookmarks/${forSidebar ? '1' : '0'}`
UI.reload(url, $('.bookmark-table-container').parent())
const theToast = UI.toast({
variant: 'success',
title: apiResult.message,
bodyHtml: $('<div/>').append(
$('<span/>').text('Cancel deletion operation.'),
$('<button/>').addClass(['btn', 'btn-primary', 'btn-sm', 'ms-3']).text('Restore bookmark').click(function () {
const urlRestore = '/user-settings/saveBookmark'
AJAXApi.quickFetchAndPostForm(urlRestore, {
bookmark_label: bookmark.label,
bookmark_name: bookmark.name,
bookmark_url: bookmark.url,
}, {
provideFeedback: true,
statusNode: $('.bookmark-table-container')
}).then(() => {
const url = `/userSettings/getBookmarks/${forSidebar ? '1' : '0'}`
UI.reload(url, $('.bookmark-table-container').parent())
})
}),
),
})
}).catch((e) => { })
}
var UI var UI
$(document).ready(() => { $(document).ready(() => {
if (typeof UIFactory !== "undefined") { if (typeof UIFactory !== "undefined") {
@ -179,10 +222,6 @@ $(document).ready(() => {
}) })
$('.sidebar #btn-add-bookmark').click(() => { $('.sidebar #btn-add-bookmark').click(() => {
const url = '/user-settings/saveBookmark'; openSaveBookmarkModal(window.location.pathname)
UI.submissionModal(url).then(([modalFactory, ajaxApi]) => {
const $input = modalFactory.$modal.find('input[name="bookmark_url"]')
$input.val(window.location.pathname)
})
}) })
}) })