diff --git a/controllers/PageController.php b/controllers/PageController.php index 387f726b..2c448e2b 100644 --- a/controllers/PageController.php +++ b/controllers/PageController.php @@ -15,6 +15,7 @@ use humhub\modules\wiki\permissions\CreatePage; use humhub\modules\wiki\permissions\EditPages; use humhub\modules\wiki\permissions\ViewHistory; +use humhub\modules\user\models\User; use Throwable; use Yii; use yii\base\Exception; @@ -22,6 +23,7 @@ use yii\db\StaleObjectException; use yii\web\HttpException; use yii\web\Response; +use DateTime; /** * PageController @@ -29,7 +31,9 @@ * @author luke */ class PageController extends BaseController -{ +{ + public const TTL = 300; + /** * @return $this|Response * @throws Exception @@ -211,6 +215,7 @@ public function actionEdit($id = null, $title = null, $categoryId = null) ]); } + $form->page->updateIsEditing(); return $this->renderSidebarContent('edit', $params); } @@ -335,7 +340,7 @@ public function actionDelete(int $id) } $page->delete(); - + $page->doneEditing(); return $this->redirect($this->contentContainer->createUrl('index')); } @@ -439,5 +444,160 @@ protected function getAccessRules() ]; } + /** + * @param int $id + * @return $this|Response + * @throws HttpException + */ + public function actionMerge(int $id) + { + $dateTime = new DateTime(); + $userIdentity = Yii::$app->user->identity->username; + + $page = $this->getWikiPage($id); + if (!$page) { + throw new HttpException(404, 'Wiki page not found!'); + } + + $form = (new PageEditForm(['container' => $this->contentContainer]))->forPage($id); + if (!$form->load(Yii::$app->request->post())) { + throw new HttpException(404); + } + + $submittedRevision = new WikiPageRevision(); + $submittedRevision->revision = time(); + $submittedRevision->content = $form->revision->content; + $submittedRevision->isCurrentlyEditing = true; + + $mergedRevision = $page->createRevision(); + $changedContentSepeartor = '**conflicting changes of '. $userIdentity .' from '. $dateTime->format('Y-m-d H:i:s').'**'; + $mergedRevision->content = $page->latestRevision->content.'

'.$changedContentSepeartor.'
'.$submittedRevision->content; + $mergedRevision->save(); + $page->doneEditing(); + + return $this->redirect(Url::toWiki($page)); + } + + /** + * @param int $id + * @return $this|Response + * @throws HttpException + */ + public function actionCreateCopy(int $id) + { + $userIdentity = Yii::$app->user->identity->username; + $dateTime = new DateTime(); + + $page = $this->getWikiPage($id); + if (!$page) { + throw new HttpException(404, 'Wiki page not found!'); + } + + $parentId = $page->parent_page_id; + + $form = (new PageEditForm(['container' => $this->contentContainer]))->forPage($id); + if (!$form->load(Yii::$app->request->post())) { + throw new HttpException(404); + } + + $childPage = new WikiPage(); + $childPage->title = $page->title.' conflicting copy of '. $userIdentity .' from '. $dateTime->format('Y-m-d H:i:s'); + $childPage->parent_page_id = $parentId; + $childPage->content->contentcontainer_id= $page->content->contentcontainer_id; + + if (!$childPage->save()) { + throw new HttpException(500, 'Failed to create the child page!'); + } + + if (!$childPage->id) { + throw new HttpException('Child page ID is not available after saving.'); + } + + $revision = new WikiPageRevision(); + $revision->content = $form->revision->content; + $revision->wiki_page_id = $childPage->id; + $revision->revision = 1; + $revision->user_id = Yii::$app->user->id; + + if (!$revision->save()) { + throw new HttpException('Failed to add content to the child page.'); + } + + $page->doneEditing(); + + return $this->redirect(Url::toWiki($page)); + } + + /** + * @param int $id + * @return $this|Response + * @throws HttpException + */ + public function actionEditingStatus(int $id) + { + $page = $this->getWikiPage($id); + if (!$page) { + throw new HttpException(404, 'Wiki page not found!'); + } + + $user = User::find()->where(['username' => $page->is_currently_editing])->one(); + if($user) { + $firstName = $user->profile->firstname; + $lastName = $user->profile->lastname; + $fullName = $firstName.' '.$lastName.' ('.$page->is_currently_editing.')'; + } + else { $fullName = ''; } + + return $this->asJson([ + 'success' => true, + 'isEditing' => $page->isEditing(), + 'body' => $fullName .' '. Yii::t('WikiModule.base', 'is already editing.
Editing it would cause conflict. Do you really want to continue?'), + ]); + } + + /** + * @return $this|Response + */ + public function actionEditingTimerUpdate(int $id = null) + { + $conflictingEditing = false; + $page = $this->getWikiPage($id); + if (!$page) { + return $this->asJson([ + 'sucess' => false, + ]); + } + + $user = Yii::$app->user->identity->username; + + $editingUser = User::find()->where(['username' => $page->is_currently_editing])->one(); + if($editingUser) { + $firstName = $editingUser->profile->firstname; + $lastName = $editingUser->profile->lastname; + $fullName = $firstName.' '.$lastName.' ('.$page->is_currently_editing.')'; + } + else { $fullName = ''; } + + if ($page->is_currently_editing == NULL) { + $page->updateIsEditing(); + } + + if ($page->is_currently_editing == $user) { + $page->updateEditingTime(); + } + elseif (time() - $page->editing_started_at < self::TTL) { + $conflictingEditing = true; + } + + return $this->asJson([ + 'success' => true, + 'conflictingEditing' => $conflictingEditing, + 'url' => Url::toWiki($page), + 'header' => Yii::t('WikiModule.base', 'Confirm Edit'), + 'body' => $fullName .' '. Yii::t('WikiModule.base', 'is already editing.
Editing it would cause conflict. Do you really want to continue?'), + 'confirmText' => Yii::t('WikiModule.base', 'Cancel'), + 'cancelText' => Yii::t('WikiModule.base', 'Continue'), + ]); + } } diff --git a/helpers/Url.php b/helpers/Url.php index 8aba1d31..45755d3b 100644 --- a/helpers/Url.php +++ b/helpers/Url.php @@ -31,6 +31,10 @@ class Url extends \yii\helpers\Url public const ROUTE_WIKI_DIFF_EDITING = '/wiki/page/diff-editing'; public const ROUTE_WIKI_REVERT = '/wiki/page/revert'; public const ROUTE_EXTRACT_TITLES = '/wiki/page/headlines'; + public const ROUTE_WIKI_MERGE = '/wiki/page/merge'; + public const ROUTE_WIKI_CREATE_COPY = '/wiki/page/create-copy'; + public const ROUTE_WIKI_EDITING_STATUS = '/wiki/page/editing-status'; + public const ROUTE_WIKI_EDITING_TIMER_UPDATE = '/wiki/page/editing-timer-update'; public static function toHome(ContentContainerActiveRecord $container) { @@ -129,4 +133,24 @@ public static function toWikiEntry(WikiPage $page) return static::to([static::ROUTE_WIKI_ENTRY, 'id' => $page->id, 'container' => $page->content->container]); } + public static function toWikiMerge(WikiPage $page) + { + return static::to([static::ROUTE_WIKI_MERGE, 'id' => $page->id, 'container' => $page->content->container]); + } + + public static function toWikiCreateCopy(WikiPage $page) + { + return static::to([static::ROUTE_WIKI_CREATE_COPY, 'id' => $page->id, 'container' => $page->content->container]); + } + + public static function toWikiEditingStatus(WikiPage $page) + { + return static::to([static::ROUTE_WIKI_EDITING_STATUS, 'id' => $page->id, 'container' => $page->content->container]); + } + + public static function toWikiEditingTimerUpdate(WikiPage $page) + { + return static::to([static::ROUTE_WIKI_EDITING_TIMER_UPDATE, 'id' => $page->id, 'container' => $page->content->container]); + } + } diff --git a/messages/de/base.php b/messages/de/base.php index b7c00dad..12ebd921 100644 --- a/messages/de/base.php +++ b/messages/de/base.php @@ -3,7 +3,7 @@ return [ '({n,plural,=1{+1 subpage}other{+{count} subpages}})' => '({n,plural,=1{+1 Unterseite}other{+{count} Unterseiten}})', 'Confirm page reverting' => 'Seite rückgängig machen bestätigen', - 'Warning!

Another user has updated this page since you have started editing it. Please confirm that you want to overwrite those changes.
:linkToCompare' => 'Warnung!

Ein anderer Benutzer hat diese Seite gespeichert, seit mit der Bearbeitung begonnen wurde. Sollen diese Änderungen überschreiben werden?
:linkToCompare', + 'Warning!

Another user has updated this page since you have started editing it. Please confirm that you want to overwrite those changes.
:linkToCompare' => 'Warnung!

Ein anderer Benutzer bearbeitet gerade diese Seite. Durch eine parallele Bearbeitung kann es zu Konflikten oder Datenverlust kommen. Möchten Sie trotzdem fortfahren?
:linkZuVergleichen', 'Wiki link' => 'Wiki-Verweis', 'Wiki module configuration' => 'Wiki Modulkonfiguration', 'Add Page' => 'Seite hinzufügen', @@ -86,4 +86,10 @@ 'Hide Navigation Entries of this module globally by default' => '', 'Hide Navigation Entry' => '', 'No wiki pages found.' => '', + 'Merge' => 'Zusammenführen', + 'Create Copy' => 'Kopie erstellen', + 'Confirm Page Edit' => 'Seite wird gerade bearbeitet', + 'Continue' => 'Weiter', + 'Confirm Edit' => 'Bearbeitung bestätigen', + 'is already editing.
Editing it would cause conflict. Do you really want to continue?' => 'bearbeitet diese Seite.
Durch die Bearbeitung kommt es zu einem Dateikonflikt. Trotzdem fortfahren?', ]; diff --git a/migrations/m250203_094213_is_currently_editing.php b/migrations/m250203_094213_is_currently_editing.php new file mode 100644 index 00000000..c6ba194c --- /dev/null +++ b/migrations/m250203_094213_is_currently_editing.php @@ -0,0 +1,26 @@ +addColumn('wiki_page', 'is_currently_editing', $this->string(50)->null()); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropColumn('wiki_page', 'is_currently_editing'); + + } +} diff --git a/migrations/m250219_192950_editing_started_at.php b/migrations/m250219_192950_editing_started_at.php new file mode 100644 index 00000000..60bac647 --- /dev/null +++ b/migrations/m250219_192950_editing_started_at.php @@ -0,0 +1,26 @@ +addColumn('wiki_page', 'editing_started_at', $this->integer()->null()); + } + + /** + * {@inheritdoc} + */ + public function safeDown() + { + $this->dropColumn('wiki_page', 'editing_started_at'); + } + +} diff --git a/models/WikiPage.php b/models/WikiPage.php index ea6ce769..85308a9e 100644 --- a/models/WikiPage.php +++ b/models/WikiPage.php @@ -28,6 +28,8 @@ * @property int $sort_order * @property int $is_container_menu * @property int $container_menu_order + * @property string|null $is_currently_editing + * @property int|null $editing_started_at * * @property-read WikiPage|null $categoryPage * @property-read WikiPageRevision $latestRevision @@ -42,6 +44,7 @@ class WikiPage extends ContentActiveRecord implements Searchable public const SCENARIO_EDIT = 'edit'; public const CACHE_CHILDREN_COUNT_KEY = 'wikiChildrenCount_%s'; public $moduleId = 'wiki'; + public const TTL = 300; /** * @inheritdoc */ @@ -540,4 +543,48 @@ public function getIsCategory(): bool return $this->_isCategory; } + + /** + * Function to check if any other user is currently editing the page + * + * @return bool + */ + public function isEditing() { + $user = Yii::$app->user->identity->username; + + if ($this->is_currently_editing == NULL || $this->is_currently_editing == $user) { + return false; + } + + if (time() - $this->editing_started_at > self::TTL) { + $this->doneEditing(); + return false; + } + + return true; + } + + /** + * Function to update the Attribute value to the new user in the edit page + */ + public function updateIsEditing() { + $user = Yii::$app->user->identity->username; + if ($this->is_currently_editing == NULL) { + $this->updateAttributes(['is_currently_editing' => $user, 'editing_started_at' => time()]); + } + } + + /** + * Function to make the editing attributes null to show that no user is currently editing the page. + */ + public function doneEditing() { + $this->updateAttributes(['is_currently_editing' => new Expression('NULL'), 'editing_started_at' => new Expression('NULL')]); + } + + /** + * Function to update the time stamp i.e editing_started_at + */ + public function updateEditingTime() { + $this->updateAttributes(['editing_started_at' => time()]); + } } diff --git a/models/forms/PageEditForm.php b/models/forms/PageEditForm.php index d6c7a831..112d6be8 100644 --- a/models/forms/PageEditForm.php +++ b/models/forms/PageEditForm.php @@ -241,6 +241,8 @@ public function save() $this->page->content->hidden = $this->hidden; } + $this->page->doneEditing(); + return WikiPage::getDb()->transaction(function ($db) { if ($this->page->save()) { $this->revision->wiki_page_id = $this->page->id; diff --git a/resources/js/humhub.wiki.Form.js b/resources/js/humhub.wiki.Form.js index a4ab0b97..2a988b99 100644 --- a/resources/js/humhub.wiki.Form.js +++ b/resources/js/humhub.wiki.Form.js @@ -2,6 +2,11 @@ humhub.module('wiki.Form', function(module, require, $) { var Widget = require('ui.widget').Widget; var wikiView = require('wiki'); var additions = require('ui.additions'); + var client = require('client'); + var modal = require('ui.modal'); + + var editPollingInterval = 5000; + var editPollingTimer = null; /** * This widget represents the wiki form @@ -39,6 +44,8 @@ humhub.module('wiki.Form', function(module, require, $) { } }); } + checkValidUser(); + startEditPolling(); }; Form.prototype.getRichtextMenu = function() { @@ -74,9 +81,56 @@ humhub.module('wiki.Form', function(module, require, $) { }, 500); }; + Form.prototype.openUrlLink = function(evt) { + var form = this.$; + form.attr('action', evt.$trigger.data('action-click-url')) + .submit(); + } + Form.submit = function () { + if (editPollingTimer) { + clearInterval(editPollingTimer); + } $('form[data-ui-widget="wiki.Form"]').submit(); }; + function pollTimerEditingStatus() { + var url = document.querySelector('[data-url-editing-timer-update]').getAttribute('data-url-editing-timer-update'); + + client.get(url).then(function(response) { + }).catch(function(e) { + module.log.error(e, true); + }); + } + + function startEditPolling() { + if (editPollingTimer) { + clearInterval(editPollingTimer); + } + editPollingTimer = setInterval(pollTimerEditingStatus, editPollingInterval); + } + + function checkValidUser() { + var url = document.querySelector('[data-url-editing-timer-update]').getAttribute('data-url-editing-timer-update'); + client.get(url).then(function(response) { + if(response.success&&response.conflictingEditing) { + var options = { + 'header': response.header, + 'body': response.body, + 'confirmText': response.confirmText, + 'cancelText' : response.cancelText, + }; + + modal.confirm(options).then(function ($confirmed) { + if ($confirmed) { + client.redirect(response.url); + } + }); + } + }).catch(function(e) { + module.log.error(e, true); + }) + } + module.export = Form; }); diff --git a/resources/js/humhub.wiki.js b/resources/js/humhub.wiki.js index 8a7ab3f6..6ab6de39 100644 --- a/resources/js/humhub.wiki.js +++ b/resources/js/humhub.wiki.js @@ -4,8 +4,11 @@ humhub.module('wiki', function(module, require, $) { var view = require('ui.view'); var client = require('client'); var loader = require('ui.loader'); + var modal = require('ui.modal'); var stickyElementSettings = []; + var pollingInterval = 5000; + var pollingTimer = null; var registerStickyElement = function($node, $trigger, condition) { stickyElementSettings.push({$node: $node, $trigger: $trigger, condition: condition}); @@ -50,6 +53,7 @@ humhub.module('wiki', function(module, require, $) { call: () => $(window).width() < 768 }); } + startPolling(); }; Content.prototype.loader = function (show) { @@ -126,12 +130,60 @@ humhub.module('wiki', function(module, require, $) { event.off('humhub:content:afterMove.wiki'); }; + var confirmEditing = function(evt) { + var editUrl = evt.$trigger.data('action-click-url'); + client.redirect(editUrl); + }; + + function pollEditingStatus() { + if (document.querySelector('[data-url-editing-status]') == null) { + if (pollingTimer) { + clearInterval(pollingTimer); + } + return; + } + var url = document.querySelector('[data-url-editing-status]').getAttribute('data-url-editing-status'); + + client.get(url).then(function(response) { + if (response.success && response.isEditing) { + openEditingDialog(response.body); + } else { + closeEditingDialog(); + } + }).catch(function(e) { + module.log.error(e, true); + }); + } + + function openEditingDialog(body) { + var button = document.querySelector('[data-url-editing-status]'); + button.setAttribute('data-action-confirm', body); + + } + + function closeEditingDialog() { + var button = document.querySelector('[data-url-editing-status]'); + if (button.getAttribute('data-action-confirm') != null) { + button.removeAttribute('data-action-confirm'); + client.reload(); + } + } + + function startPolling() { + if (pollingTimer) { + clearInterval(pollingTimer); + } + pollingTimer = setInterval(pollEditingStatus, pollingInterval); + } + module.export({ Content: Content, toAnchor: toAnchor, revertRevision: revertRevision, actionDelete: actionDelete, registerStickyElement: registerStickyElement, - unload: unload + unload: unload, + confirmEditing: confirmEditing, + startPolling: startPolling, }) }); diff --git a/tests/codeception/functional/EditingConflictCest.php b/tests/codeception/functional/EditingConflictCest.php new file mode 100644 index 00000000..f1bd3d0e --- /dev/null +++ b/tests/codeception/functional/EditingConflictCest.php @@ -0,0 +1,190 @@ +wantTo('Ensure merging wiki page revisions works correctly'); + $space = $I->loginBySpaceUserGroup(Space::USERGROUP_ADMIN); + $I->amAdmin(true); + $I->enableModule($space->guid, 'wiki'); + $page = $I->createWiki($space, 'Test Page', 'Initial content'); + + $I->sendAjaxPostRequest(Url::toWikiMerge($page), [ + 'WikiPage' => ['title' => 'Updated title'], + 'WikiPageRevision' => ['content' => 'Updated content.'], + 'PageEditForm' => ['latestRevisionNumber' => '1234567890'],]); + + $I->amOnSpace($space->guid, '/wiki/page/view', ['id' => $page->id, 'title' => $page->title]); + $I->see('Initial content'); + $I->see('conflicting changes'); + $I->see('Updated content'); + + } + + public function testCreateCopyFunctionality(FunctionalTester $I) + { + $I->wantTo('Ensure Creating copy of wiki page revisions works correctly'); + $space = $I->loginBySpaceUserGroup(Space::USERGROUP_ADMIN); + $I->amAdmin(true); + $I->enableModule($space->guid, 'wiki'); + $page = $I->createWiki($space, 'Test Page', 'Initial content'); + + $I->sendAjaxPostRequest(Url::toWikiCreateCopy($page), [ + 'WikiPage' => ['title' => 'Test Page'], + 'WikiPageRevision' => ['content' => 'Updated content.'], + 'PageEditForm' => ['latestRevisionNumber' => '1234567890'],]); + + $I->amOnSpace($space->guid, '/wiki/overview'); + $I->see('Test page conflicting copy of'); + + $I->amOnSpace($space->guid, '/wiki/page/view', ['id' => 2]); + $I->see('Test page conflicting copy of'); + $I->see('Updated content.'); + $I->dontSee('Initial content'); + } + + + public function testEditingStatusSameUser(FunctionalTester $I) + { + $I->wantTo('Check the editing status of an existing Wiki page when access by same user'); + $space = $I->loginBySpaceUserGroup(Space::USERGROUP_ADMIN); + $I->amAdmin(true); + $I->enableModule($space->guid, 'wiki'); + $page = $I->createWiki($space, 'Test Page', 'Initial content'); + + $I->amOnSpace($space->guid, '/wiki/page/editing-status', ['id' => $page->id]); + + $I->seeResponseCodeIs(200); + + $I->seeInSource('"success":true'); + $I->seeInSource('"isEditing":false'); + $I->seeInSource('"user":null'); + + $I->amOnSpace($space->guid, '/wiki/page/edit', ['id' => $page->id]); + $I->amOnSpace($space->guid, '/wiki/page/editing-status', ['id' => $page->id]); + $I->seeResponseCodeIs(200); + + $I->seeInSource('"success":true'); + $I->seeInSource('"isEditing":false'); + $I->seeInSource('"user":"admin"'); + + $I->amOnSpace($space->guid, '/wiki/overview'); + $I->sendAjaxPostRequest(Url::toWikiDelete($page)); + $I->seeSuccessResponseCode(); + + $I->amOnSpace($space->guid, '/wiki/page/editing-status', ['id' => $page->id]); + + $I->seeResponseCodeIs(404); + + } + + public function testEditingStatusSave(FunctionalTester $I) + { + $I->wantTo('Check the editing status of an existing Wiki page when access by same user'); + $space = $I->loginBySpaceUserGroup(Space::USERGROUP_ADMIN); + $I->amAdmin(true); + $I->enableModule($space->guid, 'wiki'); + $page = $I->createWiki($space, 'Test Page', 'Initial content'); + + $I->amOnSpace($space->guid, '/wiki/page/edit', ['id' => $page->id]); + $I->saveWiki(); + $I->amOnSpace($space->guid, '/wiki/page/editing-status', ['id' => $page->id]); + $I->seeResponseCodeIs(200); + + $I->seeInSource('"success":true'); + $I->seeInSource('"isEditing":false'); + $I->seeInSource('"user":null'); + + } + + public function testEditingStatusDiffUser(FunctionalTester $I) + { + $I->wantTo('Check the editing status of an existing Wiki page when access by different user'); + $space = $I->loginBySpaceUserGroup(Space::USERGROUP_MODERATOR); + $I->enableModule($space->guid, 'wiki'); + $page = $I->createWiki($space, 'Test Page', 'Initial content'); + + $page->updateAttributes(['is_currently_editing' => 'admin', 'editing_started_at' => time()]); + $I->amOnSpace($space->guid, '/wiki/page/editing-status', ['id' => $page->id]); + + $I->seeResponseCodeIs(200); + + $I->seeInSource('"success":true'); + $I->seeInSource('"isEditing":true'); + $I->seeInSource('"user":"admin"'); + + } + + public function testEditingStatusPageNotFound(FunctionalTester $I) + { + $I->wantTo('Check the editing status of an non-existing Wiki page when access by different user'); + $space = $I->loginBySpaceUserGroup(Space::USERGROUP_ADMIN); + $I->amAdmin(true); + $I->enableModule($space->guid, 'wiki'); + $I->amOnSpace($space->guid, '/wiki/page/editing-status', ['id' => 123456789]); + $I->seeResponseCodeIs(404); + } + + public function testEditingTTLSameUser(FunctionalTester $I) + { + $I->wantTo('Check the editing TTL status of a Wiki page when access by same user'); + $space = $I->loginBySpaceUserGroup(Space::USERGROUP_ADMIN); + $I->amAdmin(true); + $I->enableModule($space->guid, 'wiki'); + $page = $I->createWiki($space, 'Test Page', 'Initial content'); + + + $I->amOnSpace($space->guid, '/wiki/page/editing-timer-update', ['id' => $page->id]); + + $I->seeResponseCodeIs(200); + + $I->seeInSource('"success":true'); + $I->seeInSource('"user":"admin"'); + $I->seeInSource('"unAuthorizedLogin":false'); + + } + + public function testEditingTTLDiffUser(FunctionalTester $I) + { + $I->wantTo('Check the editing TTL status of a Wiki page when access by different user'); + $space = $I->loginBySpaceUserGroup(Space::USERGROUP_MODERATOR); + $I->enableModule($space->guid, 'wiki'); + $page = $I->createWiki($space, 'Test Page', 'Initial content'); + + $page->updateAttributes(['is_currently_editing' => 'admin', 'editing_started_at' => time()]); + $I->amOnSpace($space->guid, '/wiki/page/editing-timer-update', ['id' => $page->id]); + + $I->seeResponseCodeIs(200); + + $I->seeInSource('"success":true'); + $I->seeInSource('"user":"admin"'); + $I->seeInSource('"unAuthorizedLogin":true'); + + } + +} \ No newline at end of file diff --git a/views/page/edit.php b/views/page/edit.php index 28e31293..9cd9e848 100644 --- a/views/page/edit.php +++ b/views/page/edit.php @@ -15,6 +15,7 @@ use humhub\modules\wiki\widgets\WikiPagePicker; use humhub\modules\wiki\widgets\WikiPath; use humhub\widgets\Button; +use humhub\modules\wiki\helpers\Url; use humhub\modules\topic\widgets\TopicPicker; /* @var $this View */ @@ -47,7 +48,7 @@ ]) ?> -
getTitle() ?>
+
page);?>>getTitle() ?>
submit() ?> + action('openUrlLink', Url::toWikiMerge($model->page))?> + + action('openUrlLink', Url::toWikiCreateCopy($model->page))?> + action('backOverwriting')->icon('back')->loader(false); ?>
diff --git a/widgets/WikiMenu.php b/widgets/WikiMenu.php index 1d9a0fd6..219a68b8 100644 --- a/widgets/WikiMenu.php +++ b/widgets/WikiMenu.php @@ -280,6 +280,13 @@ private function getLink($link) 'label' => Yii::t('WikiModule.base', 'Edit'), 'url' => Url::toWikiEdit($this->object), 'icon' => 'fa-pencil', + 'htmlOptions' => [ + 'data-url-editing-status' => Url::toWikiEditingStatus($this->object), + 'data-action-click' => 'wiki.confirmEditing', + 'data-action-click-url' => Url::toWikiEdit($this->object), + 'data-action-confirm-header' => Yii::t('WikiModule.base', 'Confirm Edit'), + 'data-action-confirm-text' => Yii::t('WikiModule.base', 'Continue'), + ], ]) : null; case static::LINK_HISTORY: return $this->canViewHistory() ? new MenuLink([