Merge pull request 'chore: merge dev into main — tech-debt, namespace migration, combo-multiselect' (#373) from dev into main
Deploy MokoGitea / deploy (push) Failing after 6m39s
Deploy MokoGitea / deploy (push) Failing after 6m39s
This commit was merged in pull request #373.
This commit is contained in:
@@ -3,6 +3,38 @@
|
||||
This changelog goes through the changes that have been made in each release
|
||||
without substantial changes to our git log; to see the highlights of what has
|
||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||
## [v1.26.1-moko.05.15.00] - 2026-05-31
|
||||
|
||||
* FEATURES
|
||||
* feat(ui): add generic combo-multiselect component (#361)
|
||||
* Reusable dropdown with search, checkable items, and selected-items display
|
||||
* Template: `shared/combolist.tmpl` — accepts Items, Name, Title, SelectedValues
|
||||
* Decoupled from issue sidebar — works in any form context
|
||||
* feat(updates): extension metadata settings for update feed generation
|
||||
* feat(licenses): platform enforcement, key deletion, expired key cleanup
|
||||
* feat(licenses): store keys in plaintext, show full key with copy button
|
||||
* TECH DEBT
|
||||
* chore: full namespace migration from git.mokoconsulting.tech to code.mokoconsulting.tech (#336, #337, #344)
|
||||
* Go module path, all imports, template URLs, workflow configs (2,276 files)
|
||||
* fix(blame): set HasSourceRenderedToggle for renderable files (#344)
|
||||
* fix(settings): translate team permission strings via data-locale attributes (#344)
|
||||
* fix(dropzone): use relative path for non-image attachment markdown links (#344)
|
||||
* fix(templates): add required validation to issue dropdown fields (#350)
|
||||
* refactor(ts): remove redundant `handled` field from MarkdownHandleIndentionResult (#350)
|
||||
* refactor(go): rename HasOrgOrUserVisible to IsOwnerVisibleToDoer (#350)
|
||||
* refactor(go): replace ValuesRepository with maps.Values (Go 1.21+) (#357)
|
||||
* refactor(go): remove CanEnableEditor wrapper, use CanContentChange directly (#357)
|
||||
* fix(ts): parseIssueHref now uses URL pathname and trims appSubUrl (#360)
|
||||
* fix(actions): enforce MaxJobNumPerRun (256) limit when creating jobs (#360)
|
||||
* fix(css): use calc(infinity * 1px) for --border-radius-full (#361)
|
||||
* fix(css): remove legacy .center class from 2015, replace with tw-text-center (#361)
|
||||
* BUGFIXES
|
||||
* fix(build): use slices.Collect for maps.Values (Go 1.23+ compat)
|
||||
* fix(licenses): remove duplicate DeleteLicenseKey declaration
|
||||
* fix(licenses): only show licenses tab when licensing is enabled
|
||||
* fix(licenses): show feed URLs based on repo update platform setting
|
||||
* fix(updates): correct dlid prefix and align XML with Joomla standard
|
||||
|
||||
## [v1.26.1-moko.05.06.00] - 2026-05-30
|
||||
|
||||
* FEATURES
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
{{/*
|
||||
Generic multiselect combo list component.
|
||||
Provides a dropdown with search, checkable items, and a selected-items display list.
|
||||
|
||||
Parameters:
|
||||
Name - form input name (required)
|
||||
Title - display label (required)
|
||||
Items - slice of items, each must have .Value and .Label fields
|
||||
SelectedValues - comma-separated string of selected values
|
||||
Placeholder - search input placeholder (optional, defaults to "Filter...")
|
||||
EmptyText - text when nothing is selected (optional)
|
||||
Disabled - whether the control is disabled (optional)
|
||||
Icon - gear icon shown next to title (optional, defaults to "octicon-gear")
|
||||
*/}}
|
||||
<div class="combo-multiselect" data-field-name="{{.Name}}">
|
||||
<input class="combo-value" name="{{.Name}}" type="hidden" value="{{.SelectedValues}}">
|
||||
<div class="ui dropdown full-width {{if .Disabled}}disabled{{end}}">
|
||||
<a class="fixed-text muted">
|
||||
<strong>{{.Title}}</strong> {{if not .Disabled}}{{svg (or .Icon "octicon-gear")}}{{end}}
|
||||
</a>
|
||||
<div class="menu">
|
||||
{{if .Items}}
|
||||
<div class="ui icon search input">
|
||||
<i class="icon">{{svg "octicon-search" 16}}</i>
|
||||
<input type="text" placeholder="{{or .Placeholder (ctx.Locale.Tr "search.filter")}}">
|
||||
</div>
|
||||
<div class="scrolling menu">
|
||||
<a class="item clear-selection" href="#">{{ctx.Locale.Tr "repo.issues.new.clear_labels"}}</a>
|
||||
<div class="divider"></div>
|
||||
{{range .Items}}
|
||||
<a class="item" data-value="{{.Value}}">
|
||||
<span class="item-check-mark">{{svg "octicon-check" 16}}</span>
|
||||
<span class="item-label">{{.Label}}</span>
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="item disabled">{{or .EmptyText (ctx.Locale.Tr "repo.issues.new.no_items")}}</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui list combo-multiselect-list">
|
||||
<span class="item empty-list">{{or .EmptyText "None"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,6 +34,7 @@
|
||||
@import "./modules/codeeditor.css";
|
||||
@import "./modules/chroma.css";
|
||||
@import "./modules/charescape.css";
|
||||
@import "./modules/combo-multiselect.css";
|
||||
|
||||
@import "./shared/flex-list.css";
|
||||
@import "./shared/milestone.css";
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/* Styles for the generic combo-multiselect component (shared/combolist.tmpl) */
|
||||
|
||||
.combo-multiselect > .ui.dropdown .item:not(.checked) .item-check-mark {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.combo-multiselect > .ui.dropdown .item .item-check-mark {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.combo-multiselect > .combo-multiselect-list {
|
||||
margin-top: 0.25em;
|
||||
}
|
||||
|
||||
.combo-multiselect > .combo-multiselect-list > .item {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
margin: 2px;
|
||||
border-radius: var(--border-radius);
|
||||
background: var(--color-label-bg);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.combo-multiselect > .combo-multiselect-list > .item.empty-list {
|
||||
background: none;
|
||||
color: var(--color-text-light-2);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// Copyright 2026 Moko Consulting. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||
import {addDelegatedEventListener, queryElemChildren, queryElems, toggleElem} from '../utils/dom.ts';
|
||||
|
||||
/**
|
||||
* Generic multiselect combo list component.
|
||||
* Works with the "shared/combolist" template to provide a reusable
|
||||
* dropdown with search, checkable items, and a selected-items display list.
|
||||
*
|
||||
* Usage: add class="combo-multiselect" to the container element.
|
||||
* The component is self-contained — no backend calls, just updates the hidden input.
|
||||
*/
|
||||
class ComboMultiselect {
|
||||
container: HTMLElement;
|
||||
elDropdown: HTMLElement;
|
||||
elList: HTMLElement;
|
||||
elComboValue: HTMLInputElement;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
this.container = container;
|
||||
this.elDropdown = container.querySelector<HTMLElement>(':scope > .ui.dropdown')!;
|
||||
this.elList = container.querySelector<HTMLElement>(':scope > .combo-multiselect-list')!;
|
||||
this.elComboValue = container.querySelector<HTMLInputElement>(':scope > .combo-value')!;
|
||||
}
|
||||
|
||||
collectCheckedValues(): string[] {
|
||||
return Array.from(
|
||||
this.elDropdown.querySelectorAll('.menu > .item.checked'),
|
||||
(el) => el.getAttribute('data-value')!,
|
||||
);
|
||||
}
|
||||
|
||||
updateUiList() {
|
||||
const checkedValues = this.collectCheckedValues();
|
||||
const elEmptyTip = this.elList.querySelector(':scope > .item.empty-list')!;
|
||||
queryElemChildren(this.elList, '.item:not(.empty-list)', (el) => el.remove());
|
||||
|
||||
for (const value of checkedValues) {
|
||||
const el = this.elDropdown.querySelector<HTMLElement>(`.menu > .item[data-value="${CSS.escape(value)}"]`);
|
||||
if (!el) continue;
|
||||
const labelText = el.querySelector('.item-label')?.textContent || value;
|
||||
const listItem = document.createElement('span');
|
||||
listItem.classList.add('item');
|
||||
listItem.textContent = labelText;
|
||||
this.elList.append(listItem);
|
||||
}
|
||||
|
||||
toggleElem(elEmptyTip, checkedValues.length === 0);
|
||||
this.elComboValue.value = checkedValues.join(',');
|
||||
this.elComboValue.dispatchEvent(new Event('change', {bubbles: true}));
|
||||
}
|
||||
|
||||
onItemClick(elItem: HTMLElement, e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
if (elItem.matches('.clear-selection')) {
|
||||
queryElems(this.elDropdown, '.menu > .item', (el) => el.classList.remove('checked'));
|
||||
this.updateUiList();
|
||||
return;
|
||||
}
|
||||
|
||||
elItem.classList.toggle('checked');
|
||||
this.updateUiList();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Restore checked state from initial value
|
||||
const initialValues = this.elComboValue.value ? this.elComboValue.value.split(',') : [];
|
||||
for (const value of initialValues) {
|
||||
const elItem = this.elDropdown.querySelector<HTMLElement>(`.menu > .item[data-value="${CSS.escape(value)}"]`);
|
||||
elItem?.classList.add('checked');
|
||||
}
|
||||
this.updateUiList();
|
||||
|
||||
addDelegatedEventListener(this.elDropdown, 'click', '.item', (el, e) => this.onItemClick(el, e));
|
||||
|
||||
fomanticQuery(this.elDropdown).dropdown('setting', {
|
||||
action: 'nothing',
|
||||
fullTextSearch: 'exact',
|
||||
hideDividers: 'empty',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function initComboMultiselect() {
|
||||
queryElems(document, '.combo-multiselect', (el) => {
|
||||
if (el.hasAttribute('data-combo-inited')) return;
|
||||
el.setAttribute('data-combo-inited', 'true');
|
||||
new ComboMultiselect(el).init();
|
||||
});
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import {initUserExternalLogins, initUserCheckAppUrl} from './features/user-auth.
|
||||
import {initRepoPullRequestReview, initRepoIssueFilterItemLabel} from './features/repo-issue.ts';
|
||||
import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.ts';
|
||||
import {initRepoTopicBar} from './features/repo-home.ts';
|
||||
import {initComboMultiselect} from './features/combo-multiselect.ts';
|
||||
import {initAdminCommon} from './features/admin/common.ts';
|
||||
import {initRepoCodeView} from './features/repo-code.ts';
|
||||
import {initSshKeyFormParser} from './features/sshkey-helper.ts';
|
||||
@@ -105,6 +106,7 @@ const initPerformanceTracer = callInitFunctions([
|
||||
initTableSort,
|
||||
initRepoFileSearch,
|
||||
initCopyContent,
|
||||
initComboMultiselect,
|
||||
|
||||
initAdminCommon,
|
||||
initAdminUserListSearchForm,
|
||||
|
||||
Reference in New Issue
Block a user