mirror of
https://github.com/go-gitea/gitea.git
synced 2026-02-14 23:19:24 +00:00
Add code editor setting dropdowns (#36534)
Adds three `<select>` controls on top right for indent style, indent size, and line wrap to the code editor (`_edit`), diff patch editor (`_diffpatch`) and git hook editor (`/settings/hooks/git/pre-receive`). The git hooks editor is restyled to wrap the content in a box. Also included is a bugfix for the git hooks editor where monaco was not initialized correctly. --------- Signed-off-by: silverwind <me@silverwind.io> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -148,6 +148,13 @@
|
||||
"filter.private": "Private",
|
||||
"no_results_found": "No results found.",
|
||||
"internal_error_skipped": "Internal error occurred but is skipped: %s",
|
||||
"characters_spaces": "Spaces",
|
||||
"characters_tabs": "Tabs",
|
||||
"text_indent_style": "Indent style",
|
||||
"text_indent_size": "Indent size",
|
||||
"text_line_wrap": "Wrap",
|
||||
"text_line_nowrap": "No wrap",
|
||||
"text_line_wrap_mode": "Line wrap mode",
|
||||
"search.search": "Search…",
|
||||
"search.type_tooltip": "Search type",
|
||||
"search.fuzzy": "Fuzzy",
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/httplib"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -78,8 +77,6 @@ func prepareEditorPageFormOptions(ctx *context.Context, editorAction string) *co
|
||||
ctx.Data["CommitFormOptions"] = commitFormOptions
|
||||
|
||||
// for online editor
|
||||
ctx.Data["PreviewableExtensions"] = strings.Join(markup.PreviewableExtensions(), ",")
|
||||
ctx.Data["LineWrapExtensions"] = strings.Join(setting.Repository.Editor.LineWrapExtensions, ",")
|
||||
ctx.Data["IsEditingFileOnly"] = ctx.FormString("return_uri") != ""
|
||||
ctx.Data["ReturnURI"] = ctx.FormString("return_uri")
|
||||
|
||||
@@ -321,7 +318,7 @@ func EditFile(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["EditorconfigJson"] = getContextRepoEditorConfig(ctx, ctx.Repo.TreePath)
|
||||
ctx.Data["CodeEditorConfig"] = getCodeEditorConfig(ctx, ctx.Repo.TreePath)
|
||||
ctx.HTML(http.StatusOK, tplEditFile)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ func NewDiffPatch(ctx *context.Context) {
|
||||
}
|
||||
|
||||
ctx.Data["PageIsPatch"] = true
|
||||
ctx.Data["CodeEditorConfig"] = CodeEditorConfig{} // not really editing a file, so no need to fill in the config
|
||||
ctx.HTML(http.StatusOK, tplPatchFile)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
git_model "code.gitea.io/gitea/models/git"
|
||||
@@ -14,9 +15,11 @@ import (
|
||||
user_model "code.gitea.io/gitea/models/user"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/gitrepo"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/markup"
|
||||
repo_module "code.gitea.io/gitea/modules/repository"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
context_service "code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
@@ -62,17 +65,33 @@ func getClosestParentWithFiles(gitRepo *git.Repository, branchName, originTreePa
|
||||
return f(originTreePath, commit)
|
||||
}
|
||||
|
||||
// getContextRepoEditorConfig returns the editorconfig JSON string for given treePath or "null"
|
||||
func getContextRepoEditorConfig(ctx *context_service.Context, treePath string) string {
|
||||
// CodeEditorConfig is also used by frontend, defined in "codeeditor.ts"
|
||||
type CodeEditorConfig struct {
|
||||
PreviewableExtensions []string `json:"previewable_extensions"`
|
||||
LineWrapExtensions []string `json:"line_wrap_extensions"`
|
||||
LineWrapOn bool `json:"line_wrap_on"`
|
||||
|
||||
IndentStyle string `json:"indent_style"`
|
||||
IndentSize int `json:"indent_size"`
|
||||
TabWidth int `json:"tab_width"`
|
||||
TrimTrailingWhitespace *bool `json:"trim_trailing_whitespace,omitempty"`
|
||||
}
|
||||
|
||||
func getCodeEditorConfig(ctx *context_service.Context, treePath string) (ret CodeEditorConfig) {
|
||||
ret.PreviewableExtensions = markup.PreviewableExtensions()
|
||||
ret.LineWrapExtensions = setting.Repository.Editor.LineWrapExtensions
|
||||
ret.LineWrapOn = util.SliceContainsString(ret.LineWrapExtensions, path.Ext(treePath), true)
|
||||
ec, _, err := ctx.Repo.GetEditorconfig()
|
||||
if err == nil {
|
||||
def, err := ec.GetDefinitionForFilename(treePath)
|
||||
if err == nil {
|
||||
jsonStr, _ := json.Marshal(def)
|
||||
return string(jsonStr)
|
||||
ret.IndentStyle = def.IndentStyle
|
||||
ret.IndentSize, _ = strconv.Atoi(def.IndentSize)
|
||||
ret.TabWidth = def.TabWidth
|
||||
ret.TrimTrailingWhitespace = def.TrimTrailingWhitespace
|
||||
}
|
||||
}
|
||||
return "null"
|
||||
return ret
|
||||
}
|
||||
|
||||
// getParentTreeFields returns list of parent tree names and corresponding tree paths based on given treePath.
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/routers/web/repo"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
@@ -41,6 +42,7 @@ func GitHooksEdit(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["Hook"] = hook
|
||||
ctx.Data["CodeEditorConfig"] = repo.CodeEditorConfig{} // not really editing a repo file, so no editor config
|
||||
ctx.HTML(http.StatusOK, tplGithookEdit)
|
||||
}
|
||||
|
||||
|
||||
@@ -80,10 +80,10 @@ export default {
|
||||
semibold: 'var(--font-weight-semibold)',
|
||||
bold: 'var(--font-weight-bold)',
|
||||
},
|
||||
fontSize: { // not using `rem` units because our root is currently 14px
|
||||
'xs': '12px',
|
||||
'sm': '14px',
|
||||
'base': '16px',
|
||||
fontSize: { // rarely used, but "text-base" (matching body's 1em=14px) is useful to reset font-size in a header container
|
||||
'xs': '11px',
|
||||
'sm': '12px',
|
||||
'base': '14px',
|
||||
'lg': '18px',
|
||||
'xl': '20px',
|
||||
'2xl': '24px',
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
{{ctx.Locale.Tr "packages.container.layers"}}
|
||||
{{/* only show the platform if the image metadata is not the package's, which means that it is a sub manifest */}}
|
||||
{{if ne .ContainerImageMetadata .PackageDescriptor.Metadata}}
|
||||
<span class="tw-text-sm flex-text-inline" title="{{ctx.Locale.Tr "packages.container.details.platform"}}">
|
||||
<span class="tw-text-base flex-text-inline" title="{{ctx.Locale.Tr "packages.container.details.platform"}}">
|
||||
({{svg "octicon-cpu" 12}} {{.ContainerImageMetadata.Platform}})
|
||||
</span>
|
||||
{{end}}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{range $i, $v := .TreeNames}}
|
||||
<div class="breadcrumb-divider">/</div>
|
||||
{{if eq $i $l}}
|
||||
<input id="file-name" maxlength="255" value="{{$v}}" placeholder="{{ctx.Locale.Tr (Iif $.PageIsUpload "repo.editor.add_subdir" "repo.editor.name_your_file")}}" data-editorconfig="{{$.EditorconfigJson}}" {{Iif $.PageIsUpload "" "required"}} autofocus>
|
||||
<input id="file-name" maxlength="255" value="{{$v}}" placeholder="{{ctx.Locale.Tr (Iif $.PageIsUpload "repo.editor.add_subdir" "repo.editor.name_your_file")}}" data-code-editor-config="{{JsonUtils.EncodeToString $.CodeEditorConfig}}" {{Iif $.PageIsUpload "" "required"}} autofocus>
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
|
||||
{{else}}
|
||||
<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
|
||||
|
||||
@@ -18,19 +18,22 @@
|
||||
{{if not .NotEditableReason}}
|
||||
<div class="field">
|
||||
<div class="ui top attached header">
|
||||
<div class="ui compact small menu small-menu-items repo-editor-menu">
|
||||
<a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a>
|
||||
<a class="item" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
|
||||
{{if not .IsNewFile}}
|
||||
<a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a>
|
||||
{{end}}
|
||||
<div class="flex-text-block tw-justify-between tw-flex-wrap">
|
||||
<div class="ui compact small menu small-menu-items repo-editor-menu tw-self-start">
|
||||
<a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a>
|
||||
<a class="item" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
|
||||
{{if not .IsNewFile}}
|
||||
<a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{template "repo/editor/options" dict "CodeEditorConfig" $.CodeEditorConfig}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui bottom attached segment tw-p-0">
|
||||
<div class="ui active tab tw-rounded-b" data-tab="write">
|
||||
<textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
|
||||
data-previewable-extensions="{{.PreviewableExtensions}}"
|
||||
data-line-wrap-extensions="{{.LineWrapExtensions}}">{{.FileContent}}</textarea>
|
||||
data-previewable-extensions="{{StringUtils.Join $.CodeEditorConfig.PreviewableExtensions ","}}"
|
||||
data-line-wrap-extensions="{{StringUtils.Join $.CodeEditorConfig.LineWrapExtensions ","}}">{{.FileContent}}</textarea>
|
||||
<div class="editor-loading is-loading"></div>
|
||||
</div>
|
||||
<div class="ui tab tw-px-4 tw-py-3" data-tab="preview">
|
||||
|
||||
30
templates/repo/editor/options.tmpl
Normal file
30
templates/repo/editor/options.tmpl
Normal file
@@ -0,0 +1,30 @@
|
||||
{{$indentStyle := $.CodeEditorConfig.IndentStyle}}
|
||||
{{$indentSize := or $.CodeEditorConfig.IndentSize 4}}
|
||||
{{$lineWrapOn := $.CodeEditorConfig.LineWrapOn}}
|
||||
<div class="flex-text-block code-editor-options">
|
||||
<div class="native-select">
|
||||
<select class="js-indent-style-select" aria-label="{{ctx.Locale.Tr "text_indent_style"}}">
|
||||
<optgroup label="{{ctx.Locale.Tr "text_indent_style"}}">
|
||||
<option{{if eq $indentStyle "space"}} selected{{end}} value="space">{{ctx.Locale.Tr "characters_spaces"}}</option>
|
||||
<option{{if eq $indentStyle "tab"}} selected{{end}} value="tab">{{ctx.Locale.Tr "characters_tabs"}}</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="native-select">
|
||||
<select class="js-indent-size-select" aria-label="{{ctx.Locale.Tr "text_indent_size"}}">
|
||||
<optgroup label="{{ctx.Locale.Tr "text_indent_size"}}">
|
||||
<option{{if eq $indentSize 2}} selected{{end}} value="2">2</option>
|
||||
<option{{if eq $indentSize 4}} selected{{end}} value="4">4</option>
|
||||
<option{{if eq $indentSize 8}} selected{{end}} value="8">8</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="native-select">
|
||||
<select class="js-line-wrap-select" aria-label="{{ctx.Locale.Tr "text_line_wrap_mode"}}">
|
||||
<optgroup label="{{ctx.Locale.Tr "text_line_wrap_mode"}}">
|
||||
<option{{if $lineWrapOn}} selected{{end}} value="on">{{ctx.Locale.Tr "text_line_wrap"}}</option>
|
||||
<option{{if not $lineWrapOn}} selected{{end}} value="off">{{ctx.Locale.Tr "text_line_nowrap"}}</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -20,15 +20,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<div class="ui compact small menu small-menu-items repo-editor-menu">
|
||||
<a class="active item" data-tab="write">{{svg "octicon-code" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.editor.new_patch"}}</a>
|
||||
<div class="ui top attached header">
|
||||
<div class="flex-text-block tw-justify-between tw-flex-wrap">
|
||||
<div class="ui compact small menu small-menu-items repo-editor-menu tw-self-start">
|
||||
<a class="active item" data-tab="write">{{svg "octicon-code" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.editor.new_patch"}}</a>
|
||||
</div>
|
||||
{{template "repo/editor/options" dict "CodeEditorConfig" $.CodeEditorConfig}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui active tab segment tw-rounded tw-p-0" data-tab="write">
|
||||
<textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-patch"
|
||||
data-context="{{.RepoLink}}"
|
||||
data-line-wrap-extensions="{{.LineWrapExtensions}}">
|
||||
{{.FileContent}}</textarea>
|
||||
<div class="editor-loading is-loading"></div>
|
||||
<div class="ui bottom attached segment tw-p-0">
|
||||
<div class="ui active tab tw-rounded-b" data-tab="write">
|
||||
<textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-patch"
|
||||
data-context="{{.RepoLink}}">{{.FileContent}}</textarea>
|
||||
<div class="editor-loading is-loading"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "repo/editor/commit_form" .}}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
{{template "repo/settings/layout_head" (dict "ctxData" . "pageClass" "repository settings edit githook")}}
|
||||
<div class="repo-setting-content">
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "repo.settings.githooks"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<p>{{ctx.Locale.Tr "repo.settings.githook_edit_desc"}}</p>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<h4 class="ui top attached header flex-text-block tw-justify-between tw-flex-wrap">
|
||||
{{ctx.Locale.Tr "repo.settings.githooks"}}
|
||||
<div class="tw-font-normal tw-font-sans tw-text-base">
|
||||
{{template "repo/editor/options" dict "CodeEditorConfig" $.CodeEditorConfig}}
|
||||
</div>
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<p>{{ctx.Locale.Tr "repo.settings.githook_edit_desc"}}</p>
|
||||
{{with .Hook}}
|
||||
<div class="inline field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.githook_name"}}</label>
|
||||
@@ -20,7 +23,7 @@
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "repo.settings.update_githook"}}</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{template "repo/settings/layout_footer" .}}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
--checkbox-mask-checked: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 18 18" width="16" height="16"><path fill-rule="evenodd" d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"></path></svg>');
|
||||
--checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" d="M2 7.75A.75.75 0 012.75 7h10a.75.75 0 010 1.5h-10A.75.75 0 012 7.75z"></path></svg>');
|
||||
--octicon-chevron-right: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z"></path></svg>');
|
||||
--select-arrows: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="m4.074 9.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.043 9H4.251a.25.25 0 0 0-.177.427m0-1.957L7.47 4.073a.25.25 0 0 1 .354 0L11.22 7.47a.25.25 0 0 1-.177.426H4.251a.25.25 0 0 1-.177-.426"/></svg>');
|
||||
/* other variables */
|
||||
--border-radius: 4px;
|
||||
--border-radius-medium: 6px;
|
||||
@@ -309,6 +310,34 @@ a.label,
|
||||
text-decoration-line: none !important;
|
||||
}
|
||||
|
||||
.native-select {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.native-select > select {
|
||||
appearance: none; /* hide default triangle */
|
||||
padding: 6px 26px 6px 10px;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
/* ::before and ::after pseudo elements don't work on select elements,
|
||||
so we need to put it on the parent wrapper element. */
|
||||
.native-select::after {
|
||||
position: absolute;
|
||||
pointer-events: none; /* make the click event can pass through the svg to the select element */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 5px;
|
||||
content: "";
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
mask-size: cover;
|
||||
-webkit-mask-size: cover;
|
||||
mask-image: var(--select-arrows);
|
||||
-webkit-mask-image: var(--select-arrows);
|
||||
background: currentcolor;
|
||||
}
|
||||
|
||||
.ui.search > .results {
|
||||
background: var(--color-body);
|
||||
border-color: var(--color-secondary);
|
||||
@@ -416,7 +445,9 @@ a.label,
|
||||
.ui.selection.dropdown .menu > .item {
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.ui.selection.dropdown .menu .item:first-of-type {
|
||||
border-radius: 0;
|
||||
}
|
||||
.ui.selection.visible.dropdown > .text:not(.default) {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
@@ -142,14 +142,6 @@ textarea:focus,
|
||||
margin-top: 0.7em;
|
||||
}
|
||||
|
||||
.ui.form select {
|
||||
display: block;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
border-radius: 0.28571429rem;
|
||||
padding: 0.62em 1em;
|
||||
}
|
||||
|
||||
.ui.form .field > .selection.dropdown {
|
||||
min-width: 14em; /* matches the default min width */
|
||||
width: 100%;
|
||||
|
||||
@@ -407,6 +407,9 @@ $.fn.dropdown = function(parameters) {
|
||||
.html( templates.dropdown(selectValues, fields, settings.preserveHTML, settings.className) )
|
||||
.insertBefore($input)
|
||||
;
|
||||
|
||||
$module.attr('data-tooltip-content', $input.attr('data-tooltip-content') ?? null); // GITEA-PATCH: convert "select" to "dropdown" with attrs
|
||||
|
||||
if($input.hasClass(className.multiple) && $input.prop('multiple') === false) {
|
||||
module.error(error.missingMultiple);
|
||||
$input.prop('multiple', true);
|
||||
|
||||
@@ -10,15 +10,11 @@ type IGlobalEditorOptions = MonacoNamespace.editor.IGlobalEditorOptions;
|
||||
type ITextModelUpdateOptions = MonacoNamespace.editor.ITextModelUpdateOptions;
|
||||
type MonacoOpts = IEditorOptions & IGlobalEditorOptions & ITextModelUpdateOptions;
|
||||
|
||||
type EditorConfig = {
|
||||
type CodeEditorConfig = {
|
||||
indent_style?: 'tab' | 'space',
|
||||
indent_size?: string | number, // backend emits this as string
|
||||
indent_size?: number,
|
||||
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<string, string> = {};
|
||||
@@ -38,14 +34,15 @@ const baseOptions: MonacoOpts = {
|
||||
scrollbar: {horizontalScrollbarSize: 6, verticalScrollbarSize: 6, alwaysConsumeMouseWheel: false},
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
indentSize: 'tabSize',
|
||||
wrappingIndent: 'none',
|
||||
wordWrapBreakAfterCharacters: '',
|
||||
wordWrapBreakBeforeCharacters: '',
|
||||
matchBrackets: 'never',
|
||||
};
|
||||
|
||||
function getEditorconfig(input: HTMLInputElement): EditorConfig | null {
|
||||
const json = input.getAttribute('data-editorconfig');
|
||||
function getCodeEditorConfig(input: HTMLInputElement): CodeEditorConfig | null {
|
||||
const json = input.getAttribute('data-code-editor-config');
|
||||
if (!json) return null;
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
@@ -152,6 +149,7 @@ export async function createMonaco(textarea: HTMLTextAreaElement, filename: stri
|
||||
const editor = monaco.editor.create(container, {
|
||||
model,
|
||||
theme: 'gitea',
|
||||
...baseOptions,
|
||||
...other,
|
||||
});
|
||||
|
||||
@@ -167,6 +165,22 @@ export async function createMonaco(textarea: HTMLTextAreaElement, filename: stri
|
||||
textarea.dispatchEvent(new Event('change')); // seems to be needed for jquery-are-you-sure
|
||||
});
|
||||
|
||||
const elEditorOptions = textarea.closest('form')?.querySelector('.code-editor-options');
|
||||
if (elEditorOptions) {
|
||||
elEditorOptions.querySelector<HTMLSelectElement>('.js-indent-style-select')!.addEventListener('change', (e) => {
|
||||
const insertSpaces = (e.target as HTMLSelectElement).value === 'space';
|
||||
editor.updateOptions({insertSpaces, useTabStops: !insertSpaces});
|
||||
});
|
||||
elEditorOptions.querySelector<HTMLSelectElement>('.js-indent-size-select')!.addEventListener('change', (e) => {
|
||||
const tabSize = Number((e.target as HTMLSelectElement).value);
|
||||
editor.updateOptions({tabSize});
|
||||
});
|
||||
elEditorOptions.querySelector<HTMLSelectElement>('.js-line-wrap-select')!.addEventListener('change', (e) => {
|
||||
const wordWrap = (e.target as HTMLSelectElement).value as IEditorOptions['wordWrap'];
|
||||
editor.updateOptions({wordWrap});
|
||||
});
|
||||
}
|
||||
|
||||
exportEditor(editor);
|
||||
|
||||
const loading = document.querySelector('.editor-loading');
|
||||
@@ -203,14 +217,13 @@ export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameIn
|
||||
const previewableExts = new Set((textarea.getAttribute('data-previewable-extensions') || '').split(','));
|
||||
const lineWrapExts = (textarea.getAttribute('data-line-wrap-extensions') || '').split(',');
|
||||
const isPreviewable = previewableExts.has(extname(filename));
|
||||
const editorConfig = getEditorconfig(filenameInput);
|
||||
const editorConfig = getCodeEditorConfig(filenameInput);
|
||||
|
||||
togglePreviewDisplay(isPreviewable);
|
||||
|
||||
const {monaco, editor} = await createMonaco(textarea, filename, {
|
||||
...baseOptions,
|
||||
...getFileBasedOptions(filenameInput.value, lineWrapExts),
|
||||
...getEditorConfigOptions(editorConfig),
|
||||
...getMonacoOptsByCodeEditorConfig(editorConfig),
|
||||
});
|
||||
|
||||
filenameInput.addEventListener('input', onInputDebounce(() => {
|
||||
@@ -223,20 +236,15 @@ export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameIn
|
||||
return editor;
|
||||
}
|
||||
|
||||
function getEditorConfigOptions(ec: EditorConfig | null): MonacoOpts {
|
||||
function getMonacoOptsByCodeEditorConfig(ec: CodeEditorConfig | null): MonacoOpts {
|
||||
if (!ec || !isObject(ec)) return {};
|
||||
|
||||
const opts: MonacoOpts = {};
|
||||
opts.detectIndentation = !('indent_style' in ec) || !('indent_size' in ec);
|
||||
opts.detectIndentation = !ec.indent_style || !ec.indent_size;
|
||||
|
||||
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)];
|
||||
// with indentSize='tabSize', this also controls the `indentSize` option
|
||||
if (!opts.detectIndentation) {
|
||||
opts.tabSize = Number(ec.tab_width) || Number(ec.indent_size) || 4;
|
||||
}
|
||||
|
||||
opts.trimAutoWhitespace = ec.trim_trailing_whitespace === true;
|
||||
|
||||
Reference in New Issue
Block a user