diff --git a/docs/de/user/shortcuts/index.md b/docs/de/user/shortcuts/index.md
index 5e3a8eef3f..c377e7a39e 100644
--- a/docs/de/user/shortcuts/index.md
+++ b/docs/de/user/shortcuts/index.md
@@ -10,14 +10,14 @@ verfügbaren Tastenkürzel mittels der `?`-Taste aufrufen.
### Globale Tastenkürzel
-| Key Combination | Description |
-|-----------------|-------------------------------------|
-| ? | Öffne die Tastaturkürzelübersicht |
-| / | Fokussiere die globale Schnellsuche |
-| alt r | Navigiere zur Repositoryübersicht |
-| alt u | Navigiere zur Benutzerübersicht |
-| alt g | Navigiere zur Gruppenübersicht |
-| alt a | Navigiere zur Administration |
+| Tastenkürzel | Beschreibung |
+|--------------|-------------------------------------|
+| ? | Öffne die Tastaturkürzelübersicht |
+| / | Fokussiere die globale Schnellsuche |
+| alt r | Navigiere zur Repositoryübersicht |
+| alt u | Navigiere zur Benutzerübersicht |
+| alt g | Navigiere zur Gruppenübersicht |
+| alt a | Navigiere zur Administration |
### Navigation von Listen
@@ -25,20 +25,25 @@ Einige Seiten mit Listen erlauben die Navigation per Tastatur.
Wenn die Seite dieses unterstützt, tauchen die Tastaturkürzel in der Übersicht im SCM-Manager
auf (`?`).
-| Key Combination | Description |
-|-----------------|---------------------------------------------------|
-| j | Bewege den Fokus auf den nächsten Listeneintrag |
-| k | Bewege den Fokus auf den vorherigen Listeneintrag |
+| Tastenkürzel | Beschreibung |
+|--------------|---------------------------------------------------|
+| j | Bewege den Fokus auf den nächsten Listeneintrag |
+| k | Bewege den Fokus auf den vorherigen Listeneintrag |
### Repositoryspezifische Tastenkürzel
-| Key Combination | Description |
-|-----------------|------------------------------|
-| g i | Wechsel zur Repository-Info |
-| g b | Wechsel zu den Branches |
-| g t | Wechsel zu den Tags |
-| g c | Wechsel zum Code |
-| g s | Wechsel zu den Einstellungen |
+| Tastenkürzel | Beschreibung |
+|--------------|------------------------------|
+| g i | Wechsel zur Repository-Info |
+| g b | Wechsel zu den Branches |
+| g t | Wechsel zu den Tags |
+| g c | Wechsel zum Code |
+| g s | Wechsel zu den Einstellungen |
+
+### Codespezifische Tastenkürzel
+| Tastenkürzel | Beschreibung |
+|--------------|------------------------|
+| g f | Wechsel zur Dateisuche |
### Tastenkürzel aus Plugin
diff --git a/docs/en/user/shortcuts/index.md b/docs/en/user/shortcuts/index.md
index 4e092827b6..aa691b9f29 100644
--- a/docs/en/user/shortcuts/index.md
+++ b/docs/en/user/shortcuts/index.md
@@ -39,6 +39,12 @@ If the page supports this feature, the shortcuts show up in the shortcut overvie
| g c | Switch to code |
| g s | Switch to settings |
+### Code-specific Shortcuts
+
+| Key Combination | Description |
+|-----------------|---------------------------|
+| g f | Switch to file search |
+
### Plugin Shortcuts
Plugins can introduce new shortcuts.
diff --git a/gradle/changelog/file_search.yaml b/gradle/changelog/file_search.yaml
new file mode 100644
index 0000000000..8c9c0255b3
--- /dev/null
+++ b/gradle/changelog/file_search.yaml
@@ -0,0 +1,4 @@
+- type: added
+ description: Keyboard shortcut (g+f) within code view for file search
+- type: changed
+ description: File search page design; in particular with regard to accessibility
diff --git a/scm-ui/e2e-tests/cypress/integration/repository_code_filesearch_spec.ts b/scm-ui/e2e-tests/cypress/integration/repository_code_filesearch_spec.ts
index b4d574b44f..ee33b9d97e 100644
--- a/scm-ui/e2e-tests/cypress/integration/repository_code_filesearch_spec.ts
+++ b/scm-ui/e2e-tests/cypress/integration/repository_code_filesearch_spec.ts
@@ -43,7 +43,7 @@ describe("Repository File Search", () => {
// Act
cy.visit(`/repo/${namespace}/${name}/code/sources`);
cy.byTestId("file_search_button").click();
- cy.url().should("include", `/repo/${namespace}/${name}/code/search/main?q=`);
+ cy.url().should("include", `/repo/${namespace}/${name}/code/search/main`);
cy.byTestId("file_search_filter_input").type("README");
// Assert
diff --git a/scm-ui/ui-core/src/base/forms/index.ts b/scm-ui/ui-core/src/base/forms/index.ts
index 0ee36d79a2..7c5ae5a252 100644
--- a/scm-ui/ui-core/src/base/forms/index.ts
+++ b/scm-ui/ui-core/src/base/forms/index.ts
@@ -43,6 +43,7 @@ export { default as ConfigurationForm } from "./ConfigurationForm";
export { default as SelectField } from "./select/SelectField";
export { default as ComboboxField } from "./combobox/ComboboxField";
export { default as Input } from "./input/Input";
+export { default as InputField } from "./input/InputField";
export { default as Textarea } from "./input/Textarea";
export { default as Select } from "./select/Select";
export * from "./resourceHooks";
diff --git a/scm-ui/ui-core/src/base/forms/input/ControlledInputField.tsx b/scm-ui/ui-core/src/base/forms/input/ControlledInputField.tsx
index f2fe5df2a2..6c7157a3e4 100644
--- a/scm-ui/ui-core/src/base/forms/input/ControlledInputField.tsx
+++ b/scm-ui/ui-core/src/base/forms/input/ControlledInputField.tsx
@@ -29,6 +29,7 @@ type Props> = Omit<
rules?: ComponentProps["rules"];
name: Path;
label?: string;
+ icon?: string;
};
function ControlledInputField>({
@@ -41,6 +42,7 @@ function ControlledInputField>({
defaultValue,
readOnly,
className,
+ icon,
...props
}: Props) {
const { control, t, readOnly: formReadonly, formId } = useScmFormContext();
@@ -66,6 +68,7 @@ function ControlledInputField>({
{...field}
form={formId}
label={labelTranslation}
+ icon={icon}
helpText={helpTextTranslation}
descriptionText={descriptionTextTranslation}
error={
diff --git a/scm-ui/ui-core/src/base/forms/input/InputField.stories.mdx b/scm-ui/ui-core/src/base/forms/input/InputField.stories.mdx
index 761470af7e..16c8cad121 100644
--- a/scm-ui/ui-core/src/base/forms/input/InputField.stories.mdx
+++ b/scm-ui/ui-core/src/base/forms/input/InputField.stories.mdx
@@ -20,3 +20,7 @@ This will be our first form field molecule
+
+
+
+
diff --git a/scm-ui/ui-core/src/base/forms/input/InputField.tsx b/scm-ui/ui-core/src/base/forms/input/InputField.tsx
index 1bfbfa1a9f..df7c708f5a 100644
--- a/scm-ui/ui-core/src/base/forms/input/InputField.tsx
+++ b/scm-ui/ui-core/src/base/forms/input/InputField.tsx
@@ -23,24 +23,26 @@ import Input from "./Input";
import Help from "../base/help/Help";
import { useAriaId } from "../../helpers";
-type InputFieldProps = {
+export type InputFieldProps = {
label: string;
+ labelClassName?: string;
helpText?: string;
descriptionText?: string;
error?: string;
+ icon?: string;
} & React.ComponentProps;
/**
* @see https://bulma.io/documentation/form/input/
*/
const InputField = React.forwardRef(
- ({ name, label, helpText, descriptionText, error, className, id, ...props }, ref) => {
+ ({ name, label, helpText, descriptionText, error, icon, className, labelClassName, id, ...props }, ref) => {
const inputId = useAriaId(id ?? props.testId);
const descriptionId = descriptionText ? `input-description-${name}` : undefined;
const variant = error ? "danger" : undefined;
return (
-
diff --git a/scm-ui/ui-webapp/public/locales/de/repos.json b/scm-ui/ui-webapp/public/locales/de/repos.json
index e5a28265ee..be2125461a 100644
--- a/scm-ui/ui-webapp/public/locales/de/repos.json
+++ b/scm-ui/ui-webapp/public/locales/de/repos.json
@@ -24,7 +24,7 @@
"help": {
"namespaceHelpText": "Der Namespace des Repository. Dieser wird Teil der URL des Repository sein.",
"nameHelpText": "Der Name des Repository. Dieser wird Teil der URL des Repository sein.",
- "contactHelpText": "E-Mail Adresse der Person, die für das Repository verantwortlich ist.",
+ "contactHelpText": "E-Mail-Adresse der Person, die für das Repository verantwortlich ist.",
"descriptionHelpText": "Eine kurze Beschreibung des Repository.",
"initializeRepository": "Erstellt einen ersten Branch und committet eine README.md.",
"importUrlHelpText": "Importiert das gesamte Repository inkl. aller Branches und Tags über die Remote URL.",
@@ -311,7 +311,7 @@
"shortSummary": "Committet <0/> <1/>",
"tags": "Tags",
"diffNotSupported": "Diff des Changesets wird von diesem Repositorytyp nicht unterstützt",
- "keyOwner": "Schlüssel Besitzer",
+ "keyOwner": "Schlüsselbesitzer",
"signatureStatus": "Status",
"keyId": "Schlüssel-ID",
"keyContacts": "Kontakte",
@@ -527,8 +527,8 @@
"modal": {
"title": "Repository umbenennen",
"label": {
- "repoName": "Repository Name",
- "repoNamespace": "Repository Namespace"
+ "repoName": "Repository-Name",
+ "repoNamespace": "Repository-Namespace"
},
"button": {
"rename": "Umbenennen",
@@ -618,23 +618,25 @@
},
"fileSearch": {
"button": {
- "title": "Dateipfad Suche"
+ "title": "Dateipfadsuche"
},
"file": "Datei",
"home": "Zurück zu Sources",
"input": {
- "placeholder": "Dateipfad Suche",
- "help": "Tippe 2 oder mehr Zeichen ein, um die Suche zu starten"
+ "placeholder": "Dateipfadsuche",
+ "help": "Tippen Sie mindestens zwei Zeichen ein, um die Suche zu starten."
},
+ "results_one": "{{count}} Ergebnis gefunden:",
+ "results_other": "{{count}} Ergebnisse gefunden:",
"notifications": {
- "queryToShort": "Tippe mindestens 2 Zeichen ein, um die Suche zu starten",
- "emptyResult": "Es wurden keine Ergebnisse für <0>{{query}}0> gefunden"
+ "emptyResult": "Keine Ergebnisse gefunden für: <0>{{query}}0>."
},
"searchWithRevisionAndNamespaceName": "Suche auf {{revision}} in {{namespace}}/{{name}}"
},
"shortcuts": {
"info": "Wechsel zur Repository-Info",
"branches": "Wechsel zu den Branches",
+ "fileSearch": "Wechsel zur Dateisuche",
"tags": "Wechsel zu den Tags",
"code": "Wechsel zum Code",
"settings": "Wechsel zu den Einstellungen"
diff --git a/scm-ui/ui-webapp/public/locales/en/repos.json b/scm-ui/ui-webapp/public/locales/en/repos.json
index e6b7727b47..13e7983fd6 100644
--- a/scm-ui/ui-webapp/public/locales/en/repos.json
+++ b/scm-ui/ui-webapp/public/locales/en/repos.json
@@ -624,17 +624,19 @@
"home": "Go back to source root",
"input": {
"placeholder": "Search filepath",
- "help": "Type 2 or more letters to search for a filepath in the repository"
+ "help": "Type at least two letters to start the search."
},
+ "results_one": "{{count}} result found:",
+ "results_other": "{{count}} results found:",
"notifications": {
- "queryToShort": "Type at least two characters to start the search",
- "emptyResult": "Nothing found for query <0>{{query}}0>"
+ "emptyResult": "Nothing found for: <0>{{query}}0>."
},
"searchWithRevisionAndNamespaceName": "Search on {{revision}} in {{namespace}}/{{name}}"
},
"shortcuts": {
"info": "Switch to repository info",
"branches": "Switch to branches",
+ "fileSearch": "Switch to file search",
"tags": "Switch to tags",
"code": "Switch to code",
"settings": "Switch to settings"
diff --git a/scm-ui/ui-webapp/src/repos/codeSection/components/FileSearchButton.tsx b/scm-ui/ui-webapp/src/repos/codeSection/components/FileSearchButton.tsx
index b2e2fc7ae2..4a5d1e6335 100644
--- a/scm-ui/ui-webapp/src/repos/codeSection/components/FileSearchButton.tsx
+++ b/scm-ui/ui-webapp/src/repos/codeSection/components/FileSearchButton.tsx
@@ -18,9 +18,9 @@ import React, { FC } from "react";
import { useTranslation } from "react-i18next";
import { File, Repository } from "@scm-manager/ui-types";
import { Link } from "react-router-dom";
-import { Icon } from "@scm-manager/ui-components";
import styled from "styled-components";
-import { urls } from "@scm-manager/ui-api";
+import { getFileSearchLink } from "../utils/fileSearchLink";
+import { Icon } from "@scm-manager/ui-core";
type Props = {
repository: Repository;
@@ -35,18 +35,13 @@ const SearchIcon = styled(Icon)`
const FileSearchButton: FC = ({ baseUrl, revision, currentSource, repository }) => {
const [t] = useTranslation("repos");
- const currentSourcePath =
- repository.type === "svn"
- ? urls.createPrevSourcePathQuery(`${revision}/${currentSource.path}`)
- : urls.createPrevSourcePathQuery(currentSource.path);
-
return (
-
+ search
);
};
diff --git a/scm-ui/ui-webapp/src/repos/codeSection/components/FileSearchHit.tsx b/scm-ui/ui-webapp/src/repos/codeSection/components/FileSearchHit.tsx
new file mode 100644
index 0000000000..8fbb3c127c
--- /dev/null
+++ b/scm-ui/ui-webapp/src/repos/codeSection/components/FileSearchHit.tsx
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020 - present Cloudogu GmbH
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU Affero General Public License as published by the Free
+ * Software Foundation, version 3.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see https://www.gnu.org/licenses/.
+ */
+
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { urls } from "@scm-manager/ui-api";
+import { Icon, useKeyboardIteratorTarget } from "@scm-manager/ui-core";
+import { Link } from "react-router-dom";
+import styled from "styled-components";
+
+type FileSearchHitProps = {
+ contentBaseUrl: string;
+ path: string;
+};
+
+const IconColumn = styled.td`
+ width: 16px;
+`;
+
+const LeftOverflowTd = styled.td`
+ overflow: hidden;
+ max-width: 1px;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ direction: rtl;
+ text-align: left !important;
+`;
+
+export function FileSearchHit({ contentBaseUrl, path }: FileSearchHitProps) {
+ const [t] = useTranslation("repos");
+ const link = urls.concat(contentBaseUrl, path);
+ const ref = useKeyboardIteratorTarget();
+ return (
+