Skip to content

Commit

Permalink
Merge remote-tracking branch 'giteaofficial/main'
Browse files Browse the repository at this point in the history
* giteaofficial/main:
  Fix commit_status problem when testing (go-gitea#29672)
  Support pasting URLs over markdown text (go-gitea#29566)
  bump python version to 3.12 in dev container (go-gitea#29670)
  • Loading branch information
zjjhot committed Mar 8, 2024
2 parents ab0b054 + 0c273f1 commit cf0c76a
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 33 deletions.
4 changes: 3 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
},
"ghcr.io/devcontainers/features/git-lfs:1.1.0": {},
"ghcr.io/devcontainers-contrib/features/poetry:2": {},
"ghcr.io/devcontainers/features/python:1": {}
"ghcr.io/devcontainers/features/python:1": {
"version": "3.12"
}
},
"customizations": {
"vscode": {
Expand Down
2 changes: 1 addition & 1 deletion services/pull/commit_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func MergeRequiredContextsCommitStatus(commitStatuses []*git_model.CommitStatus,
}
}

if matchedCount == 0 {
if matchedCount == 0 && returnedStatus == structs.CommitStatusSuccess {
status := git_model.CalcCommitStatus(commitStatuses)
if status != nil {
return status.State
Expand Down
17 changes: 14 additions & 3 deletions web_src/js/features/comp/ComboMarkdownEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import '@github/text-expander-element';
import $ from 'jquery';
import {attachTribute} from '../tribute.js';
import {hideElem, showElem, autosize, isElemVisible} from '../../utils/dom.js';
import {initEasyMDEImagePaste, initTextareaImagePaste} from './ImagePaste.js';
import {initEasyMDEPaste, initTextareaPaste} from './Paste.js';
import {handleGlobalEnterQuickSubmit} from './QuickSubmit.js';
import {renderPreviewPanelContent} from '../repo-editor.js';
import {easyMDEToolbarActions} from './EasyMDEToolbarActions.js';
Expand Down Expand Up @@ -84,6 +84,17 @@ class ComboMarkdownEditor {
if (el.nodeName === 'BUTTON' && !el.getAttribute('type')) el.setAttribute('type', 'button');
}

this.textarea.addEventListener('keydown', (e) => {
if (e.shiftKey) {
e.target._shiftDown = true;
}
});
this.textarea.addEventListener('keyup', (e) => {
if (!e.shiftKey) {
e.target._shiftDown = false;
}
});

const monospaceButton = this.container.querySelector('.markdown-switch-monospace');
const monospaceEnabled = localStorage?.getItem('markdown-editor-monospace') === 'true';
const monospaceText = monospaceButton.getAttribute(monospaceEnabled ? 'data-disable-text' : 'data-enable-text');
Expand All @@ -108,7 +119,7 @@ class ComboMarkdownEditor {
});

if (this.dropzone) {
initTextareaImagePaste(this.textarea, this.dropzone);
initTextareaPaste(this.textarea, this.dropzone);
}
}

Expand Down Expand Up @@ -241,7 +252,7 @@ class ComboMarkdownEditor {
});
this.applyEditorHeights(this.container.querySelector('.CodeMirror-scroll'), this.options.editorHeights);
await attachTribute(this.easyMDE.codemirror.getInputField(), {mentions: true, emoji: true});
initEasyMDEImagePaste(this.easyMDE, this.dropzone);
initEasyMDEPaste(this.easyMDE, this.dropzone);
hideElem(this.textareaMarkdownToolbar);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {htmlEscape} from 'escape-goat';
import {POST} from '../../modules/fetch.js';
import {imageInfo} from '../../utils/image.js';
import {getPastedContent, replaceTextareaSelection} from '../../utils/dom.js';
import {isUrl} from '../../utils/url.js';

async function uploadFile(file, uploadUrl) {
const formData = new FormData();
Expand All @@ -10,17 +12,6 @@ async function uploadFile(file, uploadUrl) {
return await res.json();
}

function clipboardPastedImages(e) {
if (!e.clipboardData) return [];

const files = [];
for (const item of e.clipboardData.items || []) {
if (!item.type || !item.type.startsWith('image/')) continue;
files.push(item.getAsFile());
}
return files;
}

function triggerEditorContentChanged(target) {
target.dispatchEvent(new CustomEvent('ce-editor-content-changed', {bubbles: true}));
}
Expand Down Expand Up @@ -91,20 +82,16 @@ class CodeMirrorEditor {
}
}

const uploadClipboardImage = async (editor, dropzone, e) => {
async function handleClipboardImages(editor, dropzone, images, e) {
const uploadUrl = dropzone.getAttribute('data-upload-url');
const filesContainer = dropzone.querySelector('.files');

if (!uploadUrl || !filesContainer) return;
if (!dropzone || !uploadUrl || !filesContainer || !images.length) return;

const pastedImages = clipboardPastedImages(e);
if (!pastedImages || pastedImages.length === 0) {
return;
}
e.preventDefault();
e.stopPropagation();

for (const img of pastedImages) {
for (const img of images) {
const name = img.name.slice(0, img.name.lastIndexOf('.'));

const placeholder = `![${name}](uploading ...)`;
Expand All @@ -131,18 +118,37 @@ const uploadClipboardImage = async (editor, dropzone, e) => {
input.value = uuid;
filesContainer.append(input);
}
};
}

export function initEasyMDEImagePaste(easyMDE, dropzone) {
if (!dropzone) return;
easyMDE.codemirror.on('paste', async (_, e) => {
return uploadClipboardImage(new CodeMirrorEditor(easyMDE.codemirror), dropzone, e);
function handleClipboardText(textarea, text, e) {
// when pasting links over selected text, turn it into [text](link), except when shift key is held
const {value, selectionStart, selectionEnd, _shiftDown} = textarea;
if (_shiftDown) return;
const selectedText = value.substring(selectionStart, selectionEnd);
const trimmedText = text.trim();
if (selectedText && isUrl(trimmedText)) {
e.stopPropagation();
e.preventDefault();
replaceTextareaSelection(textarea, `[${selectedText}](${trimmedText})`);
}
}

export function initEasyMDEPaste(easyMDE, dropzone) {
easyMDE.codemirror.on('paste', (_, e) => {
const {images} = getPastedContent(e);
if (images.length) {
handleClipboardImages(new CodeMirrorEditor(easyMDE.codemirror), dropzone, images, e);
}
});
}

export function initTextareaImagePaste(textarea, dropzone) {
if (!dropzone) return;
textarea.addEventListener('paste', async (e) => {
return uploadClipboardImage(new TextareaEditor(textarea), dropzone, e);
export function initTextareaPaste(textarea, dropzone) {
textarea.addEventListener('paste', (e) => {
const {images, text} = getPastedContent(e);
if (images.length) {
handleClipboardImages(new TextareaEditor(textarea), dropzone, images, e);
} else if (text) {
handleClipboardText(textarea, text, e);
}
});
}
36 changes: 36 additions & 0 deletions web_src/js/utils/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,3 +243,39 @@ export function isElemVisible(element) {

return Boolean(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
}

// extract text and images from "paste" event
export function getPastedContent(e) {
const images = [];
for (const item of e.clipboardData?.items ?? []) {
if (item.type?.startsWith('image/')) {
images.push(item.getAsFile());
}
}
const text = e.clipboardData?.getData?.('text') ?? '';
return {text, images};
}

// replace selected text in a textarea while preserving editor history, e.g. CTRL-Z works after this
export function replaceTextareaSelection(textarea, text) {
const before = textarea.value.slice(0, textarea.selectionStart ?? undefined);
const after = textarea.value.slice(textarea.selectionEnd ?? undefined);
let success = true;

textarea.contentEditable = 'true';
try {
success = document.execCommand('insertText', false, text);
} catch {
success = false;
}
textarea.contentEditable = 'false';

if (success && !textarea.value.slice(0, textarea.selectionStart ?? undefined).endsWith(text)) {
success = false;
}

if (!success) {
textarea.value = `${before}${text}${after}`;
textarea.dispatchEvent(new CustomEvent('change', {bubbles: true, cancelable: true}));
}
}
12 changes: 12 additions & 0 deletions web_src/js/utils/url.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
export function pathEscapeSegments(s) {
return s.split('/').map(encodeURIComponent).join('/');
}

function stripSlash(url) {
return url.endsWith('/') ? url.slice(0, -1) : url;
}

export function isUrl(url) {
try {
return stripSlash((new URL(url).href)).trim() === stripSlash(url).trim();
} catch {
return false;
}
}
9 changes: 8 additions & 1 deletion web_src/js/utils/url.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import {pathEscapeSegments} from './url.js';
import {pathEscapeSegments, isUrl} from './url.js';

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

test('isUrl', () => {
expect(isUrl('https://example.com')).toEqual(true);
expect(isUrl('https://example.com/')).toEqual(true);
expect(isUrl('https://example.com/index.html')).toEqual(true);
expect(isUrl('/index.html')).toEqual(false);
});

0 comments on commit cf0c76a

Please sign in to comment.