Implementation of view context header

This commit is contained in:
buddh4 2020-09-25 19:16:53 +02:00
parent 94070f9a93
commit 9126e14253
18 changed files with 245 additions and 84 deletions

View File

@ -29,6 +29,7 @@ class Yii extends \yii\BaseYii
* @property-read \humhub\components\AssetManager $assetManager
* @property-read \humhub\modules\user\authclient\Collection $authClientCollection
* @property-read \yii\queue\Queue $queue
* @property-read \humhub\components\Request $request
* @property-read \humhub\components\UrlManager $urlManager
* @property-read \humhub\modules\live\components\Sender $live
* @property-read \yii\mutex\Mutex $mutex

View File

@ -42,9 +42,9 @@ class CoreApiAsset extends WebStaticAssetBundle
'js/humhub/humhub.ui.loader.js',
'js/humhub/humhub.action.js',
'js/humhub/humhub.ui.widget.js',
'js/humhub/humhub.ui.view.js',
'js/humhub/humhub.client.js',
'js/humhub/humhub.ui.status.js',
'js/humhub/humhub.ui.view.js',
'js/humhub/humhub.ui.navigation.js', // Required here since we set the active navigation on each call
'js/humhub/humhub.ui.modal.js', // Should be moved to CoreModuleScriptAssets later
'js/humhub/humhub.ui.progress.js',

View File

@ -8,6 +8,7 @@
namespace humhub\components;
use humhub\models\Setting;
use Yii;
/**
@ -18,13 +19,14 @@ use Yii;
*/
class Request extends \yii\web\Request
{
const HEADER_VIEW_CONTEXT = 'HUMHUB-VIEW-CONTEXT';
/**
* @inheritdoc
*/
public function init()
{
if (\humhub\models\Setting::isInstalled()) {
if (Setting::isInstalled()) {
$secret = Yii::$app->settings->get('secret');
if ($secret != "") {
$this->cookieValidationKey = $secret;
@ -35,4 +37,9 @@ class Request extends \yii\web\Request
$this->cookieValidationKey = 'installer';
}
}
public function getViewContext()
{
return $this->getHeaders()->get(static::HEADER_VIEW_CONTEXT);
}
}

View File

@ -19,25 +19,36 @@ use yii\base\Model;
*/
class StreamEntryOptions extends Model
{
/**
* Used when rendering the entry in default context e.g. in a container stream (default)
*/
const VIEW_CONTEXT_DEFAULT = 'default';
/**
* Used when rendering the entry on the dashboard
*/
const VIEW_CONTEXT_DASHBOARD = 'dashboard';
/**
* Used when rendering the entry outside of its container (similar to dashboard)
* Used when rendering the entry on the search stream
*/
const VIEW_CONTEXT_GLOBAL = 'global';
const VIEW_CONTEXT_SEARCH = 'search';
/**
* Used when rendering the entry in the context of its container (default)
* Used when rendering the entry e.g. as single stream entry
*/
const VIEW_CONTEXT_CONTAINER = 'container';
const VIEW_CONTEXT_DETAIL = 'detail';
/**
* @var string the active view mode of this stream entry
* Used when rendering the entry in a modal
*/
public $viewMode;
const VIEW_CONTEXT_MODAL = 'modal';
/**
* @var string the active view context used for the stream entry rendering
*/
public $viewContext;
/**
* @var string used to overwrite the widget class while rendering
@ -57,7 +68,7 @@ class StreamEntryOptions extends Model
}
if($base) {
$this->viewMode = $base->viewMode;
$this->viewContext = $base->viewContext;
$this->widgetClass = $base->widgetClass;
}
@ -67,8 +78,8 @@ class StreamEntryOptions extends Model
public function init()
{
parent::init();
if(!$this->viewMode) {
$this->viewMode = Yii::$app->request->get('from', static::VIEW_CONTEXT_CONTAINER);
if(!$this->viewContext) {
$this->viewContext = Yii::$app->request->getViewContext() ?? static::VIEW_CONTEXT_DEFAULT;
}
}
@ -92,31 +103,75 @@ class StreamEntryOptions extends Model
* @param $viewMode
* @return $this
*/
public function viewMode($viewMode)
public function viewContext($viewMode)
{
$this->viewMode = $viewMode;
$this->viewContext = $viewMode;
return $this;
}
public function getViewMode()
/**
* @return string returns the active view context
*/
public function getViewContext()
{
return $this->viewMode;
return $this->viewContext;
}
public function isViewMode($viewMode) {
return $this->viewMode === $viewMode;
/**
* Checks the active view mode against the $viewModes parameter.
* The $viewModes parameter can either be an array of view modes or a single view mode string.
* This function returns true, if one of the given view modes is active.
*
* @param string|array $viewMode
* @return bool
*/
public function isViewContext($viewContexts) {
if(!is_array($viewContexts)) {
return $this->viewContext === $viewContexts;
}
foreach ($viewContexts as $viewContext) {
if($this->viewContext === $viewContext) {
return true;
}
}
return false;
}
/**
* Checks if additional container information should be displayed in the current context. This usually should be the
* case if the entry is rendered outside of the related container stream.
*
* @param ContentActiveRecord $model
* @return bool
*/
public function isShowContainerInformation(ContentActiveRecord $model)
{
if(!$model->content->container) {
return false;
}
if($model->content->container->is($model->content->createdBy)) {
return false;
}
if(!ContentContainerHelper::getCurrent()) {
return true;
}
return !$this->isViewContext(static::VIEW_CONTEXT_DEFAULT);
}
/**
* @param ContentActiveRecord $model
* @return bool whether or not this entry should be displayed as pinned
* @return bool whether or not this entry should be displayed as pinned in the current context
*/
public function isPinned(ContentActiveRecord $model)
{
$currentContainer = ContentContainerHelper::getCurrent();
$content = $model->content;
return $currentContainer
&& $this->isViewMode(static::VIEW_CONTEXT_CONTAINER)
&& $this->isViewContext(static::VIEW_CONTEXT_DEFAULT)
&& $content->isPinned()
&& $currentContainer->contentcontainer_id === $content->contentcontainer_id;
}

View File

@ -47,20 +47,21 @@ class WallStreamEntryOptions extends StreamEntryOptions
const VIEW_CONTEXT_SEARCH = 'search';
private $addonOptions = [];
private $disableAddons = false;
private $justEdited = false;
private $contextMenuOptions = [];
private $disableContextMenu = false;
private $disableSubHeadlineAuthor = false;
public $disableAddons = false;
private $disableTargetSpaceImage = true;
public $justEdited = false;
public function disableSubHeadlineAuthor()
public $disableContextMenu = false;
public $enableSubHeadlineAuthor = false;
public $disableTargetSpaceImage = true;
public function enableSubHeadlineAuthor()
{
$this->disableSubHeadlineAuthor = true;
$this->enableSubHeadlineAuthor = true;
}
public function disableTargetSpaceImage()
@ -78,24 +79,7 @@ class WallStreamEntryOptions extends StreamEntryOptions
public function isShowAuthorLinkInSubHeadLine(ContentActiveRecord $model)
{
return !$this->disableSubHeadlineAuthor && !$model->content->container->is($model->content->createdBy);
}
public function isShowContainerInformation(ContentActiveRecord $model)
{
if(!$model->content->container) {
return false;
}
if($model->content->container->is($model->content->createdBy)) {
return false;
}
if(!ContentContainerHelper::getCurrent()) {
return true;
}
return !$this->isViewMode(static::VIEW_CONTEXT_CONTAINER);
return $this->enableSubHeadlineAuthor && !$model->content->container->is($model->content->createdBy);
}
public function justEdited($val = true)

View File

@ -152,7 +152,7 @@ abstract class WallStreamEntryWidget extends StreamEntryWidget
$this->renderOptions = (new WallStreamEntryOptions);
}
if ($this->renderOptions->isViewMode(WallStreamEntryOptions::VIEW_CONTEXT_SEARCH)) {
if ($this->renderOptions->isViewContext(WallStreamEntryOptions::VIEW_CONTEXT_SEARCH)) {
// Disable all except permalink
$this->renderOptions
->disableContextDelete()

View File

@ -20,7 +20,7 @@ abstract class WallStreamModuleEntryWidget extends WallStreamEntryWidget
public function init()
{
parent::init();
$this->renderOptions->disableTargetSpaceImage();
$this->renderOptions->disableTargetSpaceImage()->enableSubHeadlineAuthor();
}
/**

View File

@ -53,7 +53,7 @@ class DashboardStreamAction extends ActivityStreamAction
*/
public function initStreamEntryOptions()
{
return parent::initStreamEntryOptions()->viewMode(StreamEntryOptions::VIEW_CONTEXT_DASHBOARD);
return parent::initStreamEntryOptions()->viewContext(StreamEntryOptions::VIEW_CONTEXT_DASHBOARD);
}
public function setupGuestFilter()

View File

@ -8,15 +8,19 @@
namespace humhub\modules\dashboard\controllers;
use humhub\components\behaviors\AccessControl;
use humhub\components\Controller;
use humhub\modules\dashboard\components\actions\DashboardStreamAction;
use Yii;
class DashboardController extends Controller
{
const VIEW_CONTEXT = 'dashboard';
public function init()
{
$this->appendPageTitle(Yii::t('DashboardModule.base', 'Dashboard'));
$this->view->setViewContext(static::VIEW_CONTEXT);
return parent::init();
}
@ -27,7 +31,7 @@ class DashboardController extends Controller
{
return [
'acl' => [
'class' => \humhub\components\behaviors\AccessControl::class,
'class' => AccessControl::class,
'guestAllowedActions' => [
'index',
'stream'

View File

@ -45,11 +45,8 @@ class PostController extends ContentContainerController
});
}
public function actionEdit()
public function actionEdit($id)
{
$id = Yii::$app->request->get('id');
$from = Yii::$app->request->get('from', '');
$model = Post::findOne(['id' => $id]);
if (!$model->content->canEdit()) {
@ -68,7 +65,6 @@ class PostController extends ContentContainerController
return $this->renderAjax('edit', [
'post' => $model,
'from' => $from === StreamEntryOptions::VIEW_CONTEXT_DASHBOARD ? $from : null
]);
}

View File

@ -11,13 +11,9 @@ use yii\bootstrap\ActiveForm;
/* @var $from string */
/* @var $submitUrl string */
$submitParams = ['id' => $post->id];
if (! empty($from)) {
$submitParams['from'] = $from;
}
$submitUrl = $post->content->container->createUrl('/post/post/edit', $submitParams);
$submitUrl = $post->content->container->createUrl('/post/post/edit', ['id' => $post->id]);
?>
<div class="content content_edit" id="post_edit_<?= $post->id; ?>">
<?php $form = ActiveForm::begin(['id' => 'post-edit-form_' . $post->id]); ?>
@ -42,7 +38,7 @@ $submitUrl = $post->content->container->createUrl('/post/post/edit', $submitPara
'max' => Yii::$app->getModule('content')->maxAttachedFiles
]) ?>
<?= Button::defaultType(Yii::t('base', 'Save'))->action('editSubmit', $submitUrl)->submit()->cssClass(' btn-comment-submit')->sm(); ?>
<?= Button::defaultType(Yii::t('base', 'Save'))->action('editSubmit', $submitUrl)->submit()->cssClass(' btn-comment-submit')->sm() ?>
</div>
</div>

View File

@ -25,6 +25,7 @@ use humhub\modules\search\engine\Search;
*/
class SearchController extends Controller
{
const VIEW_CONTEXT = 'search';
/**
* @var string the current search keyword
@ -37,6 +38,7 @@ class SearchController extends Controller
public function init()
{
$this->appendPageTitle(\Yii::t('SearchModule.base', 'Search'));
$this->view->setViewContext(static::VIEW_CONTEXT);
return parent::init();
}

View File

@ -159,7 +159,7 @@ humhub\modules\stream\assets\StreamAsset::register($this);
<?php try { ?>
<?php if ($result instanceof ContentActiveRecord) : ?>
<?= StreamEntryWidget::renderStreamEntry($result,
(new WallStreamEntryOptions())->viewMode(WallStreamEntryOptions::VIEW_CONTEXT_SEARCH))?>
(new WallStreamEntryOptions())->viewContext(WallStreamEntryOptions::VIEW_CONTEXT_SEARCH))?>
<?php elseif ($result instanceof ContentContainerActiveRecord) : ?>
<?= $result->getWallOut() ?>
<?php else: ?>

View File

@ -21,6 +21,7 @@ use humhub\modules\web\pwa\widgets\LayoutHeader;
use humhub\modules\web\pwa\widgets\SiteIcon;
use humhub\widgets\CoreJsConfig;
use humhub\widgets\LayoutAddons;
use phpDocumentor\Reflection\DocBlock\Tags\Since;
use yii\bootstrap\BootstrapAsset;
use yii\helpers\ArrayHelper;
use Yii;
@ -65,6 +66,33 @@ class View extends \yii\web\View
*/
private static $preloaded = [];
/**
* A viewContext is a simple string value which usually is set by a controller to define the context of a resulting view.
* If a viewContext is set, it will be sent to the client in case of pjax and full page loads. The HumHub Client API
* will then use the viewContext in all ajax (non pjax) requests as HTTP request header `HUMHUB-VIEW-CONTEXT`
* until the next pjax or full page load.
*
* The viewContext is usually used to influence the view, e.g. a viewContext 'modal' indicates that the resulting view
* will be contained in a modal, a 'dashboard' context will indicate that the ajax request was sent from the dashboard.
*
* A controller usually sets the viewContext within the init function e.g.:
*
* ```php
* public function init()
* {
* parent::init();
* $this->view->setViewContext('dashboard');
* }
* ```
*
* Note: This variable won't contain the value of the request header but is only used to set the client viewContext.
* Use `Yii::$app->request->getViewContext()` to determine the active viewContext of a request.
*
* @var string defines a view context used in ajax requests
* @since 1.7
*/
private static $viewContext;
/**
* Sets current page title
*
@ -395,6 +423,7 @@ class View extends \yii\web\View
}
if (Yii::$app->request->isPjax) {
$this->registerViewContext();
echo LayoutAddons::widget();
$this->flushJsConfig();
}
@ -410,11 +439,22 @@ class View extends \yii\web\View
// Add LayoutAddons and jsConfig registered by addons
echo LayoutAddons::widget();
$this->registerViewContext();
$this->flushJsConfig();
return parent::endBody();
}
/**
* Registers the client viewContext.
*/
private function registerViewContext()
{
if(!empty(static::$viewContext)) {
$this->registerJs('humhub.modules.ui.view.setViewContext("'.static::$viewContext.'")', View::POS_END, 'viewContext');
}
}
/**
* Writes the currently registered jsConfig entries and flushes the the config array.
*
@ -452,4 +492,9 @@ class View extends \yii\web\View
return '';
}
public function setViewContext($vctx)
{
static::$viewContext = $vctx;
}
}

View File

@ -7,6 +7,9 @@ humhub.module('client', function (module, require, $) {
var event = require('event');
var action = require('action');
var additions = require('ui.additions');
var view = require('ui.view');
var HEADER_VIEW_CONTEXT = 'HUMHUB-VIEW-CONTEXT';
/**
* Response Wrapper Object for easily accessing common data
@ -212,6 +215,15 @@ humhub.module('client', function (module, require, $) {
cfg = {'success': cfg};
}
var viewContext = cfg.viewContext || view.getViewContext();
if(viewContext) {
if(!cfg['headers']) {
cfg['headers'] = {};
}
cfg['headers'][HEADER_VIEW_CONTEXT] = viewContext
}
var requestXhr = null;
var promise = new Promise(function (resolve, reject) {
@ -368,30 +380,49 @@ humhub.module('client', function (module, require, $) {
return;
}
var state = $form.serialize();
$form.data('state', serializeFormState($form));
msg = msg || module.text('warn.onBeforeLoad');
$form.on('submit', function() {
$form.data('state', null);
offBeforeLoad();
});
// Note some browser do not support custom messages for this event.
$(window).on('beforeunload.humhub_client', function () {
if ($form.serialize() !== state) {
if (formStateChanged($form)) {
return msg;
}
});
$(document).on('pjax:beforeSend.humhub_client', function(evt) {
if ($form.serialize() !== state && !window.confirm(msg)) {
evt.preventDefault();
} else {
if(unloadForm($form, msg)) {
$form.data('state', null);
offBeforeLoad();
} else {
evt.preventDefault();
}
})
};
var serializeFormState = function($form) {
return $form.find(':not([data-prevent-statechange])').serialize();
};
var unloadForm = function($form, msg) {
return !formStateChanged($form) || confirmUnload(msg)
};
var formStateChanged = function($form) {
return $form.data('state') && $form.data('state') !== serializeFormState($form);
};
var confirmUnload = function(msg) {
msg = msg || module.text('warn.onBeforeLoad');
return window.confirm(msg)
};
var offBeforeLoad = function() {
$(window).off('beforeunload.humhub_client');
$(document).off('pjax:beforeSend.humhub_client');
@ -439,6 +470,7 @@ humhub.module('client', function (module, require, $) {
Response: Response,
onBeforeLoad: onBeforeLoad,
offBeforeLoad: offBeforeLoad,
unloadForm: unloadForm,
redirect: redirect
});
});

View File

@ -72,12 +72,15 @@ humhub.module('ui.form', function(module, require, $) {
});
this.$.on('submit', function() {
addValidationListener();
});
// activate the first tab or the tab with errors
$tabs.find('a[href="#tab-' + activeTab + '"]').tab('show');
this.$.fadeIn();
};
/**
* Prepares all included fieldsets for $form indexed
* by its label (legend).
@ -108,16 +111,16 @@ humhub.module('ui.form', function(module, require, $) {
/**
* Check for errors in a specific category.
* @param _object
* @param $fieldSet
* @returns {boolean}
*/
var _hasErrors = function($fieldSet) {
return $fieldSet.find('.error, .has-error').length > 0;
};
var init = function() {
var addValidationListener = function() {
// Make sure frontend validation also activates the tab with errors.
$(document).on('afterValidate.humhub:ui:tabbedForm', function(evt, messages, errors) {
$(document).off('afterValidate.humhub:ui:tabbedForm').one('afterValidate.humhub:ui:tabbedForm', function(evt, messages, errors) {
if (errors.length && Widget.exists('ui.form.TabbedForm')) {
var index = $(errors[0].container).closest('.tab-pane').data('tab-index');
$('a[href="#tab-' + index + '"]').tab('show');
@ -129,9 +132,13 @@ humhub.module('ui.form', function(module, require, $) {
evt.$trigger.closest('form').submit();
};
var unload = function() {
$(document).off('afterValidate.humhub:ui:tabbedForm');
};
module.export({
init: init,
sortOrder: 100,
unload: unload,
submit: submit,
TabbedForm: TabbedForm
});

View File

@ -189,7 +189,7 @@ humhub.module('ui.modal', function (module, require, $) {
Modal.prototype.load = function (url, cfg, originalEvent) {
var that = this;
var cfg = cfg || {};
cfg = setDefaultRequestData(cfg);
return new Promise(function (resolve, reject) {
if (!that.isVisible()) {
@ -205,7 +205,7 @@ humhub.module('ui.modal', function (module, require, $) {
Modal.prototype.post = function (url, cfg, originalEvent) {
var that = this;
var cfg = cfg || {};
cfg = setDefaultRequestData(cfg);
return new Promise(function (resolve, reject) {
if (!that.isVisible()) {
@ -218,6 +218,13 @@ humhub.module('ui.modal', function (module, require, $) {
});
};
var setDefaultRequestData = function(cfg) {
cfg = cfg || {};
cfg.data = cfg.data || {};
cfg.viewContext = cfg.viewContext || 'modal';
return cfg;
};
/**
* Sets an errormessage and title. This function either creates an standalone
* error modal with title and message, or adds/replaces a errorboxmessage to
@ -653,7 +660,7 @@ humhub.module('ui.modal', function (module, require, $) {
}
var modal = (id) ? module.get(id) : module.global;
return client.submit(evt, _defaultRequestOptions(evt, options)).then(function (response) {
return client.submit(evt, setDefaultRequestData(options)).then(function (response) {
if(response.success) {
modal.close();
} else {
@ -682,7 +689,7 @@ humhub.module('ui.modal', function (module, require, $) {
}
var modal = (id) ? module.get(id) : module.global;
return modal.load(evt, _defaultRequestOptions(evt, options))
return modal.load(evt, setDefaultRequestData(options))
.catch(function (err) {
module.log.error(err, true);
modal.close();
@ -700,7 +707,7 @@ humhub.module('ui.modal', function (module, require, $) {
}
var modal = (id) ? module.get(id) : module.global;
return modal.post(evt, _defaultRequestOptions(evt, options)).catch(function (err) {
return modal.post(evt, setDefaultRequestData(options)).catch(function (err) {
module.log.error(err, true);
modal.close();
});
@ -711,11 +718,6 @@ humhub.module('ui.modal', function (module, require, $) {
if(modal) {
modal.show();
}
}
var _defaultRequestOptions = function (evt, options) {
options = options || {};
return options;
};
var get = function (id, options) {

View File

@ -4,6 +4,8 @@ humhub.module('ui.view', function (module, require, $) {
var title;
var state = {};
var viewContext = null;
var prevSwipeDelay = false;
var prevSwipe = false;
@ -56,13 +58,26 @@ humhub.module('ui.view', function (module, require, $) {
setTimeout(initMobileSidebar, 50);
}
module.log.debug('Current view state', state);
module.log.debug('View state', state);
module.log.debug('View context', viewContext);
};
var unload = function() {
setViewContext(null);
};
var isSwipeAllowed = function() {
return !prevSwipeDelay && !prevSwipe;
};
var setViewContext = function(vctx) {
viewContext = vctx;
};
var getViewContext = function() {
return viewContext;
};
var isActiveScroll = function() {
return prevSwipeDelay;
};
@ -150,8 +165,21 @@ humhub.module('ui.view', function (module, require, $) {
return $topBar.position().top + $topBar.height();
};
var scrollToView = function() {
var theme = require('ui.theme', true);
if(object.isFunction(theme.getContentTop)) {
return theme.getContentTop();
}
var $topBar = $('#topbar-second');
return $topBar.position().top + $topBar.height();
};
module.export({
init: init,
unload: unload,
sortOrder: 100,
isSmall: isSmall,
preventSwipe: preventSwipe,
@ -164,6 +192,8 @@ humhub.module('ui.view', function (module, require, $) {
getContentTop: getContentTop,
// This function is called by controller itself
setState: setState,
getViewContext : getViewContext,
setViewContext: setViewContext,
getState: function () {
return $.extend({}, state);
},