Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
05a51dd
#1166 Make it optional to require edit permission for parent data in …
yisaka117 Jun 19, 2025
715712a
#1627 Fix sort in summary view
ex1nhatvh Jul 24, 2025
b7964a0
add parents option to get custom_value api
yisaka117 Aug 13, 2025
8d44b17
add a consistency check when executing workflow actions
yisaka117 Sep 11, 2025
47df561
Revert "add a consistency check when executing workflow actions"
yisaka117 Sep 11, 2025
3918b05
Add scroll position fix script
ex1sudc Dec 10, 2025
6987e85
Prevent null error in MailSendJob by passing user ID instead of object
ex1anhth Dec 10, 2025
29edfc1
Sort and limit stored scroll positions by timestamp
ex1sudc Dec 15, 2025
f3152f7
Enhance scroll position restoration and validation logic
ex1sudc Dec 15, 2025
cedab8a
Refactor scroll position handling
ex1sudc Dec 16, 2025
cc94dfe
Merge remote-tracking branch 'remotes/origin/master' into hotfix
vohoangnhat Jan 5, 2026
4691a93
Merge remote-tracking branch 'origin/master' into hotfixfeature/issue…
ex1sudc Jan 5, 2026
7ad9c3f
Merge remote-tracking branch 'origin/master' into feature/issue_1166
ex1sudc Jan 9, 2026
a2e026f
Merge pull request #1664 from exceedone/feature/issue_1166
KajitoriAdmin Jan 15, 2026
df570c5
Merge remote-tracking branch 'remotes/origin/master' into feature/add…
vohoangnhat Jan 20, 2026
78a15ce
Merge pull request #1665 from exceedone/feature/add_parent_option_to_api
KajitoriAdmin Jan 20, 2026
ff18320
Merge pull request #1661 from exceedone/hotfixfeature/issue_1627
KajitoriAdmin Jan 20, 2026
4ad705c
Merge pull request #1658 from exceedone/hotfixfeature/issues_1622
KajitoriAdmin Jan 20, 2026
7862a1a
Merge pull request #1659 from exceedone/hotfixfeature/bugfix_issue1626
KajitoriAdmin Jan 20, 2026
8987e49
Scroll restoration handling
vohoangnhat Jan 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions public/vendor/exment/js/scroll-restore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* Scroll position restore for custom table list view
* Handles scroll restoration on browser back button and pjax navigation
*/
var Exment;
(function (Exment) {
'use strict';

const CLASSNAME_CUSTOM_VALUE_GRID = '.block_custom_value_grid';

// Save current scroll position
function saveCurrentScrollPosition() {
const scrollTop = $(window).scrollTop() || document.documentElement.scrollTop || document.body.scrollTop || 0;

// Store in history state if available
if (window.history && window.history.replaceState) {
const currentState = window.history.state || {};
currentState.scrollTop = scrollTop;
try {
window.history.replaceState(currentState, document.title);
} catch (e) {
console.warn('Failed to update history state:', e);
}
}
}

// Restore scroll position from history state
function restoreScrollPosition() {
let scrollTop = 0;

// Only restore from history state (for back button)
if (window.history && window.history.state && typeof window.history.state.scrollTop === 'number') {
scrollTop = window.history.state.scrollTop;
}

if (scrollTop > 0) {
// Use setTimeout to ensure DOM is ready
setTimeout(function() {
window.scrollTo(0, scrollTop);
// Verify and retry if needed
setTimeout(function() {
const currentScroll = window.pageYOffset || document.documentElement.scrollTop;
if (Math.abs(currentScroll - scrollTop) > 50) {
window.scrollTo(0, scrollTop);
}
}, 100);
}, 0);
}
}

// ScrollRestore module
Exment.ScrollRestore = {
init: function() {
// Prevent multiple initialization
if (window.exmentScrollRestoreInitialized) {
return;
}
window.exmentScrollRestoreInitialized = true;

// Disable browser's default scroll restoration
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
}

restoreScrollPosition();

// Save scroll position periodically while scrolling
let scrollTimer;
$(window).on('scroll', function() {
clearTimeout(scrollTimer);
scrollTimer = setTimeout(function() {
// Only save if we're on a list page (grid view)
if ($(CLASSNAME_CUSTOM_VALUE_GRID).length > 0) {
saveCurrentScrollPosition();
}
}, 150);
});

// Save scroll position before leaving page
$(window).on('beforeunload', function() {
if ($(CLASSNAME_CUSTOM_VALUE_GRID).length > 0) {
saveCurrentScrollPosition();
}
});

// Handle pjax events
if ($.pjax) {
let isPjaxPopstate = false;

$(document).on('pjax:popstate', function() {
isPjaxPopstate = true;
});

// Save scroll position before pjax request
$(document).on('pjax:send', function(event, xhr, options) {
if ($(CLASSNAME_CUSTOM_VALUE_GRID).length > 0) {
saveCurrentScrollPosition();
}
});

// Handle scroll position after pjax complete
$(document).on('pjax:end', function(event, xhr, options) {
if ($(CLASSNAME_CUSTOM_VALUE_GRID).length > 0) {
if (isPjaxPopstate) {
// If it's a back/forward navigation, restore scroll
restoreScrollPosition();
isPjaxPopstate = false;
} else {
// If it's a new navigation (pagination, sort, etc), scroll to top
window.scrollTo(0, 0);
}
}
});
}

// Save position when clicking on links in grid
$(CLASSNAME_CUSTOM_VALUE_GRID).on('click', 'a', function() {
saveCurrentScrollPosition();
});
}
};

// Auto-initialize on document ready
$(function() {
Exment.ScrollRestore.init();
});

})(Exment || (Exment = {}));
2 changes: 2 additions & 0 deletions resources/lang/en/exment.php
Original file line number Diff line number Diff line change
Expand Up @@ -1196,6 +1196,7 @@
'all_user_viewable_flg' => 'All Users Can View',
'all_user_accessable_flg' => 'All Users Can Access',
'inherit_parent_permission' => 'Viewable with Permissions of Parent data',
'editable_with_parent' => 'Need Edit Permission for Parend data',
'add_parent_menu_flg' => 'Add Menu',
'add_notify_flg' => 'Add To Notification',
'add_parent_menu' => 'Target Parent Menu',
Expand Down Expand Up @@ -1278,6 +1279,7 @@
'all_user_viewable_flg' => 'If set to YES, all users will be able to view all the data in this table.',
'all_user_accessable_flg' => 'If set to YES, all users will be able to see all the data in this table.<br/>*It is not displayed on the menu or list page, it can be displayed only with internal data or reference from another table.',
'inherit_parent_permission' => 'If set to YES, the data can be viewed based on the the parent data(1:N) permissions. However, you must have viewing or similar permissions for this table itself.',
'editable_with_parent' => 'If set to YES, you need edit permissions for the parent data(1:N) to edit the data in this table.',
'add_parent_menu_flg' => 'After creating custom table, you can add it to the menu. To add it, please set it to YES. *It will be displayed after updating the browser. <br /> *It can be set only when new table is created. When updating please set it from "Menu" page.',
'add_parent_menu' => 'Please select the menu name to be parent.',
'add_notify_flg' => 'You can add settings for performing in-system notification to authorized users when creating/updating/sharing/commenting data, after creating a new table. Please add YES if you want to add.<br/>* It can be set only when creating a new table. Please set from the "notification" page when updating.',
Expand Down
2 changes: 2 additions & 0 deletions resources/lang/ja/exment.php
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,7 @@
'all_user_viewable_flg' => '全ユーザーが閲覧可能',
'all_user_accessable_flg' => '全ユーザーが参照可能',
'inherit_parent_permission' => '親データの権限で閲覧可能',
'editable_with_parent' => '親データの編集権限が必要',
'add_parent_menu_flg' => 'メニューに追加する',
'add_notify_flg' => '通知に追加する',
'add_parent_menu' => '追加先の親メニュー',
Expand Down Expand Up @@ -1279,6 +1280,7 @@
'all_user_viewable_flg' => 'YESにした場合、すべてのユーザーが、このテーブルのすべてのデータを閲覧可能になります。',
'all_user_accessable_flg' => 'YESにした場合、すべてのユーザーが、このテーブルのすべてのデータを参照可能になります。<br/>※メニューや一覧画面では表示されず、内部データや、他のテーブルからの参照でのみ表示できます。',
'inherit_parent_permission' => 'YESにした場合、親データ(1対多)の権限でデータを閲覧できます。ただし、このテーブル自体に担当者の閲覧等の権限を持っている必要があります。',
'editable_with_parent' => 'YESにした場合、このテーブルのデータを編集するには親データ(1対多)の編集権限が必要になります。',
'add_parent_menu_flg' => '新規作成後、メニューに追加することができます。追加する場合はYESにしてください。<br/>※ブラウザ更新後に表示されます。<br />※テーブルの新規作成時のみ設定できます。更新時は「メニュー」画面より設定してください。',
'add_parent_menu' => '親にするメニュー名を選択してください。',
'add_notify_flg' => 'データの新規作成・更新・共有、コメント時に、権限のあるユーザーに、システム内通知を行う設定を、テーブルの新規作成後に追加することができます。追加する場合はYESにしてください。<br/>※テーブルの新規作成時のみ設定できます。更新時は「通知」画面より設定してください。',
Expand Down
61 changes: 61 additions & 0 deletions src/Controllers/ApiDataTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use Exceedone\Exment\Enums\SearchType;
use Exceedone\Exment\Enums\ValueType;
use Exceedone\Exment\Enums\ErrorCode;
use Exceedone\Exment\Enums\RelationType;
use Validator;

/**
Expand Down Expand Up @@ -202,6 +203,7 @@ protected function modifyAfterGetValue(Request $request, $target, $options = [])
'id',
'target_view_id',
'children',
'parents',
]),
$options['appends']
);
Expand Down Expand Up @@ -267,6 +269,11 @@ protected function modifyCustomValue(Request $request, $custom_value, $recursive
$custom_value = $this->modifyChildrenValue($request, $custom_value);
}

// Change relation key name
if (!$recursive && $request->has('parents') && boolval($request->get('parents'))) {
$custom_value = $this->modifyParentsValue($request, $custom_value);
}

// convert to custom values
$valuetype = $request->get('valuetype');
if ($request->has('valuetype') && ValueType::isRegetApiCustomValue($valuetype)) {
Expand Down Expand Up @@ -386,10 +393,22 @@ protected function setQueryInfo($query)
$query->with($relation->getRelationName());
}
}
if ($request->has('parents') && boolval($request->get('parents'))) {
$relations = CustomRelation::getRelationsByChild($this->custom_table);
foreach ($relations as $relation) {
$query->with($relation->getRelationName());
}
}

return $query;
}

/**
* Modify children values (exclude virtual columns from children values)
*
* @param Request $request
* @return CustomValue $custom_value
*/
protected function modifyChildrenValue(Request $request, $custom_value)
{
$relations = CustomRelation::getRelationsByParent($this->custom_table);
Expand Down Expand Up @@ -417,4 +436,46 @@ protected function modifyChildrenValue(Request $request, $custom_value)
$custom_value['children'] = $results;
return $custom_value;
}

/**
* Modify parents values (exclude virtual columns from parents values)
*
* @param Request $request
* @return CustomValue $custom_value
*/
protected function modifyParentsValue(Request $request, $custom_value)
{
$relations = CustomRelation::getRelationsByChild($this->custom_table);

$results = [];
foreach ($relations as $relation) {
// If getted relation name, change key name
$reltionName = $relation->getRelationName();
if (array_has($custom_value, $reltionName)) {
$relationValues = $custom_value[$reltionName];
$makeHiddenArray = $relation->parent_custom_table_cache->getMakeHiddenArray();

if ($relation->relation_type == RelationType::ONE_TO_MANY) {
// Call makehidden
$relationValues = $relationValues->makeHidden($makeHiddenArray);
// Call modify custom value
$relationValues = $this->modifyCustomValue($request, $relationValues, true);
} else {
$relationValues = $relationValues->map(function ($relationValue) use ($makeHiddenArray, $request) {
// Call makehidden
$relationValue = $relationValue->makeHidden($makeHiddenArray);
// Call modify custom value
$relationValue = $this->modifyCustomValue($request, $relationValue, true);
return $relationValue;
});
}
// Set key name
$results[$relation->parent_custom_table_cache->table_name] = $relationValues;
unset($custom_value[$reltionName]);
}
}

$custom_value['parents'] = $results;
return $custom_value;
}
}
3 changes: 3 additions & 0 deletions src/Controllers/CustomTableController.php
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ protected function form($id = null)
if ($has_parent) {
$form->switchbool('inherit_parent_permission', exmtrans("custom_table.inherit_parent_permission"))->help(exmtrans("custom_table.help.inherit_parent_permission"))
->default("0");

$form->switchbool('editable_with_parent', exmtrans("custom_table.editable_with_parent"))->help(exmtrans("custom_table.help.editable_with_parent"))
->default("1");
}
})->disableHeader();

Expand Down
16 changes: 12 additions & 4 deletions src/DataItems/Grid/DefaultGrid.php
Original file line number Diff line number Diff line change
Expand Up @@ -558,10 +558,18 @@ protected function manageRowAction($grid)
$enableCreate = false;
}

if (!is_null($parent_value = $actions->row->getParentValue(null, true)) && $parent_value->enableEdit(true) !== true) {
$enableCreate = false;
$enableEdit = false;
$enableDelete = false;
if (!is_null($parent_value = $actions->row->getParentValue(null, true))) {
if (boolval($custom_table->getOption('editable_with_parent')??1)) {
if ($parent_value->enableEdit(true) !== true) {
$enableCreate = false;
$enableEdit = false;
$enableDelete = false;
}
} elseif ($parent_value->enableAccess() !== true) {
$enableCreate = false;
$enableEdit = false;
$enableDelete = false;
}
}

if (!$enableEdit) {
Expand Down
13 changes: 10 additions & 3 deletions src/DataItems/Show/DefaultShow.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,16 @@ public function createShowForm()
$tools->disableList();
}

if (!is_null($parent_value = $this->custom_value->getParentValue(null, true)) && $parent_value->enableEdit(true) !== true) {
$tools->disableEdit();
$tools->disableDelete();
if (!is_null($parent_value = $this->custom_value->getParentValue(null, true))) {
if (boolval($this->custom_table->getOption('editable_with_parent')??1)) {
if ($parent_value->enableEdit(true) !== true) {
$tools->disableEdit();
$tools->disableDelete();
}
} elseif ($parent_value->enableAccess() !== true) {
$tools->disableEdit();
$tools->disableDelete();
}
}

if ($this->modal) {
Expand Down
8 changes: 4 additions & 4 deletions src/Jobs/MailSendJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ class MailSendJob extends Notification implements ShouldQueue
* @var MailHistory
*/
protected $mailHistory;
protected $user;
protected $userId;
protected $finalUser;


public function __construct($user = null, $finalUser = false)
public function __construct($userId = null, $finalUser = false)
{
$this->user = $user;
$this->userId = $userId;
$this->finalUser = $finalUser;
}

Expand Down Expand Up @@ -104,7 +104,7 @@ public function failed($exception)
$mail_template->getValue('mail_subject'),
$mail_template->getValue('mail_body'),
$this->notify_id ?? -1,
$this->user->id,
$this->userId,
\Exment::getUserId() ?? null,
$this->mailHistory->getParentId(),
$this->mailHistory->getParentType()
Expand Down
1 change: 1 addition & 0 deletions src/Middleware/Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ protected function setCssJs(Request $request, \Closure $next)
'vendor/exment/jstree/jstree.min.js',
'vendor/exment/js/common_all.js',
'vendor/exment/js/common.js',
'vendor/exment/js/scroll-restore.js',
'vendor/exment/js/search.js',
'vendor/exment/js/calc.js',
'vendor/exment/js/notify_navbar.js',
Expand Down
26 changes: 22 additions & 4 deletions src/Model/CustomValue.php
Original file line number Diff line number Diff line change
Expand Up @@ -1794,8 +1794,17 @@ public function enableEdit($checkFormAction = false)
return ErrorCode::WORKFLOW_LOCK();
}

if (!is_null($parent_value = $this->getParentValue()) && ($code = $parent_value->enableEdit($checkFormAction)) !== true) {
return $code;
// check parent permission
if (!is_null($parent_value = $this->getParentValue())) {
if (boolval($this->custom_table->getOption('editable_with_parent')??1)) {
if (($code = $parent_value->enableEdit($checkFormAction)) !== true) {
return $code;
}
} else {
if (($code = $parent_value->enableAccess()) !== true) {
return $code;
}
}
}

if ($this->trashed()) {
Expand Down Expand Up @@ -1834,8 +1843,17 @@ public function enableDelete($checkFormAction = false)
return ErrorCode::DELETE_DISABLED();
}

if (!is_null($parent_value = $this->getParentValue()) && ($code = $parent_value->enableDelete($checkFormAction)) !== true) {
return $code;
// check parent permission
if (!is_null($parent_value = $this->getParentValue())) {
if (boolval($this->custom_table->getOption('editable_with_parent')??1)) {
if (($code = $parent_value->enableDelete($checkFormAction)) !== true) {
return $code;
}
} else {
if (($code = $parent_value->enableAccess()) !== true) {
return $code;
}
}
}

return true;
Expand Down
Loading
Loading