move select via external JS (CSP fix); rows show id/owner/config + ws counts

This commit is contained in:
2026-07-12 22:44:33 -03:00
parent 5728cf37b2
commit 1b932127d8
7 changed files with 114 additions and 29 deletions

View File

@@ -38,12 +38,40 @@
font-style: italic;
}
.wso-table td {
vertical-align: middle;
/* One project row: id (+ config dropdown) / title / owner on the left, move control on the right,
separated by a light horizontal line. */
.wso-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 2px;
border-bottom: 1px solid #eee;
}
.wso-row-main {
display: flex;
align-items: baseline;
flex-wrap: wrap;
}
.wso-id {
margin-right: 8px;
color: #777;
}
.wso-title {
margin-right: 12px;
font-weight: bold;
}
.wso-owner {
color: #999;
font-size: 0.9em;
}
.wso-move {
width: 220px;
flex: 0 0 auto;
margin-left: 12px;
text-align: right;
}

26
Asset/js/workspaceorg.js Normal file
View File

@@ -0,0 +1,26 @@
/*
* WorkspaceOrg -- submit the per-project "move to workspace" form when its dropdown changes.
*
* Kanboard's default CSP is `default-src 'self'` (no 'unsafe-inline' for scripts), so an inline
* onchange="" attribute is blocked and does nothing. The change handler must live in an external
* script served from 'self', which is what this file is.
*/
(function () {
"use strict";
function init() {
var selects = document.querySelectorAll(".wso-move-form select");
for (var i = 0; i < selects.length; i++) {
selects[i].addEventListener("change", function () {
this.form.submit();
});
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();

View File

@@ -124,8 +124,13 @@ class WorkspaceOrgHelper extends Base
$groups[self::DEFAULT_WORKSPACE] = array();
foreach ($this->projectUserRoleModel->getActiveProjectsByUser($this->uid()) as $project_id => $project_name) {
$groups[$this->workspaceOf($project_id)][] = array('id' => (int) $project_id, 'name' => $project_name);
// Full project rows (id, name, owner_name, is_active, ...) for the user's active projects,
// the same data source the native "My projects" dashboard uses.
$ids = $this->projectPermissionModel->getActiveProjectIds($this->uid());
$projects = empty($ids) ? array() : $this->projectModel->getQueryColumnStats($ids)->findAll();
foreach ($projects as $project) {
$groups[$this->workspaceOf($project['id'])][] = $project;
}
foreach ($groups as &$list) {

View File

@@ -27,6 +27,12 @@ class Plugin extends Base
$this->hook->on('template:layout:css', array(
'template' => 'plugins/WorkspaceOrg/Asset/css/workspaceorg.css',
));
// Submits the inline "move to workspace" select on change. Must be an external script:
// Kanboard's CSP (default-src 'self') blocks inline onchange handlers.
$this->hook->on('template:layout:js', array(
'template' => 'plugins/WorkspaceOrg/Asset/js/workspaceorg.js',
));
}
public function getHelpers()
@@ -53,7 +59,7 @@ class Plugin extends Base
public function getPluginVersion()
{
return '0.1.0';
return '0.2.0';
}
public function getPluginHomepage()

View File

@@ -12,7 +12,9 @@ Two people can file the same shared project differently, and neither sees the ot
- Adds a **My workspaces** entry to the dashboard sidebar (next to Overview / My projects /
My tasks / My subtasks). Visible to every logged-in user -- no admin rights needed.
- That page lists your projects **grouped by workspace**, and lets you:
- That page lists your projects **grouped by workspace** (each heading shows the project count,
e.g. `POLI/UPE (3)`), with each row showing the project **id**, **title** and **owner**, plus a
small id dropdown with a "Configure this project" shortcut. From it you can:
- **add / rename / remove** workspaces (each via a small modal, like Kanboard's Tags page);
- **move a project** to another workspace with an inline dropdown on its row.
- Shows a **`[Workspace]`** badge before the project title on the dashboard overview, the

View File

@@ -10,7 +10,7 @@
<?php foreach ($groups as $workspace => $projects): ?>
<div class="wso-group">
<h3 class="wso-group-title">
<?= $this->text->e($workspace) ?>
<?= $this->text->e($workspace) ?> (<?= count($projects) ?>)
<?php if ($workspace !== $default): ?>
<span class="wso-group-actions">
<?= $this->modal->medium('edit', t('Rename'), 'WorkspaceOrgController', 'edit', array('plugin' => 'WorkspaceOrg', 'name' => $workspace)) ?>
@@ -22,27 +22,45 @@
<?php if (empty($projects)): ?>
<p class="wso-empty"><?= t('(no projects yet)') ?></p>
<?php else: ?>
<table class="table-striped wso-table">
<?php foreach ($projects as $project): ?>
<tr>
<td class="wso-project">
<?php foreach ($projects as $project): ?>
<div class="wso-row">
<div class="wso-row-main">
<?php if ($this->user->hasProjectAccess('ProjectViewController', 'show', $project['id'])): ?>
<span class="dropdown wso-id">
<a href="#" class="dropdown-menu"><strong>#<?= (int) $project['id'] ?></strong> <i class="fa fa-caret-down"></i></a>
<ul>
<li>
<?= $this->url->link(t('Configure this project'), 'ProjectViewController', 'show', array('project_id' => $project['id'])) ?>
</li>
</ul>
</span>
<?php else: ?>
<strong class="wso-id">#<?= (int) $project['id'] ?></strong>
<?php endif ?>
<span class="wso-title">
<?= $this->url->link($this->text->e($project['name']), 'BoardViewController', 'show', array('project_id' => $project['id'])) ?>
</td>
<td class="wso-move">
<form method="post" class="wso-move-form" action="<?= $this->url->href('WorkspaceOrgController', 'assign', array('plugin' => 'WorkspaceOrg')) ?>">
<?= $this->form->csrf() ?>
<input type="hidden" name="project_id" value="<?= (int) $project['id'] ?>">
<select name="workspace" onchange="this.form.submit()">
<?php foreach ($options as $option): ?>
<option value="<?= $this->text->e($option) ?>"<?= $option === $workspace ? ' selected="selected"' : '' ?>><?= $this->text->e($option) ?></option>
<?php endforeach ?>
</select>
<noscript><button type="submit" class="btn btn-blue"><?= t('Move') ?></button></noscript>
</form>
</td>
</tr>
<?php endforeach ?>
</table>
</span>
<?php if (! empty($project['owner_id'])): ?>
<span class="wso-owner"><?= $this->text->e($project['owner_name'] ?: $project['owner_username']) ?></span>
<?php endif ?>
</div>
<div class="wso-move">
<form method="post" class="wso-move-form" action="<?= $this->url->href('WorkspaceOrgController', 'assign', array('plugin' => 'WorkspaceOrg')) ?>">
<?= $this->form->csrf() ?>
<input type="hidden" name="project_id" value="<?= (int) $project['id'] ?>">
<select name="workspace">
<?php foreach ($options as $option): ?>
<option value="<?= $this->text->e($option) ?>"<?= $option === $workspace ? ' selected="selected"' : '' ?>><?= $this->text->e($option) ?></option>
<?php endforeach ?>
</select>
<noscript><button type="submit" class="btn btn-blue"><?= t('Move') ?></button></noscript>
</form>
</div>
</div>
<?php endforeach ?>
<?php endif ?>
</div>
<?php endforeach ?>

View File

@@ -1 +1 @@
WorkspaceOrg v0.1
WorkspaceOrg v0.2