Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suggestions for issues #32327

Merged
merged 15 commits into from
Oct 29, 2024
2 changes: 1 addition & 1 deletion templates/shared/combomarkdowneditor.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Template Attributes:
<button class="markdown-toolbar-button markdown-switch-easymde" data-tooltip-content="{{ctx.Locale.Tr "editor.buttons.switch_to_legacy.tooltip"}}">{{svg "octicon-arrow-switch"}}</button>
</div>
</markdown-toolbar>
<text-expander keys=": @" suffix="">
<text-expander keys=": @ #" multiword="#" suffix="">
<textarea class="markdown-text-editor"{{if .TextareaName}} name="{{.TextareaName}}"{{end}}{{if .TextareaPlaceholder}} placeholder="{{.TextareaPlaceholder}}"{{end}}{{if .TextareaAriaLabel}} aria-label="{{.TextareaAriaLabel}}"{{end}}{{if .DisableAutosize}} data-disable-autosize="{{.DisableAutosize}}"{{end}}>{{.TextareaContent}}</textarea>
</text-expander>
<script>
Expand Down
71 changes: 68 additions & 3 deletions web_src/js/features/comp/TextExpander.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
import {matchEmoji, matchMention} from '../../utils/match.ts';
import {matchEmoji, matchMention, matchIssue} from '../../utils/match.ts';
import {emojiString} from '../emoji.ts';
import {svg} from '../../svg.ts';

type Issue = {state: 'open' | 'closed'; pull_request: {draft: boolean; merged: boolean} | null};
function getIssueIcon(issue: Issue) {
if (issue.pull_request !== null) {
anbraten marked this conversation as resolved.
Show resolved Hide resolved
if (issue.state === 'open') {
if (issue.pull_request.draft === true) {
return 'octicon-git-pull-request-draft'; // WIP PR
}
return 'octicon-git-pull-request'; // Open PR
} else if (issue.pull_request.merged === true) {
return 'octicon-git-merge'; // Merged PR
}
return 'octicon-git-pull-request'; // Closed PR
} else if (issue.state === 'open') {
return 'octicon-issue-opened'; // Open Issue
}
return 'octicon-issue-closed'; // Closed Issue
}

function getIssueColor(issue: Issue) {
if (issue.pull_request !== null) {
if (issue.pull_request.draft === true) {
return 'grey'; // WIP PR
} else if (issue.pull_request.merged === true) {
return 'purple'; // Merged PR
}
}
if (issue.state === 'open') {
return 'green'; // Open Issue
}
return 'red'; // Closed Issue
}

export function initTextExpander(expander) {
expander?.addEventListener('text-expander-change', ({detail: {key, provide, text}}) => {
Expand Down Expand Up @@ -49,12 +82,44 @@ export function initTextExpander(expander) {
}

provide({matched: true, fragment: ul});
} else if (key === '#') {
provide(new Promise(async (resolve) => {
const url = window.location.href;
const matches = await matchIssue(url, text);
if (!matches.length) return resolve({matched: false});

const ul = document.createElement('ul');
ul.classList.add('suggestions');
for (const {value, name, issue} of matches) {
const li = document.createElement('li');
li.classList.add('tw-flex', 'tw-gap-2');
li.setAttribute('role', 'option');
li.setAttribute('data-value', `${key}${value}`);

anbraten marked this conversation as resolved.
Show resolved Hide resolved
const icon = document.createElement('div');
icon.innerHTML = svg(getIssueIcon(issue), 16, ['text', getIssueColor(issue)].join(' ')).trim();
li.append(icon.firstChild);

const id = document.createElement('span');
id.classList.add('id');
id.textContent = value;
li.append(id);

const nameSpan = document.createElement('span');
nameSpan.textContent = name;
li.append(nameSpan);

ul.append(li);
}

resolve({matched: true, fragment: ul});
}));
}
});
expander?.addEventListener('text-expander-value', ({detail}) => {
if (detail?.item) {
// add a space after @mentions as it's likely the user wants one
const suffix = detail.key === '@' ? ' ' : '';
// add a space after @mentions and #issue as it's likely the user wants one
const suffix = ['@', '#'].includes(detail.key) ? ' ' : '';
detail.value = `${detail.item.getAttribute('data-value')}${suffix}`;
}
});
Expand Down
119 changes: 116 additions & 3 deletions web_src/js/utils/match.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import emojis from '../../../assets/emoji.json';
import { request } from '../modules/fetch.ts';

const maxMatches = 6;

function sortAndReduce(map: Map<string, number>) {
function sortAndReduce<T>(map: Map<T, number>): T[] {
const sortedMap = new Map(Array.from(map.entries()).sort((a, b) => a[1] - b[1]));
return Array.from(sortedMap.keys()).slice(0, maxMatches);
}
Expand All @@ -27,11 +28,12 @@ export function matchEmoji(queryText: string): string[] {
return sortAndReduce(results);
}

export function matchMention(queryText: string): string[] {
type Mention = {value: string; name: string; fullname: string; avatar: string};
export function matchMention(queryText: string): Mention[] {
const query = queryText.toLowerCase();

// results is a map of weights, lower is better
const results = new Map();
const results = new Map<Mention, number>();
for (const obj of window.config.mentionValues ?? []) {
const index = obj.key.toLowerCase().indexOf(query);
if (index === -1) continue;
Expand All @@ -41,3 +43,114 @@ export function matchMention(queryText: string): string[] {

return sortAndReduce(results);
}

type Issue = {state: 'open' | 'closed'; pull_request: {draft: boolean; merged: boolean} | null};
type IssueMention = {value: string; name: string; issue: Issue};
export async function matchIssue(url: string, queryText: string): Promise<IssueMention[]> {
const query = queryText.toLowerCase();
anbraten marked this conversation as resolved.
Show resolved Hide resolved

// http://localhost:3000/anbraten/test/issues/1
// http://localhost:3000/anbraten/test/compare/main...anbraten-patch-1
const repository = (new URL(url)).pathname.split('/').slice(1, 3).join('/');
anbraten marked this conversation as resolved.
Show resolved Hide resolved
const issuePullRequestId = url.split('/').slice(-1)[0];

console.log('suggestions for 1', {
repository,
query,
});

// TODO: fetch data from api
// const res = await request('/-/suggestions', {
// method: 'GET',
// data: {
// repository,
// query,
// },
// });
// console.log(await res.json());

// results is a map of weights, lower is better
const results = new Map<IssueMention, number>();
// for (const obj of window.config.mentionValues ?? []) {
// const index = obj.key.toLowerCase().indexOf(query);
// if (index === -1) continue;
// const existing = results.get(obj);
// results.set(obj, existing ? existing - index : index);
// }

results.set({
value: '28958',
name: 'Live removal of issue comments using htmx websocket',
issue: {
state: 'open',
pull_request: {
merged: false,
draft: false,
},
},
}, 0);

results.set({
value: '32234',
name: 'Calculate `PublicOnly` for org membership only once',
issue: {
state: 'closed',
pull_request: {
merged: true,
draft: false,
},
},
}, 1);

results.set({
value: '32280',
name: 'Optimize branch protection rule loading',
issue: {
state: 'open',
pull_request: {
merged: false,
draft: false,
},
},
}, 2);

results.set({
value: '32326',
name: 'Shallow Mirroring',
issue: {
state: 'open',
pull_request: null,
},
}, 3);

results.set({
value: '32248',
name: 'Make admins adhere to branch protection rules',
issue: {
state: 'closed',
pull_request: {
merged: true,
draft: false,
},
},
}, 4);

results.set({
value: '32249',
name: 'Add a way to disable branch protection rules for admins',
issue: {
state: 'closed',
pull_request: null,
},
}, 5);

// filter out current issue/pull request
for (const [key] of results.entries()) {
if (key.value === issuePullRequestId) {
results.delete(key);
break;
}
}

return sortAndReduce(results);
}
Loading