Skip to content

Commit be4c551

Browse files
committed
Merge branch 'main' into lunny/refactor_gitrepo
2 parents ec62dfb + 90cb5f9 commit be4c551

File tree

12 files changed

+108
-206
lines changed

12 files changed

+108
-206
lines changed

models/perm/access/repo_permission.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -348,10 +348,8 @@ func GetUserRepoPermission(ctx context.Context, repo *repo_model.Repository, use
348348

349349
for _, u := range repo.Units {
350350
for _, team := range teams {
351-
unitAccessMode := minAccessMode
352-
if teamMode, exist := team.UnitAccessModeEx(ctx, u.Type); exist {
353-
unitAccessMode = max(perm.unitsMode[u.Type], unitAccessMode, teamMode)
354-
}
351+
teamMode, _ := team.UnitAccessModeEx(ctx, u.Type)
352+
unitAccessMode := max(perm.unitsMode[u.Type], minAccessMode, teamMode)
355353
perm.unitsMode[u.Type] = unitAccessMode
356354
}
357355
}

models/perm/access/repo_permission_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,37 @@ func TestGetUserRepoPermission(t *testing.T) {
197197
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
198198
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
199199
})
200+
201+
repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) // org private repo, same org as repo 32
202+
require.NoError(t, repo3.LoadOwner(ctx))
203+
require.True(t, repo3.Owner.IsOrganization())
204+
require.NoError(t, db.TruncateBeans(ctx, &organization.TeamUnit{}, &Access{})) // The user has access set of that repo, remove it, it is useless for our test
205+
require.NoError(t, db.Insert(ctx, &organization.TeamRepo{OrgID: org.ID, TeamID: team.ID, RepoID: repo3.ID}))
206+
t.Run("DoerWithNoopTeamOnPrivateRepo", func(t *testing.T) {
207+
perm, err := GetUserRepoPermission(ctx, repo3, user)
208+
require.NoError(t, err)
209+
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
210+
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
211+
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeIssues])
212+
})
213+
214+
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeCode, AccessMode: perm_model.AccessModeNone}))
215+
require.NoError(t, db.Insert(ctx, &organization.TeamUnit{OrgID: org.ID, TeamID: team.ID, Type: unit.TypeIssues, AccessMode: perm_model.AccessModeRead}))
216+
t.Run("DoerWithReadIssueTeamOnPrivateRepo", func(t *testing.T) {
217+
perm, err := GetUserRepoPermission(ctx, repo3, user)
218+
require.NoError(t, err)
219+
assert.Equal(t, perm_model.AccessModeNone, perm.AccessMode)
220+
assert.Equal(t, perm_model.AccessModeNone, perm.unitsMode[unit.TypeCode])
221+
assert.Equal(t, perm_model.AccessModeRead, perm.unitsMode[unit.TypeIssues])
222+
})
223+
224+
require.NoError(t, db.Insert(ctx, repo_model.Collaboration{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
225+
require.NoError(t, db.Insert(ctx, Access{RepoID: repo3.ID, UserID: user.ID, Mode: perm_model.AccessModeWrite}))
226+
t.Run("DoerWithReadIssueTeamAndWriteCollaboratorOnPrivateRepo", func(t *testing.T) {
227+
perm, err := GetUserRepoPermission(ctx, repo3, user)
228+
require.NoError(t, err)
229+
assert.Equal(t, perm_model.AccessModeWrite, perm.AccessMode)
230+
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeCode])
231+
assert.Equal(t, perm_model.AccessModeWrite, perm.unitsMode[unit.TypeIssues])
232+
})
200233
}

options/locale/locale_pt-BR.ini

Lines changed: 55 additions & 55 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"@citation-js/plugin-csl": "0.7.18",
1212
"@citation-js/plugin-software-formats": "0.6.1",
1313
"@github/markdown-toolbar-element": "2.2.3",
14+
"@github/paste-markdown": "1.5.3",
1415
"@github/relative-time-element": "4.4.8",
1516
"@github/text-expander-element": "2.9.2",
1617
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
@@ -37,15 +38,13 @@
3738
"katex": "0.16.22",
3839
"mermaid": "11.11.0",
3940
"mini-css-extract-plugin": "2.9.4",
40-
"minimatch": "10.0.3",
4141
"monaco-editor": "0.53.0",
4242
"monaco-editor-webpack-plugin": "7.1.0",
4343
"online-3d-viewer": "0.16.0",
4444
"pdfobject": "2.3.1",
4545
"perfect-debounce": "2.0.0",
4646
"postcss": "8.5.6",
4747
"postcss-loader": "8.2.0",
48-
"postcss-nesting": "13.0.2",
4948
"sortablejs": "1.15.6",
5049
"swagger-ui-dist": "5.29.0",
5150
"tailwindcss": "3.4.17",
@@ -89,7 +88,6 @@
8988
"eslint-plugin-array-func": "5.0.2",
9089
"eslint-plugin-github": "6.0.0",
9190
"eslint-plugin-import-x": "4.16.1",
92-
"eslint-plugin-no-jquery": "3.1.1",
9391
"eslint-plugin-no-use-extend-native": "0.7.2",
9492
"eslint-plugin-playwright": "2.2.2",
9593
"eslint-plugin-regexp": "2.10.0",

pnpm-lock.yaml

Lines changed: 8 additions & 41 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

types.d.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,6 @@ declare module 'eslint-plugin-github' {
1717
const plugin: Eslint.Plugin;
1818
export = plugin;
1919
}
20-
declare module 'eslint-plugin-no-jquery' {
21-
import type {Eslint} from 'eslint';
22-
const plugin: Eslint.Plugin;
23-
export = plugin;
24-
}
2520
declare module '@eslint-community/eslint-plugin-eslint-comments' {
2621
import type {Eslint} from 'eslint';
2722
const plugin: Eslint.Plugin;
Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {pasteAsMarkdownLink, removeAttachmentLinksFromMarkdown} from './EditorUpload.ts';
1+
import {removeAttachmentLinksFromMarkdown} from './EditorUpload.ts';
22

33
test('removeAttachmentLinksFromMarkdown', () => {
44
expect(removeAttachmentLinksFromMarkdown('a foo b', 'foo')).toBe('a foo b');
@@ -12,13 +12,3 @@ test('removeAttachmentLinksFromMarkdown', () => {
1212
expect(removeAttachmentLinksFromMarkdown('a <img src="/attachments/foo"> b', 'foo')).toBe('a b');
1313
expect(removeAttachmentLinksFromMarkdown('a <img src="/attachments/foo" width="100"/> b', 'foo')).toBe('a b');
1414
});
15-
16-
test('preparePasteAsMarkdownLink', () => {
17-
expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'bar')).toBeNull();
18-
expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'https://gitea.com')).toBeNull();
19-
expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'bar')).toBeNull();
20-
expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'https://gitea.com')).toBe('[foo](https://gitea.com)');
21-
expect(pasteAsMarkdownLink({value: '..(url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBe('[url](https://gitea.com)');
22-
expect(pasteAsMarkdownLink({value: '[](url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBeNull();
23-
expect(pasteAsMarkdownLink({value: 'https://example.com', selectionStart: 0, selectionEnd: 19}, 'https://gitea.com')).toBeNull();
24-
});

web_src/js/features/comp/EditorUpload.ts

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import {imageInfo} from '../../utils/image.ts';
2-
import {replaceTextareaSelection} from '../../utils/dom.ts';
3-
import {isUrl} from '../../utils/url.ts';
42
import {textareaInsertText, triggerEditorContentChanged} from './EditorMarkdown.ts';
53
import {
64
DropzoneCustomEventRemovedFile,
75
DropzoneCustomEventUploadDone,
86
generateMarkdownLinkForAttachment,
97
} from '../dropzone.ts';
8+
import {subscribe} from '@github/paste-markdown';
109
import type CodeMirror from 'codemirror';
1110
import type EasyMDE from 'easymde';
1211
import type {DropzoneFile} from 'dropzone';
@@ -118,46 +117,20 @@ export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string
118117
return text;
119118
}
120119

121-
export function pasteAsMarkdownLink(textarea: {value: string, selectionStart: number, selectionEnd: number}, pastedText: string): string | null {
122-
const {value, selectionStart, selectionEnd} = textarea;
123-
const selectedText = value.substring(selectionStart, selectionEnd);
124-
const trimmedText = pastedText.trim();
125-
const beforeSelection = value.substring(0, selectionStart);
126-
const afterSelection = value.substring(selectionEnd);
127-
const isInMarkdownLink = beforeSelection.endsWith('](') && afterSelection.startsWith(')');
128-
const asMarkdownLink = selectedText && isUrl(trimmedText) && !isUrl(selectedText) && !isInMarkdownLink;
129-
return asMarkdownLink ? `[${selectedText}](${trimmedText})` : null;
130-
}
131-
132-
function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, pastedText: string, isShiftDown: boolean) {
133-
// pasting with "shift" means "paste as original content" in most applications
134-
if (isShiftDown) return; // let the browser handle it
135-
136-
// when pasting links over selected text, turn it into [text](link)
137-
const pastedAsMarkdown = pasteAsMarkdownLink(textarea, pastedText);
138-
if (pastedAsMarkdown) {
139-
e.preventDefault();
140-
replaceTextareaSelection(textarea, pastedAsMarkdown);
141-
}
142-
// else, let the browser handle it
143-
}
144-
145-
// extract text and images from "paste" event
146-
function getPastedContent(e: ClipboardEvent) {
147-
const images = [];
120+
function getPastedImages(e: ClipboardEvent) {
121+
const images: Array<File> = [];
148122
for (const item of e.clipboardData?.items ?? []) {
149123
if (item.type?.startsWith('image/')) {
150124
images.push(item.getAsFile());
151125
}
152126
}
153-
const text = e.clipboardData?.getData?.('text') ?? '';
154-
return {text, images};
127+
return images;
155128
}
156129

157130
export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) {
158131
const editor = new CodeMirrorEditor(easyMDE.codemirror as any);
159132
easyMDE.codemirror.on('paste', (_, e) => {
160-
const {images} = getPastedContent(e);
133+
const images = getPastedImages(e);
161134
if (!images.length) return;
162135
handleUploadFiles(editor, dropzoneEl, images, e);
163136
});
@@ -173,19 +146,11 @@ export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) {
173146
}
174147

175148
export function initTextareaEvents(textarea: HTMLTextAreaElement, dropzoneEl: HTMLElement) {
176-
let isShiftDown = false;
177-
textarea.addEventListener('keydown', (e: KeyboardEvent) => {
178-
if (e.shiftKey) isShiftDown = true;
179-
});
180-
textarea.addEventListener('keyup', (e: KeyboardEvent) => {
181-
if (!e.shiftKey) isShiftDown = false;
182-
});
149+
subscribe(textarea); // enable paste features
183150
textarea.addEventListener('paste', (e: ClipboardEvent) => {
184-
const {images, text} = getPastedContent(e);
151+
const images = getPastedImages(e);
185152
if (images.length && dropzoneEl) {
186153
handleUploadFiles(new TextareaEditor(textarea), dropzoneEl, images, e);
187-
} else if (text) {
188-
handleClipboardText(textarea, e, text, isShiftDown);
189154
}
190155
});
191156
textarea.addEventListener('drop', (e: DragEvent) => {

web_src/js/utils/dom.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -286,28 +286,6 @@ export function isElemVisible(el: HTMLElement): boolean {
286286
return !el.classList.contains('tw-hidden') && (el.offsetWidth || el.offsetHeight || el.getClientRects().length) && el.style.display !== 'none';
287287
}
288288

289-
/** replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this */
290-
export function replaceTextareaSelection(textarea: HTMLTextAreaElement, text: string) {
291-
const before = textarea.value.slice(0, textarea.selectionStart ?? undefined);
292-
const after = textarea.value.slice(textarea.selectionEnd ?? undefined);
293-
let success = false;
294-
295-
textarea.contentEditable = 'true';
296-
try {
297-
success = document.execCommand('insertText', false, text); // eslint-disable-line @typescript-eslint/no-deprecated
298-
} catch {} // ignore the error if execCommand is not supported or failed
299-
textarea.contentEditable = 'false';
300-
301-
if (success && !textarea.value.slice(0, textarea.selectionStart ?? undefined).endsWith(text)) {
302-
success = false;
303-
}
304-
305-
if (!success) {
306-
textarea.value = `${before}${text}${after}`;
307-
textarea.dispatchEvent(new CustomEvent('change', {bubbles: true, cancelable: true}));
308-
}
309-
}
310-
311289
export function createElementFromHTML<T extends HTMLElement>(htmlString: string): T {
312290
htmlString = htmlString.trim();
313291
// There is no way to create some elements without a proper parent, jQuery's approach: https://github.com/jquery/jquery/blob/main/src/manipulation/wrapMap.js

web_src/js/utils/url.test.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1-
import {pathEscapeSegments, isUrl, toOriginUrl} from './url.ts';
1+
import {pathEscapeSegments, toOriginUrl} from './url.ts';
22

33
test('pathEscapeSegments', () => {
44
expect(pathEscapeSegments('a/b/c')).toEqual('a/b/c');
55
expect(pathEscapeSegments('a/b/ c')).toEqual('a/b/%20c');
66
});
77

8-
test('isUrl', () => {
9-
expect(isUrl('https://example.com')).toEqual(true);
10-
expect(isUrl('https://example.com/')).toEqual(true);
11-
expect(isUrl('https://example.com/index.html')).toEqual(true);
12-
expect(isUrl('/index.html')).toEqual(false);
13-
});
14-
158
test('toOriginUrl', () => {
169
const oldLocation = String(window.location);
1710
for (const origin of ['https://example.com', 'https://example.com:3000']) {

0 commit comments

Comments
 (0)