From beb5e2c3634e147a671ba797179f61119d9c1c28 Mon Sep 17 00:00:00 2001 From: Nextcloud bot Date: Sun, 5 Jan 2025 00:58:44 +0000 Subject: [PATCH 1/4] Fix(l10n): Update translations from Transifex Signed-off-by: Nextcloud bot --- l10n/sv.js | 181 +++++++++++++++++++++++++++++++++++++++++---------- l10n/sv.json | 181 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 290 insertions(+), 72 deletions(-) diff --git a/l10n/sv.js b/l10n/sv.js index 7245fe36e..077c7079c 100644 --- a/l10n/sv.js +++ b/l10n/sv.js @@ -4,103 +4,167 @@ OC.L10N.register( "Forms" : "Formulär", "{user} has shared the form {formTitle} with you" : "{user} har delat formuläret {formTitle} med dig", "{user} has shared the form {formTitle} with group {group}" : "{user} har delat formuläret {formTitle} med gruppen {group}", + "{user} has shared the form {formTitle} with team {circle}" : "{user} har delat formuläret {formTitle} med teamet {circle}", + "Your form {formTitle} was answered by {user}" : "Ditt formulär {formTitle} besvarades av {user}", "Anonymous user" : "Anonym användare", "A form has been shared with you" : "Ett formulär har delats med dig", + "Someone answered a shared form" : "Någon besvarade ett delat formulär", "Someone answered a form" : "Någon besvarade ett formulär", + "Nextcloud Forms" : "Nextcloud formulär", + "Select form" : "Välj formulär", "Timestamp of data load" : "Tidstämpel för datainläsning", "No" : "Nej", "Yes" : "Ja", "Question" : "Fråga", + "Answer" : "Svar", + "Count" : "Antal", "Copy" : "Kopiera", - "Anonymous response" : "Anonymt svar", + "Anonymous response" : "Anonymt svar", "Shared by %s" : "Delad av %s", - "Forms including questions and submissions" : "Formulär inklusive frågor och inlämningar", + "Forms including questions and submissions" : "Formulär inklusive frågor och svar", "responses" : "svar", "User ID" : "Användar-ID", "User display name" : "Användarens visningsnamn", "Timestamp" : "Tidsstämpel", "📝 Simple surveys and questionnaires, self-hosted" : "📝 enkla undersökningar och formulär, egenhostade", + "**Simple surveys and questionnaires, self-hosted!**\n\n- **📝 Simple design:** No mass of options, only the essentials. Works well on mobile of course.\n- **📊 View & export results:** Results are visualized and can also be exported as CSV in the same format used by Google Forms.\n- **🔒 Data under your control!** Unlike in Google Forms, Typeform, Doodle and others, the survey info and responses are kept private on your instance.\n- **🧑‍💻 Connect to your software:** Easily integrate Forms into your service with our full-fledged [REST-API](https://github.com/nextcloud/forms/blob/main/docs/API.md).\n- **🙋 Get involved!** We have lots of stuff planned like more question types, collaboration on forms, [and much more](https://github.com/nextcloud/forms/milestones)!" : "**Enkla undersökningar och frågeformulär, självhostade!**\n\n- **📝 Enkel design:** Inte en massa alternativ, bara det väsentliga. Fungerar naturligtvis bra på mobilen.\n- **📊 Visa & exportera resultat:** Resultat visualiseras och kan även exporteras som CSV i samma format som används av Google Forms.\n- **🔒 Data under din kontroll:** Till skillnad från Google Forms, Typeform, Doodle och andra, hålls formulärdata och svar privata på din instans.\n- **🧑‍💻 Anslut till din mjukvara:** Integrera enkelt Formulär i din egna tjänst med vårt heltäckande [REST-API](https://github.com/nextcloud/forms/blob/main/docs/API.md)\n- **🙋 Engagera dig!** Vi planerar massor av saker, t.ex. fler frågetyper, samarbete på formulär, [och mycket mer](https://github.com/nextcloud/forms/milestones)!", + "Forms navigation" : "Formulärnavigation", "New form" : "Nytt formulär", - "Shared with you" : "Delad med dig", - "Loading forms …" : "Laddar formulär ...", + "Your forms" : "Dina formulär", + "Shared with you" : "Delas med dig", + "Archived forms" : "Arkiverade formulär", + "Loading forms …" : "Laddar formulär …", "No forms created yet" : "Inga formulär ännu", "Create a form" : "Skapa ett formulär", "Select a form or create a new one" : "Välj ett formulär eller skapa ett nytt", "Please select a form" : "Välj ett formulär", "Create new form" : "Skapa nytt formulär", "An error occurred while loading the forms list" : "Kunde inte ladda listan med formulär", - "Form not found" : "Hittade inte formuläret", + "Form not found" : "Formuläret hittades inte", "Unable to create a new form" : "Kunde inte skapa ett nytt formulär", - "Unable to copy form" : "Går ej att kopiera formulär", + "Unable to copy form" : "Kunde inte kopiera formulär", "This form does not exist" : "Detta formulär finns inte", "Form expired" : "Formuläret har upphört att gälla", "This form has expired and is no longer taking answers" : "Detta formulär har upphört att gälla och tar inte längre emot svar", - "Form creation" : "Skapa formulär", + "Form creation" : "Formulärskapande", "Restrict form creation to selected groups" : "Begränsa skapande av formulär till valda grupper", "Select groups" : "Välj grupper", - "Form sharing" : "Dela formulär", + "Form sharing" : "Formulärdelning", "Allow sharing by link" : "Tillåt delning via länk", "Allow sharing to all logged in accounts" : "Tillåt delning till alla inloggade konton", - "Error while saving configuration" : "Fel när konfigurationen skulle sparas", + "Allow showing form to all logged in accounts on sidebar" : "Tillåt visning i sidofältet för alla inloggade konton", + "Error while saving configuration" : "Fel vid sparande av konfiguration", "Error while reloading config" : "Fel vid omladdning av konfiguration", "Form actions" : "Formuläråtgärder", "Edit form" : "Redigera formulär", "Share form" : "Dela formuläret", "Results" : "Resultat", "Copy form" : "Kopiera formulär", + "Unarchive form" : "Avarkivera formulär", + "Archive form" : "Arkivera formulär", "Delete form" : "Ta bort formuläret", "Are you sure you want to delete {title}?" : "Vill du ta bort {title}?", + "Form closed" : "Formulär stängt", "Expired {relativeDate}" : "Upphörde {relativeDate}", "Expires {relativeDate}" : "Upphör {relativeDate}", + "Error changing archived state of form" : "Fel vid ändring av arkivstatus för formulär", "Error while deleting {title}" : "Kunde inte ta bort {title}", + "Add multiple options" : "Lägg till flera alternativ", + "Add multiple options (one per line)" : "Lägg till flera alternativ (ett per rad)", "Options" : "Alternativ", "Cancel" : "Avbryt", + "Add options" : "Lägg till alternativ", + "Options should be separated by new line!" : "Alternativ måste separeras med nya rader!", + "QR code representation of {text}" : "QR-kod för {text}", "Answer number {index}" : "Svar {index}", "Delete answer" : "Ta bort svaret", "Error while saving the answer" : "Kunde inte spara svaret", "Question number {index}" : "Fråga {index}", + "Move question up" : "Flytta upp fråga", + "Move question down" : "Flytta ned fråga", "Title of question number {index}" : "Titel för fråga {index}", "Required" : "Krävs", - "Delete question" : "Ta bort frågan", + "Technical name of the question" : "Tekniskt namn för frågan", + "Technical name" : "Tekniskt namn", + "Copy question" : "Kopiera fråga", + "Delete question" : "Ta bort fråga", "Description (formatting using Markdown is supported)" : "Beskrivning (formatering med Markdown stöds)", "This question needs a title!" : "Frågan behöver en rubrik!", "Shuffle options" : "Blanda alternativ", "Add a new answer" : "Lägg till ett nytt svar", - "There was an issue deleting this option" : "Problem uppstod vid borttagande av detta alternativ", + "There was an issue deleting this option" : "Fel vid borttagande av detta alternativ", + "Allow only specific file types" : "Tillåt endast specifika filtyper", + "Custom file extensions" : "Anpassade filändelser", + "Maximum number of files" : "Maximalt antal filer", "Maximum file size" : "Maximal filstorlek", "Delete" : "Radera", - "Uploading …" : "Laddar upp ...", + "Uploading …" : "Laddar upp …", + "Add new file as answer" : "Ladda upp ny fil som svar", + "Allowed file types: {fileTypes}." : "Tillåtna filtyper: {fileTypes}.", + "All file types are allowed." : "Alla filtyper är tillåtna.", + "The file {fileName} is too large. The maximum file size is {maxFileSize}." : "Filen {fileName} är för stor. Maximala filstorleken är {maxFileSize}.", + "There was an error during submitting the file: {message}." : "Ett fel uppstod vid uppladdning av filen: {message}.", "A long answer for the question “{text}”" : "Ett långt svar till frågan \"{text}\"", + "Add \"other\"" : "Lägg till \"annat\"", + "Require a minimum of options to be checked" : "Kräv markering av ett minsta antal alternativ ", + "Minimum options to be checked" : "Minsta antalet alternativ som behöver väljas", + "Require a maximum of options to be checked" : "Kräv markering av ett maximalt antal alternativ", + "Maximum options to be checked" : "Maximala antalet alternativ som får väljas", + "Other:" : "Annat:", "Other" : "Annat", + "Upper options limit must be greater than the lower limit" : "Max antal måste vara större än minsta antalet", + "Lower options limit must be smaller than the upper limit" : "Minsta antalet måste vara färre än max antalet", "A short answer for the question “{text}”" : "Ett kort svar till frågan \"{text}\"", + "Input types (currently: {type})" : "Svarstyper (just nu: {type})", + "Regular expression for input validation" : "Regular expression för svarsvalidering", + "Invalid regular expression" : "Ogiltigt regular expression", "No response" : "Inget svar", - "Delete this response" : "Ta bort svaret", + "Delete this response" : "Ta bort detta svar", "Store responses anonymously" : "Lagra svar anonymt", "Allow multiple responses per person" : "Tillåt flera svar per person", "Set expiration date" : "Välj utgångsdatum", "Show expiration date on form" : "Visa utgångsdatum på formuläret", - "This can not be controlled, if the form has a public link or stores responses anonymously." : "Detta kan inte kontrolleras om formuläret har en offentlig länk eller lagrar svar anonymt.", + "Close form" : "Stäng formulär", + "Closed forms do not accept new submissions." : "Stängda formulär tar inte emot nya svar.", + "Archived forms do not accept new submissions and can not be modified." : "Arkiverade formulär tar inte emot nya svar och kan inte redigeras.", + "Custom submission message" : "Anpassat meddelande vid inskickning", + "Message to show after a user submitted the form (formatting using Markdown is supported)" : "Meddelande som visas när användare har skickat in sina svar (formatering med Markdown stöds)", + "Message to show after a user submitted the form. Please note that the message will not be translated!" : "Meddelande som visas när användare har skickat in sina svar. Obs: meddelandet kommer inte att översättas!", + "This can not be controlled, if the form has a public link or stores responses anonymously." : "Detta kan inte ställas in om formuläret har en offentlig länk eller lagrar svar anonymt.", "Expired on {date}" : "Slutade gälla {date}", "Expires on {date}" : "Slutar gälla {date}", + "Search for user, group or team …" : "Sök efter användare, grupper eller team …", "Permissions" : "Behörigheter", "View responses" : "Visa svar", + "Delete responses" : "Ta bort svar", "Group" : "Grupp", "Team" : "Team", "Share link" : "Delningslänk", "Add link" : "Lägg till länk", + "Embeddable link" : "Inbäddningsbar länk", "Copy to clipboard" : "Kopiera till urklipp", "Show QR code" : "Visa QR-kod", "Copy embedding code" : "Kopiera inbäddningskod", + "Convert to embeddable link" : "Konvertera till inbäddningsbar länk", "Remove link" : "Ta bort länk", + "Share {formTitle}" : "Dela {formTitle}", "Internal link" : "Intern länk", "Only works for logged in accounts with access rights" : "Fungerar endast för inloggade konton med åtkomsträttigheter", - "Permit access to all logged in accounts" : "Tillåt åtkomst till alla inloggade konton", + "Permit access to all logged in accounts" : "Tillåt åtkomst för alla inloggade konton", "Show to all accounts on sidebar" : "Visa för alla konton på sidofältet", - "There was an error while adding the share" : "Det uppstod ett fel när delningen lades till", - "There was an error while adding the link" : "Det uppstod ett fel när länken lades till", - "There was an error while updating the share" : "Det uppstod ett fel när delingen skulle uppdateras", - "There was an error while removing the share" : "Det uppstod ett fel när delningen skulle tas bort", + "There was an error while adding the share" : "Ett fel uppstod vid tilläggning av delningen", + "There was an error while adding the link" : "Ett fel uppstod vid tilläggning av länken", + "There was an error while updating the share" : "Ett fel uppstod vid uppdatering av delningen", + "There was an error while removing the share" : "Ett fel uppstod vid borttagning av delningen", "Transfer ownership" : "Överför ägarskap", + "You're going to transfer the ownership of {name} to another account. Please select the account to which you want to transfer ownership." : "Du kommer att överföra ägarskap av {name} till ett annat konto. Välj vilket konto ägarskapet skall överföras till.", + "Search for a user" : "Sök efter användare", + "Type {text} to confirm." : "Fyll i {text} för att bekräfta.", + "Confirmation text" : "Bekräftelsetext", + "This can not be undone." : "Detta kan inte ångras", + "I understand, transfer this form" : "Jag förstår, överför formuläret", + "This form is now owned by" : "Detta formulär ägs nu av", + "An error occurred while transfering ownership" : "Ett fel uppstod vid överföring av ägarskap", "View mode" : "Visningsläge", "Share" : "Dela", "Toggle settings" : "Växla inställningar", @@ -108,7 +172,10 @@ OC.L10N.register( "View" : "Visa", "Edit" : "Ändra", "Show results" : "Visa resultat", + "Create form" : "Skapa formulär", "Loading {title} …" : "Läser in {title} …", + "Form is archived" : "Formulär arkiverat", + "Form '{title}' is archived and cannot be modified." : "Formulär '{title}' är arkiverat och kan inte redigeras", "Form title" : "Rubrik", "Description" : "Beskrivning", "Add a question" : "Lägg till en fråga", @@ -118,65 +185,96 @@ OC.L10N.register( "Expired {relativeDate}." : "Upphörde {relativeDate}.", "Expires {relativeDate}." : "Upphör {relativeDate}.", "There was an error while adding the new question" : "Ett fel uppstod vid sparande av den nya frågan", - "There was an error while removing the question" : "Ett fel uppstod vid borttagande av frågan", + "There was an error while removing the question" : "Ett fel uppstod vid borttagning av frågan", "Error while saving form" : "Kunde inte spara formuläret", - "Loading responses …" : "Hämtar svar ...", + "Linked file not available" : "Kopplad fil inte tillgänglig", + "Linked file is not available, would you like to link a new file?" : "Den kopplade filen är inte tillgänglig, vill du koppla en ny fil?", + "Loading responses …" : "Hämtar svar …", "No responses yet" : "Inga svar ännu", "Results of submitted forms will show up here" : "Resultat från inskickade formulär visas här", + "Create spreadsheet" : "Skapa kalkylblad", + "Open spreadsheet" : "Öppna kalkylblad", "{amount} responses" : "{amount} svar", + "Re-export spreadsheet" : "Exportera kalkylblad igen", + "Unlink spreadsheet" : "Bryt koppling med kalkylblad", + "Save copy to Files" : "Spara kopia till Filer", "Download" : "Ladda ner", "Delete all responses" : "Ta bort alla svar", + "Delete submissions" : "Ta bort svar", "Are you sure you want to delete all responses of {title}?" : "Är du säker på att du vill ta bort alla svar till {title}?", "Summary" : "Sammanfattning", "Responses" : "Svar", "There was an error while loading the results" : "Ett fel uppstod vid hämtning av resultaten", + "File {file} successfully linked" : "Fil {file} har kopplats", + "There was an error while linking the file" : "Ett fel uppstod vid koppling av filen", "Export successful to {file}" : "Export till {file} lyckades", + "There was an error while exporting to Files" : "Ett fel uppstod vid export till Filer", + "File is not linked" : "Ingen fil är kopplad", "There was an error, while exporting to Files" : "Det inträffade ett fel, vid export till Filer", - "There was an error while removing this response" : "Ett fel uppstod vid borttagande av detta svar", - "There was an error while removing responses" : "Ett fel uppstod vid borttagande av svar", + "Submission deleted" : "Svar borttaget", + "There was an error while removing this response" : "Ett fel uppstod vid borttagning av detta svar", + "There was an error while removing responses" : "Ett fel uppstod vid borttagning av svar", + "Choose spreadsheet location" : "Välj plats för kalkylblad", + "Create XLSX" : "Skapa XLSX", + "Create CSV" : "Skapa CSV", + "Create ODS" : "Skapa ODS", + "Select {file}" : "Välj {file}", "Form settings" : "Formulärinställningar", "Sharing" : "Delning", "Settings" : "Inställningar", "Submit form" : "Skicka in formulär", - "Submitting form …" : "Skickar formulär ...", + "Submitting form …" : "Skickar formulär …", "Thank you for completing the form!" : "Tack för att du fyllde i formuläret!", + "This form was closed and is no longer taking answers" : "Formuläret har stängts och tar inte längre emot svar", + "Clear form" : "Rensa formulär", "Submit" : "Skicka", + "Confirm submit" : "Bekräfta", + "Are you sure you want to submit an empty form?" : "Är du säker att du vill skicka in ett blankt formulär?", + "Leave form" : "Lämna formulär", "You have unsaved changes! Do you still want to leave?" : "Du har osparade förändringar! Vill du fortfarande lämna?", + "Do you want to clear all answers?" : "Vill du rensa alla svar?", + "The form has changed since your last visit. Do you want to clear all answers?" : "Formuläret har ändrats sedan ditt senaste besök. Vill du rensa alla svar?", "Abort" : "Avbryt", + "Leave" : "Lämna", "Clear" : "Rensa", + "Some answers are not valid" : "Vissa svar är ogiltiga", + "There was an error submitting the form: {message}" : "Ett fel uppstod vid inskickning av formuläret: {message}", "Error while saving question" : "Ett fel uppstod vid sparande av frågan", - "Form link copied" : "Formulärlänken kopierad", - "Cannot copy, please copy the link manually" : "Kan inte kopiera, länken måste kopieras manuellt", + "Error while saving question options" : "Ett fel uppstod vid sparande av frågealternativ", + "Form link copied" : "Länk till formuläret kopierad", + "Cannot copy, please copy the link manually" : "Kan inte kopiera, vänligen kopiera länken manuellt", "No recommendations. Start typing." : "Inga rekommendationer. Börja skriva.", "No elements found." : "Inga element hittades.", "Checkboxes" : "Kryssfrågor", "Checkbox question title" : "Rubrik till kryssfråga", + "People can submit a different answer" : "Användare kan lämna ett annat svar", "Enter your answer" : "Ange ditt svar", - "This question needs a title and at least one answer!" : "Denna fråga behöver rubrik och minst ett svar!", + "This question needs a title and at least one answer!" : "Frågan behöver en rubrik och minst ett alternativ!", "Radio buttons" : "Radioknappar", "Radio buttons question title" : "Rubrik till radioknappsfråga", "Dropdown" : "Rullgardinsfråga", "Dropdown question title" : "Rubrik till rullgardinsfråga", - "People can pick one option" : "Användaren kan välja ett alternativ", + "People can pick one option" : "Användare kan välja ett alternativ", "Pick an option" : "Välj ett alternativ", "File" : "Fil", + "File question title" : "Rubrik till filfråga", "Short answer" : "Kortsvarsfråga", - "Short answer question title" : "Titel för kortsvarsfråga", - "People can enter a short answer" : "Användaren kan ange ett kort svar", + "Short answer question title" : "Rubrik för kortsvarsfråga", + "People can enter a short answer" : "Användare kan ange ett kort svar", "Long text" : "Lång text", "Long text question title" : "Rubrik till fråga med lång text", - "People can enter a long text" : "Användaren kan ange en lång text", + "People can enter a long text" : "Användare kan ange en lång text", "Date" : "Datum", "Date question title" : "Rubrik till datumfråga", - "People can pick a date" : "Personer kan välja ett datum", + "People can pick a date" : "Användare kan välja ett datum", "Pick a date" : "Välj ett datum", "Datetime" : "Datum Tid", - "Datetime question title" : "Rubrik till datum/tid - fråga", - "People can pick a date and time" : "Personer kan välja datum och tid", + "Datetime question title" : "Rubrik till datum/tid-fråga", + "People can pick a date and time" : "Användare kan välja datum och tid", "Pick a date and time" : "Välj datum och tid", "Time" : "Tid", "Time question title" : "Rubrik till tidsfråga", - "People can pick a time" : "Personer kan välja en tid", + "People can pick a time" : "Användare kan välja en tid", "Pick a time" : "Välj en tidpunkt", "Image" : "Bild", "Document" : "Dokument", @@ -184,7 +282,18 @@ OC.L10N.register( "Spreadsheet" : "Kalkylblad", "Text" : "Text", "Phone number" : "Telefonnummer", + "The input is not a valid phone number" : "Svaret är inte ett giltigt telefonnummer", + "People can enter a telephone number" : "Användare kan ange ett telefonnummer", + "Enter a telephone number" : "Fyll i ett telefonnummer", "Email address" : "E-postadress", - "Number" : "Nummer" + "The input is not a valid email address" : "Svaret är inte en giltig e-postadress", + "People can enter an email address" : "Användare kan ange en e-postadress", + "Enter an email address" : "Fyll i en e-postadress", + "Number" : "Nummer", + "The input is not a valid number" : "Svaret är inte ett giltigt tal", + "People can enter a number" : "Användare kan ange ett nummer", + "Enter a number" : "Fyll i ett tal", + "Custom regular expression" : "Anpassa regular expression", + "The input does not match the required pattern" : "Svaret passar inte det förväntade mönstret" }, "nplurals=2; plural=(n != 1);"); diff --git a/l10n/sv.json b/l10n/sv.json index bfaf9ce10..9e0ab85e4 100644 --- a/l10n/sv.json +++ b/l10n/sv.json @@ -2,103 +2,167 @@ "Forms" : "Formulär", "{user} has shared the form {formTitle} with you" : "{user} har delat formuläret {formTitle} med dig", "{user} has shared the form {formTitle} with group {group}" : "{user} har delat formuläret {formTitle} med gruppen {group}", + "{user} has shared the form {formTitle} with team {circle}" : "{user} har delat formuläret {formTitle} med teamet {circle}", + "Your form {formTitle} was answered by {user}" : "Ditt formulär {formTitle} besvarades av {user}", "Anonymous user" : "Anonym användare", "A form has been shared with you" : "Ett formulär har delats med dig", + "Someone answered a shared form" : "Någon besvarade ett delat formulär", "Someone answered a form" : "Någon besvarade ett formulär", + "Nextcloud Forms" : "Nextcloud formulär", + "Select form" : "Välj formulär", "Timestamp of data load" : "Tidstämpel för datainläsning", "No" : "Nej", "Yes" : "Ja", "Question" : "Fråga", + "Answer" : "Svar", + "Count" : "Antal", "Copy" : "Kopiera", - "Anonymous response" : "Anonymt svar", + "Anonymous response" : "Anonymt svar", "Shared by %s" : "Delad av %s", - "Forms including questions and submissions" : "Formulär inklusive frågor och inlämningar", + "Forms including questions and submissions" : "Formulär inklusive frågor och svar", "responses" : "svar", "User ID" : "Användar-ID", "User display name" : "Användarens visningsnamn", "Timestamp" : "Tidsstämpel", "📝 Simple surveys and questionnaires, self-hosted" : "📝 enkla undersökningar och formulär, egenhostade", + "**Simple surveys and questionnaires, self-hosted!**\n\n- **📝 Simple design:** No mass of options, only the essentials. Works well on mobile of course.\n- **📊 View & export results:** Results are visualized and can also be exported as CSV in the same format used by Google Forms.\n- **🔒 Data under your control!** Unlike in Google Forms, Typeform, Doodle and others, the survey info and responses are kept private on your instance.\n- **🧑‍💻 Connect to your software:** Easily integrate Forms into your service with our full-fledged [REST-API](https://github.com/nextcloud/forms/blob/main/docs/API.md).\n- **🙋 Get involved!** We have lots of stuff planned like more question types, collaboration on forms, [and much more](https://github.com/nextcloud/forms/milestones)!" : "**Enkla undersökningar och frågeformulär, självhostade!**\n\n- **📝 Enkel design:** Inte en massa alternativ, bara det väsentliga. Fungerar naturligtvis bra på mobilen.\n- **📊 Visa & exportera resultat:** Resultat visualiseras och kan även exporteras som CSV i samma format som används av Google Forms.\n- **🔒 Data under din kontroll:** Till skillnad från Google Forms, Typeform, Doodle och andra, hålls formulärdata och svar privata på din instans.\n- **🧑‍💻 Anslut till din mjukvara:** Integrera enkelt Formulär i din egna tjänst med vårt heltäckande [REST-API](https://github.com/nextcloud/forms/blob/main/docs/API.md)\n- **🙋 Engagera dig!** Vi planerar massor av saker, t.ex. fler frågetyper, samarbete på formulär, [och mycket mer](https://github.com/nextcloud/forms/milestones)!", + "Forms navigation" : "Formulärnavigation", "New form" : "Nytt formulär", - "Shared with you" : "Delad med dig", - "Loading forms …" : "Laddar formulär ...", + "Your forms" : "Dina formulär", + "Shared with you" : "Delas med dig", + "Archived forms" : "Arkiverade formulär", + "Loading forms …" : "Laddar formulär …", "No forms created yet" : "Inga formulär ännu", "Create a form" : "Skapa ett formulär", "Select a form or create a new one" : "Välj ett formulär eller skapa ett nytt", "Please select a form" : "Välj ett formulär", "Create new form" : "Skapa nytt formulär", "An error occurred while loading the forms list" : "Kunde inte ladda listan med formulär", - "Form not found" : "Hittade inte formuläret", + "Form not found" : "Formuläret hittades inte", "Unable to create a new form" : "Kunde inte skapa ett nytt formulär", - "Unable to copy form" : "Går ej att kopiera formulär", + "Unable to copy form" : "Kunde inte kopiera formulär", "This form does not exist" : "Detta formulär finns inte", "Form expired" : "Formuläret har upphört att gälla", "This form has expired and is no longer taking answers" : "Detta formulär har upphört att gälla och tar inte längre emot svar", - "Form creation" : "Skapa formulär", + "Form creation" : "Formulärskapande", "Restrict form creation to selected groups" : "Begränsa skapande av formulär till valda grupper", "Select groups" : "Välj grupper", - "Form sharing" : "Dela formulär", + "Form sharing" : "Formulärdelning", "Allow sharing by link" : "Tillåt delning via länk", "Allow sharing to all logged in accounts" : "Tillåt delning till alla inloggade konton", - "Error while saving configuration" : "Fel när konfigurationen skulle sparas", + "Allow showing form to all logged in accounts on sidebar" : "Tillåt visning i sidofältet för alla inloggade konton", + "Error while saving configuration" : "Fel vid sparande av konfiguration", "Error while reloading config" : "Fel vid omladdning av konfiguration", "Form actions" : "Formuläråtgärder", "Edit form" : "Redigera formulär", "Share form" : "Dela formuläret", "Results" : "Resultat", "Copy form" : "Kopiera formulär", + "Unarchive form" : "Avarkivera formulär", + "Archive form" : "Arkivera formulär", "Delete form" : "Ta bort formuläret", "Are you sure you want to delete {title}?" : "Vill du ta bort {title}?", + "Form closed" : "Formulär stängt", "Expired {relativeDate}" : "Upphörde {relativeDate}", "Expires {relativeDate}" : "Upphör {relativeDate}", + "Error changing archived state of form" : "Fel vid ändring av arkivstatus för formulär", "Error while deleting {title}" : "Kunde inte ta bort {title}", + "Add multiple options" : "Lägg till flera alternativ", + "Add multiple options (one per line)" : "Lägg till flera alternativ (ett per rad)", "Options" : "Alternativ", "Cancel" : "Avbryt", + "Add options" : "Lägg till alternativ", + "Options should be separated by new line!" : "Alternativ måste separeras med nya rader!", + "QR code representation of {text}" : "QR-kod för {text}", "Answer number {index}" : "Svar {index}", "Delete answer" : "Ta bort svaret", "Error while saving the answer" : "Kunde inte spara svaret", "Question number {index}" : "Fråga {index}", + "Move question up" : "Flytta upp fråga", + "Move question down" : "Flytta ned fråga", "Title of question number {index}" : "Titel för fråga {index}", "Required" : "Krävs", - "Delete question" : "Ta bort frågan", + "Technical name of the question" : "Tekniskt namn för frågan", + "Technical name" : "Tekniskt namn", + "Copy question" : "Kopiera fråga", + "Delete question" : "Ta bort fråga", "Description (formatting using Markdown is supported)" : "Beskrivning (formatering med Markdown stöds)", "This question needs a title!" : "Frågan behöver en rubrik!", "Shuffle options" : "Blanda alternativ", "Add a new answer" : "Lägg till ett nytt svar", - "There was an issue deleting this option" : "Problem uppstod vid borttagande av detta alternativ", + "There was an issue deleting this option" : "Fel vid borttagande av detta alternativ", + "Allow only specific file types" : "Tillåt endast specifika filtyper", + "Custom file extensions" : "Anpassade filändelser", + "Maximum number of files" : "Maximalt antal filer", "Maximum file size" : "Maximal filstorlek", "Delete" : "Radera", - "Uploading …" : "Laddar upp ...", + "Uploading …" : "Laddar upp …", + "Add new file as answer" : "Ladda upp ny fil som svar", + "Allowed file types: {fileTypes}." : "Tillåtna filtyper: {fileTypes}.", + "All file types are allowed." : "Alla filtyper är tillåtna.", + "The file {fileName} is too large. The maximum file size is {maxFileSize}." : "Filen {fileName} är för stor. Maximala filstorleken är {maxFileSize}.", + "There was an error during submitting the file: {message}." : "Ett fel uppstod vid uppladdning av filen: {message}.", "A long answer for the question “{text}”" : "Ett långt svar till frågan \"{text}\"", + "Add \"other\"" : "Lägg till \"annat\"", + "Require a minimum of options to be checked" : "Kräv markering av ett minsta antal alternativ ", + "Minimum options to be checked" : "Minsta antalet alternativ som behöver väljas", + "Require a maximum of options to be checked" : "Kräv markering av ett maximalt antal alternativ", + "Maximum options to be checked" : "Maximala antalet alternativ som får väljas", + "Other:" : "Annat:", "Other" : "Annat", + "Upper options limit must be greater than the lower limit" : "Max antal måste vara större än minsta antalet", + "Lower options limit must be smaller than the upper limit" : "Minsta antalet måste vara färre än max antalet", "A short answer for the question “{text}”" : "Ett kort svar till frågan \"{text}\"", + "Input types (currently: {type})" : "Svarstyper (just nu: {type})", + "Regular expression for input validation" : "Regular expression för svarsvalidering", + "Invalid regular expression" : "Ogiltigt regular expression", "No response" : "Inget svar", - "Delete this response" : "Ta bort svaret", + "Delete this response" : "Ta bort detta svar", "Store responses anonymously" : "Lagra svar anonymt", "Allow multiple responses per person" : "Tillåt flera svar per person", "Set expiration date" : "Välj utgångsdatum", "Show expiration date on form" : "Visa utgångsdatum på formuläret", - "This can not be controlled, if the form has a public link or stores responses anonymously." : "Detta kan inte kontrolleras om formuläret har en offentlig länk eller lagrar svar anonymt.", + "Close form" : "Stäng formulär", + "Closed forms do not accept new submissions." : "Stängda formulär tar inte emot nya svar.", + "Archived forms do not accept new submissions and can not be modified." : "Arkiverade formulär tar inte emot nya svar och kan inte redigeras.", + "Custom submission message" : "Anpassat meddelande vid inskickning", + "Message to show after a user submitted the form (formatting using Markdown is supported)" : "Meddelande som visas när användare har skickat in sina svar (formatering med Markdown stöds)", + "Message to show after a user submitted the form. Please note that the message will not be translated!" : "Meddelande som visas när användare har skickat in sina svar. Obs: meddelandet kommer inte att översättas!", + "This can not be controlled, if the form has a public link or stores responses anonymously." : "Detta kan inte ställas in om formuläret har en offentlig länk eller lagrar svar anonymt.", "Expired on {date}" : "Slutade gälla {date}", "Expires on {date}" : "Slutar gälla {date}", + "Search for user, group or team …" : "Sök efter användare, grupper eller team …", "Permissions" : "Behörigheter", "View responses" : "Visa svar", + "Delete responses" : "Ta bort svar", "Group" : "Grupp", "Team" : "Team", "Share link" : "Delningslänk", "Add link" : "Lägg till länk", + "Embeddable link" : "Inbäddningsbar länk", "Copy to clipboard" : "Kopiera till urklipp", "Show QR code" : "Visa QR-kod", "Copy embedding code" : "Kopiera inbäddningskod", + "Convert to embeddable link" : "Konvertera till inbäddningsbar länk", "Remove link" : "Ta bort länk", + "Share {formTitle}" : "Dela {formTitle}", "Internal link" : "Intern länk", "Only works for logged in accounts with access rights" : "Fungerar endast för inloggade konton med åtkomsträttigheter", - "Permit access to all logged in accounts" : "Tillåt åtkomst till alla inloggade konton", + "Permit access to all logged in accounts" : "Tillåt åtkomst för alla inloggade konton", "Show to all accounts on sidebar" : "Visa för alla konton på sidofältet", - "There was an error while adding the share" : "Det uppstod ett fel när delningen lades till", - "There was an error while adding the link" : "Det uppstod ett fel när länken lades till", - "There was an error while updating the share" : "Det uppstod ett fel när delingen skulle uppdateras", - "There was an error while removing the share" : "Det uppstod ett fel när delningen skulle tas bort", + "There was an error while adding the share" : "Ett fel uppstod vid tilläggning av delningen", + "There was an error while adding the link" : "Ett fel uppstod vid tilläggning av länken", + "There was an error while updating the share" : "Ett fel uppstod vid uppdatering av delningen", + "There was an error while removing the share" : "Ett fel uppstod vid borttagning av delningen", "Transfer ownership" : "Överför ägarskap", + "You're going to transfer the ownership of {name} to another account. Please select the account to which you want to transfer ownership." : "Du kommer att överföra ägarskap av {name} till ett annat konto. Välj vilket konto ägarskapet skall överföras till.", + "Search for a user" : "Sök efter användare", + "Type {text} to confirm." : "Fyll i {text} för att bekräfta.", + "Confirmation text" : "Bekräftelsetext", + "This can not be undone." : "Detta kan inte ångras", + "I understand, transfer this form" : "Jag förstår, överför formuläret", + "This form is now owned by" : "Detta formulär ägs nu av", + "An error occurred while transfering ownership" : "Ett fel uppstod vid överföring av ägarskap", "View mode" : "Visningsläge", "Share" : "Dela", "Toggle settings" : "Växla inställningar", @@ -106,7 +170,10 @@ "View" : "Visa", "Edit" : "Ändra", "Show results" : "Visa resultat", + "Create form" : "Skapa formulär", "Loading {title} …" : "Läser in {title} …", + "Form is archived" : "Formulär arkiverat", + "Form '{title}' is archived and cannot be modified." : "Formulär '{title}' är arkiverat och kan inte redigeras", "Form title" : "Rubrik", "Description" : "Beskrivning", "Add a question" : "Lägg till en fråga", @@ -116,65 +183,96 @@ "Expired {relativeDate}." : "Upphörde {relativeDate}.", "Expires {relativeDate}." : "Upphör {relativeDate}.", "There was an error while adding the new question" : "Ett fel uppstod vid sparande av den nya frågan", - "There was an error while removing the question" : "Ett fel uppstod vid borttagande av frågan", + "There was an error while removing the question" : "Ett fel uppstod vid borttagning av frågan", "Error while saving form" : "Kunde inte spara formuläret", - "Loading responses …" : "Hämtar svar ...", + "Linked file not available" : "Kopplad fil inte tillgänglig", + "Linked file is not available, would you like to link a new file?" : "Den kopplade filen är inte tillgänglig, vill du koppla en ny fil?", + "Loading responses …" : "Hämtar svar …", "No responses yet" : "Inga svar ännu", "Results of submitted forms will show up here" : "Resultat från inskickade formulär visas här", + "Create spreadsheet" : "Skapa kalkylblad", + "Open spreadsheet" : "Öppna kalkylblad", "{amount} responses" : "{amount} svar", + "Re-export spreadsheet" : "Exportera kalkylblad igen", + "Unlink spreadsheet" : "Bryt koppling med kalkylblad", + "Save copy to Files" : "Spara kopia till Filer", "Download" : "Ladda ner", "Delete all responses" : "Ta bort alla svar", + "Delete submissions" : "Ta bort svar", "Are you sure you want to delete all responses of {title}?" : "Är du säker på att du vill ta bort alla svar till {title}?", "Summary" : "Sammanfattning", "Responses" : "Svar", "There was an error while loading the results" : "Ett fel uppstod vid hämtning av resultaten", + "File {file} successfully linked" : "Fil {file} har kopplats", + "There was an error while linking the file" : "Ett fel uppstod vid koppling av filen", "Export successful to {file}" : "Export till {file} lyckades", + "There was an error while exporting to Files" : "Ett fel uppstod vid export till Filer", + "File is not linked" : "Ingen fil är kopplad", "There was an error, while exporting to Files" : "Det inträffade ett fel, vid export till Filer", - "There was an error while removing this response" : "Ett fel uppstod vid borttagande av detta svar", - "There was an error while removing responses" : "Ett fel uppstod vid borttagande av svar", + "Submission deleted" : "Svar borttaget", + "There was an error while removing this response" : "Ett fel uppstod vid borttagning av detta svar", + "There was an error while removing responses" : "Ett fel uppstod vid borttagning av svar", + "Choose spreadsheet location" : "Välj plats för kalkylblad", + "Create XLSX" : "Skapa XLSX", + "Create CSV" : "Skapa CSV", + "Create ODS" : "Skapa ODS", + "Select {file}" : "Välj {file}", "Form settings" : "Formulärinställningar", "Sharing" : "Delning", "Settings" : "Inställningar", "Submit form" : "Skicka in formulär", - "Submitting form …" : "Skickar formulär ...", + "Submitting form …" : "Skickar formulär …", "Thank you for completing the form!" : "Tack för att du fyllde i formuläret!", + "This form was closed and is no longer taking answers" : "Formuläret har stängts och tar inte längre emot svar", + "Clear form" : "Rensa formulär", "Submit" : "Skicka", + "Confirm submit" : "Bekräfta", + "Are you sure you want to submit an empty form?" : "Är du säker att du vill skicka in ett blankt formulär?", + "Leave form" : "Lämna formulär", "You have unsaved changes! Do you still want to leave?" : "Du har osparade förändringar! Vill du fortfarande lämna?", + "Do you want to clear all answers?" : "Vill du rensa alla svar?", + "The form has changed since your last visit. Do you want to clear all answers?" : "Formuläret har ändrats sedan ditt senaste besök. Vill du rensa alla svar?", "Abort" : "Avbryt", + "Leave" : "Lämna", "Clear" : "Rensa", + "Some answers are not valid" : "Vissa svar är ogiltiga", + "There was an error submitting the form: {message}" : "Ett fel uppstod vid inskickning av formuläret: {message}", "Error while saving question" : "Ett fel uppstod vid sparande av frågan", - "Form link copied" : "Formulärlänken kopierad", - "Cannot copy, please copy the link manually" : "Kan inte kopiera, länken måste kopieras manuellt", + "Error while saving question options" : "Ett fel uppstod vid sparande av frågealternativ", + "Form link copied" : "Länk till formuläret kopierad", + "Cannot copy, please copy the link manually" : "Kan inte kopiera, vänligen kopiera länken manuellt", "No recommendations. Start typing." : "Inga rekommendationer. Börja skriva.", "No elements found." : "Inga element hittades.", "Checkboxes" : "Kryssfrågor", "Checkbox question title" : "Rubrik till kryssfråga", + "People can submit a different answer" : "Användare kan lämna ett annat svar", "Enter your answer" : "Ange ditt svar", - "This question needs a title and at least one answer!" : "Denna fråga behöver rubrik och minst ett svar!", + "This question needs a title and at least one answer!" : "Frågan behöver en rubrik och minst ett alternativ!", "Radio buttons" : "Radioknappar", "Radio buttons question title" : "Rubrik till radioknappsfråga", "Dropdown" : "Rullgardinsfråga", "Dropdown question title" : "Rubrik till rullgardinsfråga", - "People can pick one option" : "Användaren kan välja ett alternativ", + "People can pick one option" : "Användare kan välja ett alternativ", "Pick an option" : "Välj ett alternativ", "File" : "Fil", + "File question title" : "Rubrik till filfråga", "Short answer" : "Kortsvarsfråga", - "Short answer question title" : "Titel för kortsvarsfråga", - "People can enter a short answer" : "Användaren kan ange ett kort svar", + "Short answer question title" : "Rubrik för kortsvarsfråga", + "People can enter a short answer" : "Användare kan ange ett kort svar", "Long text" : "Lång text", "Long text question title" : "Rubrik till fråga med lång text", - "People can enter a long text" : "Användaren kan ange en lång text", + "People can enter a long text" : "Användare kan ange en lång text", "Date" : "Datum", "Date question title" : "Rubrik till datumfråga", - "People can pick a date" : "Personer kan välja ett datum", + "People can pick a date" : "Användare kan välja ett datum", "Pick a date" : "Välj ett datum", "Datetime" : "Datum Tid", - "Datetime question title" : "Rubrik till datum/tid - fråga", - "People can pick a date and time" : "Personer kan välja datum och tid", + "Datetime question title" : "Rubrik till datum/tid-fråga", + "People can pick a date and time" : "Användare kan välja datum och tid", "Pick a date and time" : "Välj datum och tid", "Time" : "Tid", "Time question title" : "Rubrik till tidsfråga", - "People can pick a time" : "Personer kan välja en tid", + "People can pick a time" : "Användare kan välja en tid", "Pick a time" : "Välj en tidpunkt", "Image" : "Bild", "Document" : "Dokument", @@ -182,7 +280,18 @@ "Spreadsheet" : "Kalkylblad", "Text" : "Text", "Phone number" : "Telefonnummer", + "The input is not a valid phone number" : "Svaret är inte ett giltigt telefonnummer", + "People can enter a telephone number" : "Användare kan ange ett telefonnummer", + "Enter a telephone number" : "Fyll i ett telefonnummer", "Email address" : "E-postadress", - "Number" : "Nummer" + "The input is not a valid email address" : "Svaret är inte en giltig e-postadress", + "People can enter an email address" : "Användare kan ange en e-postadress", + "Enter an email address" : "Fyll i en e-postadress", + "Number" : "Nummer", + "The input is not a valid number" : "Svaret är inte ett giltigt tal", + "People can enter a number" : "Användare kan ange ett nummer", + "Enter a number" : "Fyll i ett tal", + "Custom regular expression" : "Anpassa regular expression", + "The input does not match the required pattern" : "Svaret passar inte det förväntade mönstret" },"pluralForm" :"nplurals=2; plural=(n != 1);" } \ No newline at end of file From f69a1161920f22505140e98e8a426c20f7d9e95e Mon Sep 17 00:00:00 2001 From: Christian Hartmann Date: Fri, 3 Jan 2025 15:44:56 +0100 Subject: [PATCH 2/4] make Forms available in unified search Signed-off-by: Christian Hartmann --- CHANGELOG.en.md | 6 +++ css/forms.css | 11 +++++ lib/AppInfo/Application.php | 2 + lib/Db/FormMapper.php | 16 ++++++- lib/Search/FormsSearchResultEntry.php | 22 +++++++++ lib/Search/SearchProvider.php | 67 +++++++++++++++++++++++++++ lib/Service/FormsService.php | 28 +++++++++++ 7 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 lib/Search/FormsSearchResultEntry.php create mode 100644 lib/Search/SearchProvider.php diff --git a/CHANGELOG.en.md b/CHANGELOG.en.md index 51419c06b..20609473a 100644 --- a/CHANGELOG.en.md +++ b/CHANGELOG.en.md @@ -5,6 +5,12 @@ # Changelog +## v5.0.0 - tbd + +- **Unified Search integration** + + You can now use the Unified Search to search forms based on the title and the description. + ## v4.3.0 - 2024-10-04 - **New question type: Files** diff --git a/css/forms.css b/css/forms.css index 9d66d1633..62b659990 100644 --- a/css/forms.css +++ b/css/forms.css @@ -9,3 +9,14 @@ html { scroll-padding-top: calc(var(--header-height) + 60px); } + +.icon-forms { + background-image: url(../img/forms-dark.svg); + filter: var(--background-invert-if-dark); +} + +.icon-forms-white, +.icon-forms.icon-white { + background-image: url(../img/forms.svg); + filter: var(--background-invert-if-dark); +} diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php index 1dbf407bc..319e48f3e 100644 --- a/lib/AppInfo/Application.php +++ b/lib/AppInfo/Application.php @@ -14,6 +14,7 @@ use OCA\Forms\FormsMigrator; use OCA\Forms\Listener\AnalyticsDatasourceListener; use OCA\Forms\Listener\UserDeletedListener; +use OCA\Forms\Search\SearchProvider; use OCP\AppFramework\App; use OCP\AppFramework\Bootstrap\IBootContext; use OCP\AppFramework\Bootstrap\IBootstrap; @@ -42,6 +43,7 @@ public function register(IRegistrationContext $context): void { $context->registerCapability(Capabilities::class); $context->registerEventListener(UserDeletedEvent::class, UserDeletedListener::class); $context->registerEventListener(DatasourceEvent::class, AnalyticsDatasourceListener::class); + $context->registerSearchProvider(SearchProvider::class); $context->registerUserMigrator(FormsMigrator::class); } diff --git a/lib/Db/FormMapper.php b/lib/Db/FormMapper.php index 89a032f3b..649494b3b 100644 --- a/lib/Db/FormMapper.php +++ b/lib/Db/FormMapper.php @@ -97,9 +97,10 @@ public function findByHash(string $hash): Form { * @param string[] $groups IDs of groups the user is memeber of * @param string[] $teams IDs of teams the user is memeber of * @param bool $filterShown Set to false to also include forms shared but not visible on sidebar + * @param string $queryTerm optional: The search query for universal search * @return Form[] */ - public function findSharedForms(string $userId, array $groups = [], array $teams = [], bool $filterShown = true): array { + public function findSharedForms(string $userId, array $groups = [], array $teams = [], bool $filterShown = true, ?string $queryTerm = null): array { $qbShares = $this->db->getQueryBuilder(); $qbForms = $this->db->getQueryBuilder(); @@ -156,6 +157,11 @@ public function findSharedForms(string $userId, array $groups = [], array $teams ->addOrderBy('last_updated', 'DESC') ->addOrderBy('created', 'DESC'); + if ($queryTerm) { + $qbForms->andWhere($qbForms->expr()->iLike('title', $qbForms->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%')) . + ' OR ' . $qbForms->expr()->iLike('description', $qbForms->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%'))); + } + // We need to add the parameters from the shared forms IDs select to the final select query $qbForms->setParameters($qbShares->getParameters(), $qbShares->getParameterTypes()); @@ -163,9 +169,10 @@ public function findSharedForms(string $userId, array $groups = [], array $teams } /** + * @param string $queryTerm optional: The search query for universal search * @return Form[] */ - public function findAllByOwnerId(string $ownerId): array { + public function findAllByOwnerId(string $ownerId, ?string $queryTerm = null): array { $qb = $this->db->getQueryBuilder(); $qb->select('*') @@ -177,6 +184,11 @@ public function findAllByOwnerId(string $ownerId): array { ->addOrderBy('last_updated', 'DESC') ->addOrderBy('created', 'DESC'); + if ($queryTerm) { + $qb->andWhere($qb->expr()->iLike('title', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%')) . + ' OR ' . $qb->expr()->iLike('description', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%'))); + } + return $this->findEntities($qb); } diff --git a/lib/Search/FormsSearchResultEntry.php b/lib/Search/FormsSearchResultEntry.php new file mode 100644 index 000000000..7ddd7cbb3 --- /dev/null +++ b/lib/Search/FormsSearchResultEntry.php @@ -0,0 +1,22 @@ +linkToRoute('forms.page.views', ['hash' => $form->getHash(), 'view' => 'submit']); + $iconURL = $urlGenerator->getAbsoluteURL(($urlGenerator->imagePath(Application::APP_ID, 'forms-dark.svg'))); + parent::__construct($iconURL, $form->getTitle(), $form->getDescription(), $formURL, 'icon-forms'); + } +} diff --git a/lib/Search/SearchProvider.php b/lib/Search/SearchProvider.php new file mode 100644 index 000000000..4b0c2e61b --- /dev/null +++ b/lib/Search/SearchProvider.php @@ -0,0 +1,67 @@ +l10n->t('Forms'); + } + + public function search(IUser $user, ISearchQuery $query): SearchResult { + $forms = $this->formsService->search($query); + + $results = array_map(function (Form $form) { + return [ + 'object' => $form, + 'entry' => new FormsSearchResultEntry($form, $this->urlGenerator) + ]; + }, $forms); + + $resultEntries = array_map(function (array $result) { + return $result['entry']; + }, $results); + + return SearchResult::complete( + $this->l10n->t('Forms'), + $resultEntries + ); + } + + public function getOrder(string $route, array $routeParameters): int { + if (str_contains($route, Application::APP_ID)) { + // Active app, prefer my results + return -1; + } + return 77; + } +} diff --git a/lib/Service/FormsService.php b/lib/Service/FormsService.php index d9be9a3c8..62a85799c 100644 --- a/lib/Service/FormsService.php +++ b/lib/Service/FormsService.php @@ -29,6 +29,7 @@ use OCP\IUser; use OCP\IUserManager; use OCP\IUserSession; +use OCP\Search\ISearchQuery; use OCP\Security\ISecureRandom; use OCP\Share\IShare; @@ -698,6 +699,33 @@ public function areExtraSettingsValid(array $extraSettings, string $questionType return true; } + /** + * Get list of forms + * + * @param ISearchQuery $query the query to search the forms + * @return Form[] list of forms that match the query + */ + public function search(ISearchQuery $query): array { + $formsList = []; + $groups = $this->groupManager->getUserGroupIds($this->currentUser); + $teams = $this->circlesService->getUserTeamIds($this->currentUser->getUID()); + + try { + $ownedForms = $this->formMapper->findAllByOwnerId($this->currentUser->getUID(), $query->getTerm()); + $sharedForms = $this->formMapper->findSharedForms( + $this->currentUser->getUID(), + $groups, + $teams, + true, + $query->getTerm() + ); + $formsList = array_merge($ownedForms, $sharedForms); + } catch (DoesNotExistException $e) { + // silent catch + } + return $formsList; + } + public function getFilePath(Form $form): ?string { $fileId = $form->getFileId(); From ed188d999596f3ee4f674c272427d012a1ae6b0b Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 7 Jan 2025 11:36:32 +0100 Subject: [PATCH 3/4] test(playwright): use getContainer from @nextcloud/cypress/docker Avoid hardcoding `containerName` to make reuse in other apps easier Signed-off-by: Max --- playwright/support/setup.ts | 6 ++---- playwright/support/utils/session.ts | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/playwright/support/setup.ts b/playwright/support/setup.ts index 412d4900f..c85fd2127 100644 --- a/playwright/support/setup.ts +++ b/playwright/support/setup.ts @@ -4,7 +4,7 @@ */ import { test as setup } from '@playwright/test' -import { configureNextcloud, docker } from '@nextcloud/cypress/docker' +import { configureNextcloud, getContainer } from '@nextcloud/cypress/docker' /** * We use this to ensure Nextcloud is configured correctly before running our tests @@ -13,7 +13,5 @@ import { configureNextcloud, docker } from '@nextcloud/cypress/docker' * as that only checks for the URL to be accessible which happens already before everything is configured. */ setup('Configure Nextcloud', async () => { - const containerName = 'nextcloud-cypress-tests_forms' - const container = docker.getContainer(containerName) - await configureNextcloud(['forms', 'viewer'], undefined, container) + await configureNextcloud(['forms', 'viewer'], undefined, getContainer()) }) diff --git a/playwright/support/utils/session.ts b/playwright/support/utils/session.ts index 483d82a3a..7038e8c2f 100644 --- a/playwright/support/utils/session.ts +++ b/playwright/support/utils/session.ts @@ -3,7 +3,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { docker } from '@nextcloud/cypress/docker' +import { getContainer } from '@nextcloud/cypress/docker' import { expect, type APIRequestContext } from '@playwright/test' /** @@ -22,8 +22,7 @@ export async function runShell( env?: Record }, ) { - const containerName = 'nextcloud-cypress-tests_forms' - const container = docker.getContainer(containerName) + const container = getContainer() const exec = await container.exec({ Cmd: ['sh', '-c', command], From 07aecc9300bce255c166c9b48a429fd3e48dcebf Mon Sep 17 00:00:00 2001 From: Christian Hartmann Date: Tue, 7 Jan 2025 11:31:12 +0100 Subject: [PATCH 4/4] Refactor FormMapper: Improve parameter handling in findSharedForms method Signed-off-by: Christian Hartmann --- lib/Db/FormMapper.php | 45 ++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/lib/Db/FormMapper.php b/lib/Db/FormMapper.php index 649494b3b..8f79496a6 100644 --- a/lib/Db/FormMapper.php +++ b/lib/Db/FormMapper.php @@ -108,16 +108,16 @@ public function findSharedForms(string $userId, array $groups = [], array $teams // share type user and share with current user $memberships->add( $qbShares->expr()->andX( - $qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_USER)), - $qbShares->expr()->eq('shares.share_with', $qbShares->createNamedParameter($userId, IQueryBuilder::PARAM_STR)), + $qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_USER, IQueryBuilder::PARAM_STR, ':share_type_user')), + $qbShares->expr()->eq('shares.share_with', $qbShares->createNamedParameter($userId, IQueryBuilder::PARAM_STR, ':share_with_user')), ), ); // share type group and one of the user groups if (!empty($groups)) { $memberships->add( $qbShares->expr()->andX( - $qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_GROUP)), - $qbShares->expr()->in('shares.share_with', $qbShares->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY)), + $qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_GROUP, IQueryBuilder::PARAM_STR, ':share_type_group')), + $qbShares->expr()->in('shares.share_with', $qbShares->createNamedParameter($groups, IQueryBuilder::PARAM_STR_ARRAY, ':share_with_groups')), ), ); } @@ -125,19 +125,19 @@ public function findSharedForms(string $userId, array $groups = [], array $teams if (!empty($teams)) { $memberships->add( $qbShares->expr()->andX( - $qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_CIRCLE)), - $qbShares->expr()->in('shares.share_with', $qbShares->createNamedParameter($teams, IQueryBuilder::PARAM_STR_ARRAY)), + $qbShares->expr()->eq('shares.share_type', $qbShares->createNamedParameter(IShare::TYPE_CIRCLE, IQueryBuilder::PARAM_STR, ':share_type_team')), + $qbShares->expr()->in('shares.share_with', $qbShares->createNamedParameter($teams, IQueryBuilder::PARAM_STR_ARRAY, ':share_with_teams')), ), ); } - // build expression for publicy shared forms (default only directly shown) + // build expression for publicly shared forms (default only directly shown) if ($filterShown) { // Only shown - $access = $qbShares->expr()->in('access_enum', $qbShares->createNamedParameter(Constants::FORM_ACCESS_ARRAY_SHOWN, IQueryBuilder::PARAM_INT_ARRAY)); + $access = $qbShares->expr()->in('access_enum', $qbShares->createNamedParameter(Constants::FORM_ACCESS_ARRAY_SHOWN, IQueryBuilder::PARAM_INT_ARRAY, ':access_shown')); } else { // All - $access = $qbShares->expr()->neq('access_enum', $qbShares->createNamedParameter(Constants::FORM_ACCESS_NOPUBLICSHARE, IQueryBuilder::PARAM_INT)); + $access = $qbShares->expr()->neq('access_enum', $qbShares->createNamedParameter(Constants::FORM_ACCESS_NOPUBLICSHARE, IQueryBuilder::PARAM_INT, ':access_nopublicshare')); } // Select all DISTINCT IDs of shared forms @@ -146,7 +146,7 @@ public function findSharedForms(string $userId, array $groups = [], array $teams ->leftJoin('forms', $this->shareMapper->getTableName(), 'shares', $qbShares->expr()->eq('forms.id', 'shares.form_id')) ->where($memberships) ->orWhere($access) - ->andWhere($qbShares->expr()->neq('forms.owner_id', $qbShares->createNamedParameter($userId, IQueryBuilder::PARAM_STR))); + ->andWhere($qbShares->expr()->neq('forms.owner_id', $qbShares->createNamedParameter($userId, IQueryBuilder::PARAM_STR, ':owner_id'))); // Select the whole forms for the DISTINCT shared forms IDs $qbForms->select('*') @@ -158,12 +158,20 @@ public function findSharedForms(string $userId, array $groups = [], array $teams ->addOrderBy('created', 'DESC'); if ($queryTerm) { - $qbForms->andWhere($qbForms->expr()->iLike('title', $qbForms->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%')) . - ' OR ' . $qbForms->expr()->iLike('description', $qbForms->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%'))); + $likeParameter = '%' . $this->db->escapeLikeParameter($queryTerm) . '%'; + $qbForms->andWhere( + $qbForms->expr()->orX( + $qbForms->expr()->iLike('title', $qbForms->createNamedParameter($likeParameter, IQueryBuilder::PARAM_STR, ':query_term_title')), + $qbForms->expr()->iLike('description', $qbForms->createNamedParameter($likeParameter, IQueryBuilder::PARAM_STR, ':query_term_description')) + ) + ); } - // We need to add the parameters from the shared forms IDs select to the final select query - $qbForms->setParameters($qbShares->getParameters(), $qbShares->getParameterTypes()); + // Merge parameters and parameter types from $qbShares and $qbForms + $qbFormsParams = array_merge($qbShares->getParameters(), $qbForms->getParameters()); + $qbFormsParamTypes = array_merge($qbShares->getParameterTypes(), $qbForms->getParameterTypes()); + + $qbForms->setParameters($qbFormsParams, $qbFormsParamTypes); return $this->findEntities($qbForms); } @@ -185,8 +193,13 @@ public function findAllByOwnerId(string $ownerId, ?string $queryTerm = null): ar ->addOrderBy('created', 'DESC'); if ($queryTerm) { - $qb->andWhere($qb->expr()->iLike('title', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%')) . - ' OR ' . $qb->expr()->iLike('description', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($queryTerm) . '%'))); + $likeParameter = '%' . $this->db->escapeLikeParameter($queryTerm) . '%'; + $qb->andWhere( + $qb->expr()->orX( + $qb->expr()->iLike('title', $qb->createNamedParameter($likeParameter, IQueryBuilder::PARAM_STR, ':query_term_title')), + $qb->expr()->iLike('description', $qb->createNamedParameter($likeParameter, IQueryBuilder::PARAM_STR, ':query_term_description')) + ) + ); } return $this->findEntities($qb);