mirror of
https://github.com/go-gitea/gitea.git
synced 2026-09-03 03:23:25 +09:00
refactor: improve types in the frontend, misc fixes (#39142)
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
import {handleGlobalEnterQuickSubmit} from './QuickSubmit.ts';
|
||||
import {renderPreviewPanelContent} from '../repo-editor.ts';
|
||||
import {toggleTasklistCheckbox} from '../../markup/tasklist.ts';
|
||||
import {easyMDEToolbarActions} from './EasyMDEToolbarActions.ts';
|
||||
import {easyMDEToolbarActions, type EasyMdeToolbarAction} from './EasyMDEToolbarActions.ts';
|
||||
import {initTextExpander} from './TextExpander.ts';
|
||||
import {showErrorToast} from '../../modules/toast.ts';
|
||||
import {POST} from '../../modules/fetch.ts';
|
||||
@@ -25,6 +25,7 @@ import {DropzoneCustomEventReloadFiles, initDropzone} from '../dropzone.ts';
|
||||
import {createTippy} from '../../modules/tippy.ts';
|
||||
import {initTabSwitcher} from '../../modules/fomantic/tab.ts';
|
||||
import type EasyMDE from 'easymde';
|
||||
import type Dropzone from '@deltablot/dropzone';
|
||||
import {localUserSettings} from '../../modules/user-settings.ts';
|
||||
|
||||
/**
|
||||
@@ -57,11 +58,11 @@ type Heights = {
|
||||
|
||||
type ComboMarkdownEditorOptions = {
|
||||
editorHeights?: Heights,
|
||||
easyMDEOptions?: EasyMDE.Options,
|
||||
easyMDEOptions?: Omit<EasyMDE.Options, 'toolbar'> & {toolbar?: ReadonlyArray<string>},
|
||||
};
|
||||
|
||||
type ComboMarkdownEditorTextarea = HTMLTextAreaElement & {_giteaComboMarkdownEditor: any};
|
||||
type ComboMarkdownEditorContainer = HTMLElement & {_giteaComboMarkdownEditor?: any};
|
||||
type ComboMarkdownEditorTextarea = HTMLTextAreaElement & {_giteaComboMarkdownEditor: ComboMarkdownEditor};
|
||||
type ComboMarkdownEditorContainer = HTMLElement & {_giteaComboMarkdownEditor?: ComboMarkdownEditor};
|
||||
|
||||
export class ComboMarkdownEditor {
|
||||
static EventEditorContentChanged = EventEditorContentChanged;
|
||||
@@ -75,18 +76,18 @@ export class ComboMarkdownEditor {
|
||||
tabPreviewer?: HTMLElement;
|
||||
|
||||
supportEasyMDE!: boolean;
|
||||
easyMDE: any;
|
||||
easyMDEToolbarActions: any;
|
||||
easyMDEToolbarDefault: any;
|
||||
easyMDE: EasyMDE | null = null;
|
||||
easyMDEToolbarActions?: Record<string, EasyMdeToolbarAction>;
|
||||
easyMDEToolbarDefault!: string[];
|
||||
|
||||
textarea!: ComboMarkdownEditorTextarea;
|
||||
textareaMarkdownToolbar!: HTMLElement;
|
||||
textareaAutosize: any;
|
||||
textareaAutosize?: ReturnType<typeof autosize>;
|
||||
|
||||
buttonMonospace!: HTMLButtonElement;
|
||||
|
||||
dropzone: HTMLElement | null = null;
|
||||
attachedDropzoneInst: any;
|
||||
attachedDropzoneInst?: Dropzone;
|
||||
|
||||
previewMode!: string;
|
||||
previewUrl!: string;
|
||||
@@ -188,19 +189,19 @@ export class ComboMarkdownEditor {
|
||||
}
|
||||
|
||||
dropzoneReloadFiles() {
|
||||
if (!this.dropzone) return;
|
||||
if (!this.attachedDropzoneInst) return;
|
||||
this.attachedDropzoneInst.emit(DropzoneCustomEventReloadFiles);
|
||||
}
|
||||
|
||||
dropzoneSubmitReload() {
|
||||
if (!this.dropzone) return;
|
||||
if (!this.attachedDropzoneInst) return;
|
||||
this.attachedDropzoneInst.emit('submit');
|
||||
this.attachedDropzoneInst.emit(DropzoneCustomEventReloadFiles);
|
||||
}
|
||||
|
||||
isUploading() {
|
||||
if (!this.dropzone) return false;
|
||||
return this.attachedDropzoneInst.getQueuedFiles().length || this.attachedDropzoneInst.getUploadingFiles().length;
|
||||
if (!this.attachedDropzoneInst) return false;
|
||||
return Boolean(this.attachedDropzoneInst.getQueuedFiles().length || this.attachedDropzoneInst.getUploadingFiles().length);
|
||||
}
|
||||
|
||||
setupTab() {
|
||||
@@ -300,9 +301,9 @@ export class ComboMarkdownEditor {
|
||||
];
|
||||
}
|
||||
|
||||
parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions: any) {
|
||||
parseEasyMDEToolbar(easyMde: typeof EasyMDE, actions: ReadonlyArray<string>) {
|
||||
this.easyMDEToolbarActions = this.easyMDEToolbarActions || easyMDEToolbarActions(easyMde, this);
|
||||
const processed = [];
|
||||
const processed: EasyMdeToolbarAction[] = [];
|
||||
for (const action of actions) {
|
||||
const actionButton = this.easyMDEToolbarActions[action];
|
||||
if (!actionButton) throw new Error(`Unknown EasyMDE toolbar action ${action}`);
|
||||
@@ -345,27 +346,27 @@ export class ComboMarkdownEditor {
|
||||
inputStyle: 'contenteditable', // nativeSpellcheck requires contenteditable
|
||||
nativeSpellcheck: true,
|
||||
...this.options.easyMDEOptions,
|
||||
toolbar: this.parseEasyMDEToolbar(EasyMDE, this.options.easyMDEOptions?.toolbar ?? this.easyMDEToolbarDefault) as EasyMDE.Options['toolbar'],
|
||||
};
|
||||
easyMDEOpt.toolbar = this.parseEasyMDEToolbar(EasyMDE, easyMDEOpt.toolbar ?? this.easyMDEToolbarDefault);
|
||||
|
||||
this.easyMDE = new EasyMDE(easyMDEOpt);
|
||||
this.easyMDE.codemirror.on('change', () => triggerEditorContentChanged(this.container));
|
||||
this.easyMDE.codemirror.setOption('extraKeys', {
|
||||
'Cmd-Enter': (cm: any) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
|
||||
'Ctrl-Enter': (cm: any) => handleGlobalEnterQuickSubmit(cm.getTextArea()),
|
||||
Enter: (cm: any) => {
|
||||
'Cmd-Enter': () => { handleGlobalEnterQuickSubmit(this.textarea) },
|
||||
'Ctrl-Enter': () => { handleGlobalEnterQuickSubmit(this.textarea) },
|
||||
Enter: (cm) => {
|
||||
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
|
||||
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
||||
cm.execCommand('newlineAndIndent');
|
||||
}
|
||||
},
|
||||
Up: (cm: any) => {
|
||||
Up: (cm) => {
|
||||
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
|
||||
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
||||
return cm.execCommand('goLineUp');
|
||||
}
|
||||
},
|
||||
Down: (cm: any) => {
|
||||
Down: (cm) => {
|
||||
const tributeContainer = document.querySelector<HTMLElement>('.tribute-container');
|
||||
if (!tributeContainer || tributeContainer.style.display === 'none') {
|
||||
return cm.execCommand('goLineDown');
|
||||
@@ -380,20 +381,16 @@ export class ComboMarkdownEditor {
|
||||
hideElem(this.textareaMarkdownToolbar);
|
||||
}
|
||||
|
||||
value(v?: any) {
|
||||
if (v === undefined) {
|
||||
value(v?: string): string {
|
||||
if (v !== undefined) {
|
||||
if (this.easyMDE) {
|
||||
return this.easyMDE.value();
|
||||
this.easyMDE.value(v);
|
||||
} else {
|
||||
this.textarea.value = v;
|
||||
}
|
||||
return this.textarea.value;
|
||||
this.textareaAutosize?.resizeToFit();
|
||||
}
|
||||
|
||||
if (this.easyMDE) {
|
||||
this.easyMDE.value(v);
|
||||
} else {
|
||||
this.textarea.value = v;
|
||||
}
|
||||
this.textareaAutosize?.resizeToFit();
|
||||
return this.easyMDE ? this.easyMDE.value() : this.textarea.value;
|
||||
}
|
||||
|
||||
focus() {
|
||||
@@ -438,10 +435,9 @@ function applyMonospaceToAllEditors() {
|
||||
}
|
||||
}
|
||||
|
||||
export function getComboMarkdownEditor(el: any): ComboMarkdownEditor | null {
|
||||
export function getComboMarkdownEditor(el: Element | null): ComboMarkdownEditor | null {
|
||||
if (!el) return null;
|
||||
if (el.length) el = el[0];
|
||||
return el._giteaComboMarkdownEditor;
|
||||
return (el as ComboMarkdownEditorContainer)._giteaComboMarkdownEditor ?? null;
|
||||
}
|
||||
|
||||
export async function initComboMarkdownEditor(container: HTMLElement, options:ComboMarkdownEditorOptions = {}) {
|
||||
|
||||
@@ -2,8 +2,15 @@ import {svg} from '../../svg.ts';
|
||||
import type EasyMDE from 'easymde';
|
||||
import type {ComboMarkdownEditor} from './ComboMarkdownEditor.ts';
|
||||
|
||||
export function easyMDEToolbarActions(easyMde: typeof EasyMDE, editor: ComboMarkdownEditor): Record<string, Partial<EasyMDE.ToolbarIcon | string>> {
|
||||
const actions: Record<string, Partial<EasyMDE.ToolbarIcon> | string> = {
|
||||
export type EasyMdeToolbarAction = {
|
||||
name?: string,
|
||||
action: EasyMDE.ToolbarIcon['action'],
|
||||
icon: string,
|
||||
title: string,
|
||||
} | '|';
|
||||
|
||||
export function easyMDEToolbarActions(easyMde: typeof EasyMDE, editor: ComboMarkdownEditor): Record<string, EasyMdeToolbarAction> {
|
||||
const actions: Record<string, EasyMdeToolbarAction> = {
|
||||
'|': '|',
|
||||
'heading-1': {
|
||||
action: easyMde.toggleHeading1,
|
||||
|
||||
@@ -12,18 +12,20 @@ import type Dropzone from '@deltablot/dropzone';
|
||||
|
||||
let uploadIdCounter = 0;
|
||||
|
||||
type UploadFile = File & {_giteaUploadId?: number, uuid?: string};
|
||||
|
||||
export const EventUploadStateChanged = 'ce-upload-state-changed';
|
||||
|
||||
export function triggerUploadStateChanged(target: HTMLElement) {
|
||||
target.dispatchEvent(new CustomEvent(EventUploadStateChanged, {bubbles: true}));
|
||||
}
|
||||
|
||||
function uploadFile(dropzoneEl: HTMLElement, file: File) {
|
||||
return new Promise((resolve) => {
|
||||
function uploadFile(dropzoneEl: HTMLElement, file: UploadFile) {
|
||||
return new Promise<UploadFile>((resolve) => {
|
||||
const curUploadId = uploadIdCounter++;
|
||||
(file as any)._giteaUploadId = curUploadId;
|
||||
file._giteaUploadId = curUploadId;
|
||||
const dropzoneInst = dropzoneEl.dropzone;
|
||||
const onUploadDone = ({file}: {file: any}) => {
|
||||
const onUploadDone = ({file}: {file: UploadFile}) => {
|
||||
if (file._giteaUploadId === curUploadId) {
|
||||
dropzoneInst.off(DropzoneCustomEventUploadDone, onUploadDone);
|
||||
resolve(file);
|
||||
@@ -131,7 +133,7 @@ function getPastedImages(e: ClipboardEvent) {
|
||||
}
|
||||
|
||||
export function initEasyMDEPaste(easyMDE: EasyMDE, dropzoneEl: HTMLElement) {
|
||||
const editor = new CodeMirrorEditor(easyMDE.codemirror as any);
|
||||
const editor = new CodeMirrorEditor(easyMDE.codemirror as CodeMirror.EditorFromTextArea);
|
||||
easyMDE.codemirror.on('paste', (_, e) => {
|
||||
const images = getPastedImages(e);
|
||||
if (!images.length) return;
|
||||
|
||||
@@ -10,16 +10,12 @@ import type TextExpanderElement from '@github/text-expander-element';
|
||||
import type {TextExpanderChangeEvent, TextExpanderResult} from '@github/text-expander-element';
|
||||
|
||||
async function fetchIssueSuggestions(key: string, text: string, signal: AbortSignal): Promise<TextExpanderResult> {
|
||||
const issuePathInfo = parseIssueHref(window.location.href);
|
||||
if (!issuePathInfo.ownerName) {
|
||||
const repoOwnerPathInfo = parseRepoOwnerPathInfo(window.location.pathname);
|
||||
issuePathInfo.ownerName = repoOwnerPathInfo.ownerName;
|
||||
issuePathInfo.repoName = repoOwnerPathInfo.repoName;
|
||||
// then no issuePathInfo.indexString here, it is only used to exclude the current issue when "matchIssue"
|
||||
}
|
||||
if (!issuePathInfo.ownerName) return {matched: false};
|
||||
const hrefPathInfo = parseIssueHref(window.location.href);
|
||||
// the fallback has no indexString, it is only used to exclude the current issue when "matchIssue"
|
||||
const pathInfo = hrefPathInfo ?? parseRepoOwnerPathInfo(window.location.pathname);
|
||||
if (!pathInfo) return {matched: false};
|
||||
|
||||
const matches = await matchIssue(issuePathInfo.ownerName, issuePathInfo.repoName, issuePathInfo.indexString, text, signal);
|
||||
const matches = await matchIssue(pathInfo.ownerName, pathInfo.repoName, hrefPathInfo?.indexString, text, signal);
|
||||
if (!matches.length) return {matched: false};
|
||||
|
||||
const ul = createElementFromAttrs('ul', {class: 'suggestions'});
|
||||
@@ -131,7 +127,8 @@ export function initTextExpander(expander: TextExpanderElement) {
|
||||
}
|
||||
});
|
||||
|
||||
expander.addEventListener('text-expander-value', ({detail}: Record<string, any>) => {
|
||||
expander.addEventListener('text-expander-value', (event) => {
|
||||
const {detail} = event as CustomEvent<{item: HTMLElement, key: string, value: string}>;
|
||||
if (detail?.item) {
|
||||
// add a space after @mentions and #issue as it's likely the user wants one
|
||||
const suffix = ['@', '#'].includes(detail.key) ? ' ' : '';
|
||||
|
||||
Reference in New Issue
Block a user