From 2d13eafd69b368bc75faa25d7ecfbda98a0792f8 Mon Sep 17 00:00:00 2001 From: yp05327 <576951401@qq.com> Date: Mon, 9 Dec 2024 16:30:16 +0900 Subject: [PATCH 1/4] Remove unnecessary border in repo home page sidebar (#32767) --- web_src/css/repo/home.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_src/css/repo/home.css b/web_src/css/repo/home.css index fd8fac27e27..ca5b432804f 100644 --- a/web_src/css/repo/home.css +++ b/web_src/css/repo/home.css @@ -20,7 +20,7 @@ grid-row: 2; padding-left: 1em; } -.repo-home-sidebar-bottom > :first-child { +.repo-home-sidebar-bottom .flex-list > :first-child { border-top: 1px solid var(--color-secondary); /* same to .flex-list > .flex-item + .flex-item */ } @@ -43,7 +43,7 @@ grid-row: 3; padding-left: 0; } - .repo-home-sidebar-bottom > :first-child { + .repo-home-sidebar-bottom .flex-list > :first-child { border-top: 0; } } From 5675efb3e015fb8d87d86d1b79de2200f7dfb9af Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 9 Dec 2024 15:54:59 +0800 Subject: [PATCH 2/4] Fix duplicate dropdown dividers (#32760) Fix #27466 The problem is that any item in the menu could be hidden, pure CSS won't work, and dropdown's builtin "hideDividers" doesn't work with our "scope dividers". The newly introduced "archived" label makes the dividers regression more. --- .eslintrc.yaml | 2 +- modules/templates/helper.go | 8 +- templates/projects/view.tmpl | 5 +- templates/repo/issue/filter_list.tmpl | 5 +- templates/repo/issue/sidebar/label_list.tmpl | 4 +- web_src/js/features/repo-issue.ts | 56 +++++++------- web_src/js/index.ts | 3 +- web_src/js/modules/fomantic/dropdown.test.ts | 56 ++++++++++++++ web_src/js/modules/fomantic/dropdown.ts | 80 ++++++++++++++++++++ web_src/js/webcomponents/overflow-menu.ts | 2 +- 10 files changed, 179 insertions(+), 42 deletions(-) create mode 100644 web_src/js/modules/fomantic/dropdown.test.ts diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 048ca393d1b..04eb0236345 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -818,7 +818,7 @@ rules: unicorn/consistent-destructuring: [2] unicorn/consistent-empty-array-spread: [2] unicorn/consistent-existence-index-check: [0] - unicorn/consistent-function-scoping: [2] + unicorn/consistent-function-scoping: [0] unicorn/custom-error-definition: [0] unicorn/empty-brace-spaces: [2] unicorn/error-message: [0] diff --git a/modules/templates/helper.go b/modules/templates/helper.go index fdfb21925ab..e2628920697 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -294,9 +294,7 @@ func timeEstimateString(timeSec any) string { return util.TimeEstimateString(v) } -type QueryString string - -func queryBuild(a ...any) QueryString { +func queryBuild(a ...any) template.URL { var s string if len(a)%2 == 1 { if v, ok := a[0].(string); ok { @@ -304,7 +302,7 @@ func queryBuild(a ...any) QueryString { panic("queryBuild: invalid argument") } s = v - } else if v, ok := a[0].(QueryString); ok { + } else if v, ok := a[0].(template.URL); ok { s = string(v) } else { panic("queryBuild: invalid argument") @@ -356,7 +354,7 @@ func queryBuild(a ...any) QueryString { if s != "" && s != "&" && s[len(s)-1] == '&' { s = s[:len(s)-1] } - return QueryString(s) + return template.URL(s) } func panicIfDevOrTesting() { diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 71f9d059adb..acaf45e8d28 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -36,10 +36,11 @@ {{range .Labels}} {{$exclusiveScope := .ExclusiveScope}} {{if and (ne $previousExclusiveScope $exclusiveScope)}} -
+
{{end}} {{$previousExclusiveScope = $exclusiveScope}} - + {{if .IsExcluded}} {{svg "octicon-circle-slash"}} {{else if .IsSelected}} diff --git a/templates/repo/issue/filter_list.tmpl b/templates/repo/issue/filter_list.tmpl index 7335c949f47..e686f1d60f6 100644 --- a/templates/repo/issue/filter_list.tmpl +++ b/templates/repo/issue/filter_list.tmpl @@ -30,10 +30,11 @@ {{range .Labels}} {{$exclusiveScope := .ExclusiveScope}} {{if and (ne $previousExclusiveScope $exclusiveScope)}} -
+
{{end}} {{$previousExclusiveScope = $exclusiveScope}} -
+ {{if .IsExcluded}} {{svg "octicon-circle-slash"}} {{else if .IsSelected}} diff --git a/templates/repo/issue/sidebar/label_list.tmpl b/templates/repo/issue/sidebar/label_list.tmpl index fb8f1a667e7..9dd83ba1882 100644 --- a/templates/repo/issue/sidebar/label_list.tmpl +++ b/templates/repo/issue/sidebar/label_list.tmpl @@ -22,7 +22,7 @@ {{range $data.RepoLabels}} {{$exclusiveScope := .ExclusiveScope}} {{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}} -
+
{{end}} {{$previousExclusiveScope = $exclusiveScope}} {{template "repo/issue/sidebar/label_list_item" dict "Label" .}} @@ -32,7 +32,7 @@ {{range $data.OrgLabels}} {{$exclusiveScope := .ExclusiveScope}} {{if and (ne $previousExclusiveScope "_no_scope") (ne $previousExclusiveScope $exclusiveScope)}} -
+
{{end}} {{$previousExclusiveScope = $exclusiveScope}} {{template "repo/issue/sidebar/label_list_item" dict "Label" .}} diff --git a/web_src/js/features/repo-issue.ts b/web_src/js/features/repo-issue.ts index 477edbeb5f4..f5a36b7717e 100644 --- a/web_src/js/features/repo-issue.ts +++ b/web_src/js/features/repo-issue.ts @@ -8,6 +8,7 @@ import {parseIssuePageInfo, toAbsoluteUrl} from '../utils.ts'; import {GET, POST} from '../modules/fetch.ts'; import {showErrorToast} from '../modules/toast.ts'; import {initRepoIssueSidebar} from './repo-issue-sidebar.ts'; +import {fomanticQuery} from '../modules/fomantic/base.ts'; const {appSubUrl} = window.config; @@ -31,34 +32,35 @@ export function initRepoIssueSidebarList() { if (crossRepoSearch === 'true') { issueSearchUrl = `${appSubUrl}/issues/search?q={query}&priority_repo_id=${issuePageInfo.repoId}&type=${issuePageInfo.issueDependencySearchType}`; } - $('#new-dependency-drop-list') - .dropdown({ - apiSettings: { - url: issueSearchUrl, - onResponse(response) { - const filteredResponse = {success: true, results: []}; - const currIssueId = $('#new-dependency-drop-list').data('issue-id'); - // Parse the response from the api to work with our dropdown - $.each(response, (_i, issue) => { - // Don't list current issue in the dependency list. - if (issue.id === currIssueId) { - return; - } - filteredResponse.results.push({ - name: `
#${issue.number} ${htmlEscape(issue.title)}
+ fomanticQuery('#new-dependency-drop-list').dropdown({ + fullTextSearch: true, + apiSettings: { + url: issueSearchUrl, + onResponse(response) { + const filteredResponse = {success: true, results: []}; + const currIssueId = $('#new-dependency-drop-list').data('issue-id'); + // Parse the response from the api to work with our dropdown + $.each(response, (_i, issue) => { + // Don't list current issue in the dependency list. + if (issue.id === currIssueId) { + return; + } + filteredResponse.results.push({ + name: `
#${issue.number} ${htmlEscape(issue.title)}
${htmlEscape(issue.repository.full_name)}
`, - value: issue.id, - }); + value: issue.id, }); - return filteredResponse; - }, - cache: false, + }); + return filteredResponse; }, + cache: false, + }, + }); +} - fullTextSearch: true, - }); - - $('.menu a.label-filter-item').each(function () { +export function initRepoIssueLabelFilter() { + // the "label-filter" is used in 2 templates: projects/view, issue/filter_list (issue list page including the milestone page) + $('.ui.dropdown.label-filter a.label-filter-item').each(function () { $(this).on('click', function (e) { if (e.altKey) { e.preventDefault(); @@ -66,11 +68,9 @@ export function initRepoIssueSidebarList() { } }); }); - - // FIXME: it is wrong place to init ".ui.dropdown.label-filter" - $('.menu .ui.dropdown.label-filter').on('keydown', (e) => { + $('.ui.dropdown.label-filter').on('keydown', (e) => { if (e.altKey && e.key === 'Enter') { - const selectedItem = document.querySelector('.menu .ui.dropdown.label-filter .menu .item.selected'); + const selectedItem = document.querySelector('.ui.dropdown.label-filter .menu .item.selected'); if (selectedItem) { excludeLabel(selectedItem); } diff --git a/web_src/js/index.ts b/web_src/js/index.ts index f93c3495af4..2964ef55720 100644 --- a/web_src/js/index.ts +++ b/web_src/js/index.ts @@ -29,7 +29,7 @@ import { initRepoIssueWipTitle, initRepoPullRequestMergeInstruction, initRepoPullRequestAllowMaintainerEdit, - initRepoPullRequestReview, initRepoIssueSidebarList, + initRepoPullRequestReview, initRepoIssueSidebarList, initRepoIssueLabelFilter, } from './features/repo-issue.ts'; import {initRepoEllipsisButton, initCommitStatuses} from './features/repo-commit.ts'; import {initRepoTopicBar} from './features/repo-home.ts'; @@ -181,6 +181,7 @@ onDomReady(() => { initRepoGraphGit, initRepoIssueContentHistory, initRepoIssueList, + initRepoIssueLabelFilter, initRepoIssueSidebarList, initRepoIssueReferenceRepositorySearch, initRepoIssueWipTitle, diff --git a/web_src/js/modules/fomantic/dropdown.test.ts b/web_src/js/modules/fomantic/dropdown.test.ts new file mode 100644 index 00000000000..587e0bca7c6 --- /dev/null +++ b/web_src/js/modules/fomantic/dropdown.test.ts @@ -0,0 +1,56 @@ +import {createElementFromHTML} from '../../utils/dom.ts'; +import {hideScopedEmptyDividers} from './dropdown.ts'; + +test('hideScopedEmptyDividers-simple', () => { + const container = createElementFromHTML(`
+
+
a
+
+
+
+
b
+
+
`); + hideScopedEmptyDividers(container); + expect(container.innerHTML).toEqual(` + +
a
+ + +
+
b
+ +`); +}); + +test('hideScopedEmptyDividers-hidden1', () => { + const container = createElementFromHTML(`
+
a
+
+
b
+
`); + hideScopedEmptyDividers(container); + expect(container.innerHTML).toEqual(` +
a
+ +
b
+`); +}); + +test('hideScopedEmptyDividers-hidden2', () => { + const container = createElementFromHTML(`
+
a
+
+
b
+
+
c
+
`); + hideScopedEmptyDividers(container); + expect(container.innerHTML).toEqual(` +
a
+ +
b
+ +
c
+`); +}); diff --git a/web_src/js/modules/fomantic/dropdown.ts b/web_src/js/modules/fomantic/dropdown.ts index d8fb4d6e6e4..6d0f12cb438 100644 --- a/web_src/js/modules/fomantic/dropdown.ts +++ b/web_src/js/modules/fomantic/dropdown.ts @@ -59,6 +59,12 @@ function updateSelectionLabel(label: HTMLElement) { } } +function processMenuItems($dropdown, dropdownCall) { + const hideEmptyDividers = dropdownCall('setting', 'hideDividers') === 'empty'; + const itemsMenu = $dropdown[0].querySelector('.scrolling.menu') || $dropdown[0].querySelector('.menu'); + if (hideEmptyDividers) hideScopedEmptyDividers(itemsMenu); +} + // delegate the dropdown's template functions and callback functions to add aria attributes. function delegateOne($dropdown: any) { const dropdownCall = fomanticDropdownFn.bind($dropdown); @@ -72,6 +78,18 @@ function delegateOne($dropdown: any) { // * If the "dropdown icon" is clicked again when the menu is visible, Fomantic calls "blurSearch", so hide the menu dropdownCall('internal', 'blurSearch', function () { oldBlurSearch.call(this); dropdownCall('hide') }); + const oldFilterItems = dropdownCall('internal', 'filterItems'); + dropdownCall('internal', 'filterItems', function (...args: any[]) { + oldFilterItems.call(this, ...args); + processMenuItems($dropdown, dropdownCall); + }); + + const oldShow = dropdownCall('internal', 'show'); + dropdownCall('internal', 'show', function (...args: any[]) { + oldShow.call(this, ...args); + processMenuItems($dropdown, dropdownCall); + }); + // the "template" functions are used for dynamic creation (eg: AJAX) const dropdownTemplates = {...dropdownCall('setting', 'templates'), t: performance.now()}; const dropdownTemplatesMenuOld = dropdownTemplates.menu; @@ -271,3 +289,65 @@ function attachDomEvents(dropdown: HTMLElement, focusable: HTMLElement, menu: HT ignoreClickPreEvents = ignoreClickPreVisible = 0; }, true); } + +// Although Fomantic Dropdown supports "hideDividers", it doesn't really work with our "scoped dividers" +// At the moment, "label dropdown items" use scopes, a sample case is: +// * a-label +// * divider +// * scope/1 +// * scope/2 +// * divider +// * z-label +// when the "scope/*" are filtered out, we'd like to see "a-label" and "z-label" without the divider. +export function hideScopedEmptyDividers(container: Element) { + const visibleItems: Element[] = []; + const curScopeVisibleItems: Element[] = []; + let curScope: string = '', lastVisibleScope: string = ''; + const isScopedDivider = (item: Element) => item.matches('.divider') && item.hasAttribute('data-scope'); + const hideDivider = (item: Element) => item.classList.add('hidden', 'transition'); // dropdown has its own classes to hide items + + const handleScopeSwitch = (itemScope: string) => { + if (curScopeVisibleItems.length === 1 && isScopedDivider(curScopeVisibleItems[0])) { + hideDivider(curScopeVisibleItems[0]); + } else if (curScopeVisibleItems.length) { + if (isScopedDivider(curScopeVisibleItems[0]) && lastVisibleScope === curScope) { + hideDivider(curScopeVisibleItems[0]); + curScopeVisibleItems.shift(); + } + visibleItems.push(...curScopeVisibleItems); + lastVisibleScope = curScope; + } + curScope = itemScope; + curScopeVisibleItems.length = 0; + }; + + // hide the scope dividers if the scope items are empty + for (const item of container.children) { + const itemScope = item.getAttribute('data-scope') || ''; + if (itemScope !== curScope) { + handleScopeSwitch(itemScope); + } + if (!item.classList.contains('filtered') && !item.classList.contains('tw-hidden')) { + curScopeVisibleItems.push(item as HTMLElement); + } + } + handleScopeSwitch(''); + + // hide all leading and trailing dividers + while (visibleItems.length) { + if (!visibleItems[0].matches('.divider')) break; + hideDivider(visibleItems[0]); + visibleItems.shift(); + } + while (visibleItems.length) { + if (!visibleItems[visibleItems.length - 1].matches('.divider')) break; + hideDivider(visibleItems[visibleItems.length - 1]); + visibleItems.pop(); + } + // hide all duplicate dividers, hide current divider if next sibling is still divider + // no need to update "visibleItems" array since this is the last loop + for (const item of visibleItems) { + if (!item.matches('.divider')) continue; + if (item.nextElementSibling?.matches('.divider')) hideDivider(item); + } +} diff --git a/web_src/js/webcomponents/overflow-menu.ts b/web_src/js/webcomponents/overflow-menu.ts index 777d7dc65dd..4e729a268a0 100644 --- a/web_src/js/webcomponents/overflow-menu.ts +++ b/web_src/js/webcomponents/overflow-menu.ts @@ -12,7 +12,7 @@ window.customElements.define('overflow-menu', class extends HTMLElement { mutationObserver: MutationObserver; lastWidth: number; - updateItems = throttle(100, () => { // eslint-disable-line unicorn/consistent-function-scoping -- https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2088 + updateItems = throttle(100, () => { if (!this.tippyContent) { const div = document.createElement('div'); div.classList.add('tippy-target'); From 57a5e9acf8f2362539f12e7eec1625363b547ec5 Mon Sep 17 00:00:00 2001 From: silverwind Date: Mon, 9 Dec 2024 18:03:36 +0100 Subject: [PATCH 3/4] Make Monaco theme follow browser, fully type codeeditor.ts (#32756) 1. Monaco's theme now follows changes in dark/light mode setting, this works via [`MediaQueryList`](https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList)'s [change event](https://developer.mozilla.org/en-US/docs/Web/API/MediaQueryList/change_event). 2. Fully type the file, it now passes typescript strict mode. --- web_src/js/features/codeeditor.ts | 113 +++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 34 deletions(-) diff --git a/web_src/js/features/codeeditor.ts b/web_src/js/features/codeeditor.ts index 93b2042fa92..62bfccd1393 100644 --- a/web_src/js/features/codeeditor.ts +++ b/web_src/js/features/codeeditor.ts @@ -1,11 +1,30 @@ import tinycolor from 'tinycolor2'; import {basename, extname, isObject, isDarkTheme} from '../utils.ts'; import {onInputDebounce} from '../utils/dom.ts'; +import type MonacoNamespace from 'monaco-editor'; -const languagesByFilename = {}; -const languagesByExt = {}; +type Monaco = typeof MonacoNamespace; +type IStandaloneCodeEditor = MonacoNamespace.editor.IStandaloneCodeEditor; +type IEditorOptions = MonacoNamespace.editor.IEditorOptions; +type IGlobalEditorOptions = MonacoNamespace.editor.IGlobalEditorOptions; +type ITextModelUpdateOptions = MonacoNamespace.editor.ITextModelUpdateOptions; +type MonacoOpts = IEditorOptions & IGlobalEditorOptions & ITextModelUpdateOptions; -const baseOptions = { +type EditorConfig = { + indent_style?: 'tab' | 'space', + indent_size?: string | number, // backend emits this as string + tab_width?: string | number, // backend emits this as string + end_of_line?: 'lf' | 'cr' | 'crlf', + charset?: 'latin1' | 'utf-8' | 'utf-8-bom' | 'utf-16be' | 'utf-16le', + trim_trailing_whitespace?: boolean, + insert_final_newline?: boolean, + root?: boolean, +} + +const languagesByFilename: Record = {}; +const languagesByExt: Record = {}; + +const baseOptions: MonacoOpts = { fontFamily: 'var(--fonts-monospace)', fontSize: 14, // https://github.com/microsoft/monaco-editor/issues/2242 guides: {bracketPairs: false, indentation: false}, @@ -15,21 +34,23 @@ const baseOptions = { overviewRulerLanes: 0, renderLineHighlight: 'all', renderLineHighlightOnlyWhenFocus: true, - rulers: false, + rulers: [], scrollbar: {horizontalScrollbarSize: 6, verticalScrollbarSize: 6}, scrollBeyondLastLine: false, automaticLayout: true, }; -function getEditorconfig(input: HTMLInputElement) { +function getEditorconfig(input: HTMLInputElement): EditorConfig | null { + const json = input.getAttribute('data-editorconfig'); + if (!json) return null; try { - return JSON.parse(input.getAttribute('data-editorconfig')); + return JSON.parse(json); } catch { return null; } } -function initLanguages(monaco) { +function initLanguages(monaco: Monaco): void { for (const {filenames, extensions, id} of monaco.languages.getLanguages()) { for (const filename of filenames || []) { languagesByFilename[filename] = id; @@ -40,35 +61,26 @@ function initLanguages(monaco) { } } -function getLanguage(filename) { +function getLanguage(filename: string): string { return languagesByFilename[filename] || languagesByExt[extname(filename)] || 'plaintext'; } -function updateEditor(monaco, editor, filename, lineWrapExts) { +function updateEditor(monaco: Monaco, editor: IStandaloneCodeEditor, filename: string, lineWrapExts: string[]): void { editor.updateOptions(getFileBasedOptions(filename, lineWrapExts)); const model = editor.getModel(); + if (!model) return; const language = model.getLanguageId(); const newLanguage = getLanguage(filename); if (language !== newLanguage) monaco.editor.setModelLanguage(model, newLanguage); } // export editor for customization - https://github.com/go-gitea/gitea/issues/10409 -function exportEditor(editor) { +function exportEditor(editor: IStandaloneCodeEditor): void { if (!window.codeEditors) window.codeEditors = []; if (!window.codeEditors.includes(editor)) window.codeEditors.push(editor); } -export async function createMonaco(textarea: HTMLTextAreaElement, filename: string, editorOpts: Record) { - const monaco = await import(/* webpackChunkName: "monaco" */'monaco-editor'); - - initLanguages(monaco); - let {language, ...other} = editorOpts; - if (!language) language = getLanguage(filename); - - const container = document.createElement('div'); - container.className = 'monaco-editor-container'; - textarea.parentNode.append(container); - +function updateTheme(monaco: Monaco): void { // https://github.com/microsoft/monaco-editor/issues/2427 // also, monaco can only parse 6-digit hex colors, so we convert the colors to that format const styles = window.getComputedStyle(document.documentElement); @@ -80,6 +92,7 @@ export async function createMonaco(textarea: HTMLTextAreaElement, filename: stri rules: [ { background: getColor('--color-code-bg'), + token: '', }, ], colors: { @@ -101,6 +114,26 @@ export async function createMonaco(textarea: HTMLTextAreaElement, filename: stri 'focusBorder': '#0000', // prevent blue border }, }); +} + +type CreateMonacoOpts = MonacoOpts & {language?: string}; + +export async function createMonaco(textarea: HTMLTextAreaElement, filename: string, opts: CreateMonacoOpts): Promise<{monaco: Monaco, editor: IStandaloneCodeEditor}> { + const monaco = await import(/* webpackChunkName: "monaco" */'monaco-editor'); + + initLanguages(monaco); + let {language, ...other} = opts; + if (!language) language = getLanguage(filename); + + const container = document.createElement('div'); + container.className = 'monaco-editor-container'; + if (!textarea.parentNode) throw new Error('Parent node absent'); + textarea.parentNode.append(container); + + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { + updateTheme(monaco); + }); + updateTheme(monaco); const editor = monaco.editor.create(container, { value: textarea.value, @@ -114,8 +147,12 @@ export async function createMonaco(textarea: HTMLTextAreaElement, filename: stri ]); const model = editor.getModel(); + if (!model) throw new Error('Unable to get editor model'); model.onDidChangeContent(() => { - textarea.value = editor.getValue({preserveBOM: true}); + textarea.value = editor.getValue({ + preserveBOM: true, + lineEnding: '', + }); textarea.dispatchEvent(new Event('change')); // seems to be needed for jquery-are-you-sure }); @@ -127,13 +164,13 @@ export async function createMonaco(textarea: HTMLTextAreaElement, filename: stri return {monaco, editor}; } -function getFileBasedOptions(filename: string, lineWrapExts: string[]) { +function getFileBasedOptions(filename: string, lineWrapExts: string[]): MonacoOpts { return { wordWrap: (lineWrapExts || []).includes(extname(filename)) ? 'on' : 'off', }; } -function togglePreviewDisplay(previewable: boolean) { +function togglePreviewDisplay(previewable: boolean): void { const previewTab = document.querySelector('a[data-tab="preview"]'); if (!previewTab) return; @@ -145,19 +182,19 @@ function togglePreviewDisplay(previewable: boolean) { // then the "preview" tab becomes inactive (hidden), so the "write" tab should become active if (previewTab.classList.contains('active')) { const writeTab = document.querySelector('a[data-tab="write"]'); - writeTab.click(); + writeTab?.click(); } } } -export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameInput: HTMLInputElement) { +export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameInput: HTMLInputElement): Promise { const filename = basename(filenameInput.value); const previewableExts = new Set((textarea.getAttribute('data-previewable-extensions') || '').split(',')); const lineWrapExts = (textarea.getAttribute('data-line-wrap-extensions') || '').split(','); - const previewable = previewableExts.has(extname(filename)); + const isPreviewable = previewableExts.has(extname(filename)); const editorConfig = getEditorconfig(filenameInput); - togglePreviewDisplay(previewable); + togglePreviewDisplay(isPreviewable); const {monaco, editor} = await createMonaco(textarea, filename, { ...baseOptions, @@ -175,14 +212,22 @@ export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameIn return editor; } -function getEditorConfigOptions(ec: Record): Record { - if (!isObject(ec)) return {}; +function getEditorConfigOptions(ec: EditorConfig | null): MonacoOpts { + if (!ec || !isObject(ec)) return {}; - const opts: Record = {}; + const opts: MonacoOpts = {}; opts.detectIndentation = !('indent_style' in ec) || !('indent_size' in ec); - if ('indent_size' in ec) opts.indentSize = Number(ec.indent_size); - if ('tab_width' in ec) opts.tabSize = Number(ec.tab_width) || opts.indentSize; - if ('max_line_length' in ec) opts.rulers = [Number(ec.max_line_length)]; + + if ('indent_size' in ec) { + opts.indentSize = Number(ec.indent_size); + } + if ('tab_width' in ec) { + opts.tabSize = Number(ec.tab_width) || Number(ec.indent_size); + } + if ('max_line_length' in ec) { + opts.rulers = [Number(ec.max_line_length)]; + } + opts.trimAutoWhitespace = ec.trim_trailing_whitespace === true; opts.insertSpaces = ec.indent_style === 'space'; opts.useTabStops = ec.indent_style === 'tab'; From 43ca67eb8c7838e5e94846904f9116c05980af36 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Tue, 10 Dec 2024 00:34:44 +0000 Subject: [PATCH 4/4] [skip ci] Updated translations via Crowdin --- options/locale/locale_fr-FR.ini | 25 ++++++++++++++ options/locale/locale_ga-IE.ini | 25 ++++++++++++++ options/locale/locale_id-ID.ini | 58 +++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+) diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index c7562c7f3bc..c943c924c8f 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -145,6 +145,7 @@ confirm_delete_selected=Êtes-vous sûr de vouloir supprimer tous les éléments name=Nom value=Valeur +readme=Lisez-moi filter=Filtrer filter.clear=Effacer le filtre @@ -1032,6 +1033,8 @@ fork_to_different_account=Créer une bifurcation vers un autre compte fork_visibility_helper=La visibilité d'un dépôt bifurqué ne peut pas être modifiée. fork_branch=Branche à cloner sur la bifurcation all_branches=Toutes les branches +view_all_branches=Voir toutes les branches +view_all_tags=Voir toutes les étiquettes fork_no_valid_owners=Ce dépôt ne peut pas être bifurqué car il n’a pas de propriétaire valide. fork.blocked_user=Impossible de bifurquer le dépôt car vous êtes bloqué par son propriétaire. use_template=Utiliser ce modèle @@ -1043,6 +1046,8 @@ generate_repo=Générer un dépôt generate_from=Générer depuis repo_desc=Description repo_desc_helper=Décrire brièvement votre dépôt +repo_no_desc=Aucune description fournie +repo_lang=Langue repo_gitignore_helper=Sélectionner quelques .gitignore prédéfinies repo_gitignore_helper_desc=De nombreux outils et compilateurs génèrent des fichiers résiduels qui n'ont pas besoin d'être supervisés par git. Composez un .gitignore à l’aide de cette liste des languages de programmation courants. issue_labels=Jeu de labels pour les tickets @@ -1668,12 +1673,26 @@ issues.delete.title=Supprimer ce ticket ? issues.delete.text=Voulez-vous vraiment supprimer ce ticket ? (Cette opération supprimera définitivement tout le contenu. Envisagez plutôt de le fermer si vous avez l'intention de l'archiver) issues.tracker=Minuteur +issues.timetracker_timer_start=Démarrer le minuteur +issues.timetracker_timer_stop=Arrêter le minuteur +issues.timetracker_timer_discard=Annuler le minuteur +issues.timetracker_timer_manually_add=Pointer du temps +issues.time_estimate_placeholder=1h 2m +issues.time_estimate_set=Définir le temps estimé +issues.time_estimate_display=Estimation : %s +issues.change_time_estimate_at=a changé le temps estimé à %s %s +issues.remove_time_estimate_at=a supprimé le temps estimé %s +issues.time_estimate_invalid=Le format du temps estimé est invalide +issues.start_tracking_history=`a commencé son travail %s.` issues.tracker_auto_close=Le minuteur sera automatiquement arrêté quand le ticket sera fermé. issues.tracking_already_started=`Vous avez déjà un minuteur en cours sur
un autre ticket !` +issues.stop_tracking_history=`a fini de travailler sur %s %s.` issues.cancel_tracking_history=`a abandonné son minuteur %s.` issues.del_time=Supprimer ce minuteur du journal +issues.add_time_history=`a pointé du temps de travail %s.` issues.del_time_history=`a supprimé son temps de travail %s.` +issues.add_time_manually=Temps pointé manuellement issues.add_time_hours=Heures issues.add_time_minutes=Minutes issues.add_time_sum_to_small=Aucun minuteur n'a été saisi. @@ -1926,6 +1945,10 @@ pulls.delete.title=Supprimer cette demande d'ajout ? pulls.delete.text=Voulez-vous vraiment supprimer cet demande d'ajout ? (Cela supprimera définitivement tout le contenu. Envisagez de le fermer à la place, si vous avez l'intention de le garder archivé) pulls.recently_pushed_new_branches=Vous avez soumis sur la branche %[1]s %[2]s +pulls.upstream_diverging_prompt_behind_1=Cette branche est en retard de %d révision sur %s +pulls.upstream_diverging_prompt_behind_n=Cette branche est en retard de %d révisions sur %s +pulls.upstream_diverging_prompt_base_newer=La branche de base %s a de nouveaux changements +pulls.upstream_diverging_merge=Synchroniser la bifurcation pull.deleted_branch=(supprimé) : %s pull.agit_documentation=Voir la documentation sur AGit @@ -3513,6 +3536,8 @@ alpine.repository=Informations sur le Dépôt alpine.repository.branches=Branches alpine.repository.repositories=Dépôts alpine.repository.architectures=Architectures +arch.registry=Ajouter un serveur avec un dépôt et une architecture liés dans /etc/pacman.conf : +arch.install=Synchroniser le paquet avec pacman : arch.repository=Informations sur le Dépôt arch.repository.repositories=Dépôts arch.repository.architectures=Architectures diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index dc6ff2f481f..b46a8f75f30 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -145,6 +145,7 @@ confirm_delete_selected=Deimhnigh chun gach earra roghnaithe a scriosadh? name=Ainm value=Luach +readme=Readme filter=Scagaire filter.clear=Scagaire Soiléir @@ -1032,6 +1033,8 @@ fork_to_different_account=Forc chuig cuntas difriúil fork_visibility_helper=Ní féidir infheictheacht stór forcailte a athrú. fork_branch=Brainse le clónú chuig an bhforc all_branches=Gach brainse +view_all_branches=Féach ar gach brainse +view_all_tags=Féach ar gach clib fork_no_valid_owners=Ní féidir an stór seo a fhorcáil toisc nach bhfuil úinéirí bailí ann. fork.blocked_user=Ní féidir an stór a fhorcáil toisc go bhfuil úinéir an stórais bac ort. use_template=Úsáid an teimpléad seo @@ -1043,6 +1046,8 @@ generate_repo=Cruthaigh Stóras generate_from=Gin Ó repo_desc=Cur síos repo_desc_helper=Cuir isteach tuairisc ghearr (roghnach) +repo_no_desc=Níor tugadh tuairisc +repo_lang=Teangacha repo_gitignore_helper=Roghnaigh teimpléid .gitignore. repo_gitignore_helper_desc=Roghnaigh na comhaid nach bhfuil le rianú ó liosta teimpléid do theangacha coitianta. Cuirtear déantáin tipiciúla a ghineann uirlisí tógála gach teanga san áireamh ar.gitignore de réir réamhshocraithe. issue_labels=Lipéid Eisiúna @@ -1668,12 +1673,26 @@ issues.delete.title=Scrios an t-eagrán seo? issues.delete.text=An bhfuil tú cinnte gur mhaith leat an cheist seo a scriosadh? (Bainfidh sé seo an t-inneachar go léir go buan. Smaoinigh ar é a dhúnadh ina ionad sin, má tá sé i gceist agat é a choinneáil i gcartlann) issues.tracker=Rianaitheoir Ama +issues.timetracker_timer_start=Amadóir tosaithe +issues.timetracker_timer_stop=Stop an t-amadóir +issues.timetracker_timer_discard=Déan an t-amadóir a scriosadh +issues.timetracker_timer_manually_add=Cuir Am leis +issues.time_estimate_placeholder=1u 2n +issues.time_estimate_set=Socraigh am measta +issues.time_estimate_display=Meastachán: %s +issues.change_time_estimate_at=d'athraigh an meastachán ama go %s %s +issues.remove_time_estimate_at=baineadh meastachán ama %s +issues.time_estimate_invalid=Tá formáid meastachán ama neamhbhailí +issues.start_tracking_history=thosaigh ag obair %s issues.tracker_auto_close=Stopfar ama go huathoibríoch nuair a dhúnfar an tsaincheist seo issues.tracking_already_started=`Tá tús curtha agat cheana féin ag rianú ama ar eagrán eile!` +issues.stop_tracking_history=d'oibrigh do %s %s issues.cancel_tracking_history=`rianú ama curtha ar ceal %s` issues.del_time=Scrios an log ama seo +issues.add_time_history=cuireadh am caite %s %s leis issues.del_time_history=`an t-am caite scriosta %s` +issues.add_time_manually=Cuir Am leis de Láimh issues.add_time_hours=Uaireanta issues.add_time_minutes=Miontuairi issues.add_time_sum_to_small=Níor iontráilíodh aon am. @@ -1926,6 +1945,10 @@ pulls.delete.title=Scrios an t-iarratas tarraingthe seo? pulls.delete.text=An bhfuil tú cinnte gur mhaith leat an t-iarratas tarraingthe seo a scriosadh? (Bainfidh sé seo an t-inneachar go léir go buan. Smaoinigh ar é a dhúnadh ina ionad sin, má tá sé i gceist agat é a choinneáil i gcartlann) pulls.recently_pushed_new_branches=Bhrúigh tú ar bhrainse %[1]s %[2]s +pulls.upstream_diverging_prompt_behind_1=Tá an brainse seo %d tiomantas taobh thiar de %s +pulls.upstream_diverging_prompt_behind_n=Tá an brainse seo %d geallta taobh thiar de %s +pulls.upstream_diverging_prompt_base_newer=Tá athruithe nua ar an mbunbhrainse %s +pulls.upstream_diverging_merge=Forc sionc pull.deleted_branch=(scriosta): %s pull.agit_documentation=Déan athbhreithniú ar dhoiciméid faoi AGit @@ -3513,6 +3536,8 @@ alpine.repository=Eolas Stórais alpine.repository.branches=Brainsí alpine.repository.repositories=Stórais alpine.repository.architectures=Ailtireachtaí +arch.registry=Cuir freastalaí leis an stór agus an ailtireacht ghaolmhar le /etc/pacman.conf: +arch.install=Sioncronaigh pacáiste le pacman: arch.repository=Eolas Stórais arch.repository.repositories=Stórais arch.repository.architectures=Ailtireachtaí diff --git a/options/locale/locale_id-ID.ini b/options/locale/locale_id-ID.ini index 231691b4a77..237323a0fc4 100644 --- a/options/locale/locale_id-ID.ini +++ b/options/locale/locale_id-ID.ini @@ -76,29 +76,79 @@ loading=Memuat… +archived=Diarsipkan concept_code_repository=Repositori +show_full_screen=Tampilkan layar penuh +download_logs=Unduh Logs +confirm_delete_selected=Konfirmasi untuk menghapus semua item yang dipilih? name=Nama +value=Nilai +readme=Baca saya +filter=Saring +filter.clear=Hapus Filter +filter.is_archived=Diarsipkan +filter.not_archived=Tidak Diarsipkan filter.is_template=Contoh +filter.public=Publik filter.private=Pribadi +no_results_found=Hasil tidak ditemukan. [search] +search=Cari... +type_tooltip=Tipe pencarian +fuzzy_tooltip=Termasuk juga hasil yang mendekati kata pencarian +exact_tooltip=Hanya menampilkan hasil yang cocok dengan istilah pencarian +repo_kind=Cari repo... +user_kind=Telusuri pengguna... +org_kind=Cari organisasi... +team_kind=Cari tim... +code_kind=Cari kode... +code_search_unavailable=Pencarian kode saat ini tidak tersedia. Silahkan hubungi administrator. +branch_kind=Cari cabang... [aria] +navbar=Bar Navigasi +footer=Footer +footer.software=Tentang Software +footer.links=Tautan [heatmap] +number_of_contributions_in_the_last_12_months=%s Kontribusi pada 12 bulan terakhir +no_contributions=Belum ada kontribusi +less=Lebih sedikit +more=Lebih banyak [editor] +buttons.heading.tooltip=Tambahkan heading +buttons.bold.tooltip=Tambahkan teks Tebal +buttons.italic.tooltip=Tambahkan teks Miring +buttons.quote.tooltip=Kutip teks +buttons.code.tooltip=Tambah Kode +buttons.link.tooltip=Tambahkan tautan +buttons.list.unordered.tooltip=Tambah daftar titik +buttons.list.ordered.tooltip=Tambah daftar angka +buttons.list.task.tooltip=Tambahkan daftar tugas buttons.table.add.insert=Tambah +buttons.mention.tooltip=Tandai pengguna atau tim +buttons.ref.tooltip=Merujuk pada isu atau permintaan tarik +buttons.switch_to_legacy.tooltip=Gunakan editor versi lama +buttons.enable_monospace_font=Aktifkan font monospace +buttons.disable_monospace_font=Non-Aktifkan font monospace [filter] +string.asc=A - Z +string.desc=Z - A [error] +occurred=Terjadi kesalahan +report_message=Jika Anda yakin ini adalah bug Gitea, silakan cari isu di GitHub atau buka isu baru jika diperlukan. +not_found=Target tidak dapat ditemukan. [startpage] app_desc=Sebuah layanan hosting Git sendiri yang tanpa kesulitan @@ -118,8 +168,10 @@ path=Jalur repo_path=Jalur akar repositori +email_title=Pengaturan email smtp_addr=Host SMTP smtp_port=Port SMTP +smtp_from=Kirim Email Sebagai register_confirm=Perlu Konfirmasi Email Saat Pendaftaran mail_notify=Aktifkan Notifikasi Email disable_gravatar=Menonaktifkan Gravatar @@ -140,6 +192,7 @@ my_orgs=Organisasi Saya my_mirrors=Duplikat Saya view_home=Lihat %s +show_archived=Diarsipkan show_private=Pribadi @@ -481,6 +534,7 @@ email_notifications.enable=Aktifkan Pemberitahuan Surel email_notifications.disable=Nonaktifkan Email Notifikasi email_notifications.submit=Pasang Pengaturan Email +visibility.public=Publik visibility.private=Pribadi [repo] @@ -522,7 +576,9 @@ delete_preexisting_label=Hapus desc.private=Pribadi +desc.public=Publik desc.template=Contoh +desc.archived=Diarsipkan template.webhooks=Webhooks template.topics=Topik @@ -947,6 +1003,7 @@ settings=Pengaturan settings.full_name=Nama Lengkap settings.website=Situs web settings.location=Lokasi +settings.visibility.public=Publik settings.visibility.private_shortname=Pribadi settings.update_settings=Perbarui Setelan @@ -1033,6 +1090,7 @@ users.created=Dibuat users.edit=Edit users.auth_source=Sumber Otentikasi users.local=Lokal +users.list_status_filter.menu_text=Saring users.list_status_filter.is_admin=Pengelola emails.activated=Diaktifkan