This commit is contained in:
John Molakvoæ (skjnldsv) 2020-06-09 23:06:38 +02:00
commit 15e35f51c2
No known key found for this signature in database
GPG key ID: 60C25B8C072916CF
23 changed files with 406 additions and 185 deletions

View file

@ -1,6 +1,11 @@
OC.L10N.register( OC.L10N.register(
"forms", "forms",
{ {
"Anonymous response" : "익명 답변",
"Shared by %s" : "%s에 의해 공유됨",
"New form" : "새로운 형식",
"Loading forms …" : "형식 로딩중 ...",
"Create a form" : "형식 생성",
"Create new form" : "새로운 양식 생성", "Create new form" : "새로운 양식 생성",
"An error occurred while loading the forms list" : "양식 리스트를 가져오는데 문제가 발생하였습니다.", "An error occurred while loading the forms list" : "양식 리스트를 가져오는데 문제가 발생하였습니다.",
"Unable to create a new form" : "새 양식 생성할 수 없음", "Unable to create a new form" : "새 양식 생성할 수 없음",
@ -9,8 +14,11 @@ OC.L10N.register(
"Are you sure you want to delete {title}?" : "정말로 {title}을 삭제할 것입니까?", "Are you sure you want to delete {title}?" : "정말로 {title}을 삭제할 것입니까?",
"Error while deleting {title}" : "{title} 삭제 실패", "Error while deleting {title}" : "{title} 삭제 실패",
"Answer number {index}" : "응답 번호 {index}", "Answer number {index}" : "응답 번호 {index}",
"Delete answer" : "답변 지우기",
"Error while saving the answer" : "응답 저장 실패", "Error while saving the answer" : "응답 저장 실패",
"Question title" : "질문 제목",
"Delete question" : "질문 삭제", "Delete question" : "질문 삭제",
"Delete this response" : "이 응답 지우기",
"Searching …" : "검색 ...", "Searching …" : "검색 ...",
"Group" : "그룹", "Group" : "그룹",
"Description" : "설명", "Description" : "설명",
@ -21,6 +29,7 @@ OC.L10N.register(
"Expiration date" : "만료 일자", "Expiration date" : "만료 일자",
"Sharing" : "공유", "Sharing" : "공유",
"Share link" : "링크 공유", "Share link" : "링크 공유",
"Select expiration date" : "만료일자를 고르시오.",
"Submit" : "제출", "Submit" : "제출",
"Thank you for completing the form!" : "양식을 작성해주셔서 감사합니다!", "Thank you for completing the form!" : "양식을 작성해주셔서 감사합니다!",
"There was an error submitting the form" : "양식 제출 실패", "There was an error submitting the form" : "양식 제출 실패",

View file

@ -1,4 +1,9 @@
{ "translations": { { "translations": {
"Anonymous response" : "익명 답변",
"Shared by %s" : "%s에 의해 공유됨",
"New form" : "새로운 형식",
"Loading forms …" : "형식 로딩중 ...",
"Create a form" : "형식 생성",
"Create new form" : "새로운 양식 생성", "Create new form" : "새로운 양식 생성",
"An error occurred while loading the forms list" : "양식 리스트를 가져오는데 문제가 발생하였습니다.", "An error occurred while loading the forms list" : "양식 리스트를 가져오는데 문제가 발생하였습니다.",
"Unable to create a new form" : "새 양식 생성할 수 없음", "Unable to create a new form" : "새 양식 생성할 수 없음",
@ -7,8 +12,11 @@
"Are you sure you want to delete {title}?" : "정말로 {title}을 삭제할 것입니까?", "Are you sure you want to delete {title}?" : "정말로 {title}을 삭제할 것입니까?",
"Error while deleting {title}" : "{title} 삭제 실패", "Error while deleting {title}" : "{title} 삭제 실패",
"Answer number {index}" : "응답 번호 {index}", "Answer number {index}" : "응답 번호 {index}",
"Delete answer" : "답변 지우기",
"Error while saving the answer" : "응답 저장 실패", "Error while saving the answer" : "응답 저장 실패",
"Question title" : "질문 제목",
"Delete question" : "질문 삭제", "Delete question" : "질문 삭제",
"Delete this response" : "이 응답 지우기",
"Searching …" : "검색 ...", "Searching …" : "검색 ...",
"Group" : "그룹", "Group" : "그룹",
"Description" : "설명", "Description" : "설명",
@ -19,6 +27,7 @@
"Expiration date" : "만료 일자", "Expiration date" : "만료 일자",
"Sharing" : "공유", "Sharing" : "공유",
"Share link" : "링크 공유", "Share link" : "링크 공유",
"Select expiration date" : "만료일자를 고르시오.",
"Submit" : "제출", "Submit" : "제출",
"Thank you for completing the form!" : "양식을 작성해주셔서 감사합니다!", "Thank you for completing the form!" : "양식을 작성해주셔서 감사합니다!",
"There was an error submitting the form" : "양식 제출 실패", "There was an error submitting the form" : "양식 제출 실패",

View file

@ -1,20 +1,76 @@
OC.L10N.register( OC.L10N.register(
"forms", "forms",
{ {
"Anonymous response" : "Anonimni odziv",
"Forms" : "Vprašalniki", "Forms" : "Vprašalniki",
"📝 Simple surveys and questionnaires, self-hosted" : "Enostavni vprašalnik in ankete na domačem strežniku",
"New form" : "Nov vprašalnik",
"Loading forms …" : "Poteka nalaganje vprašalnikov …",
"No forms created yet" : "Ni še ustvarjenih vprašalnikov",
"Create a form" : "Ustvarite vprašalnik",
"Select a form or create a new one" : "Izberite vprašalnik oziroma ustvarite novega",
"Create new form" : "Ustvari nov vprašalnik", "Create new form" : "Ustvari nov vprašalnik",
"An error occurred while loading the forms list" : "Med nalaganjem seznama vprašalnikov je prišlo do napake",
"Unable to create a new form" : "Novega vprašalnika ni mogoče ustvariti",
"Responses" : "Odzivi",
"Clone form" : "Kloniraj vprašalnik",
"Delete form" : "Izbriši vprašalnik", "Delete form" : "Izbriši vprašalnik",
"Form link copied" : "Povezava vprašalnika je kopirana",
"Cannot copy, please copy the link manually" : "Povezave ni mogoče kopirati. Storite to ročno.", "Cannot copy, please copy the link manually" : "Povezave ni mogoče kopirati. Storite to ročno.",
"Copy share link" : "Kopiraj povezavo souporabe",
"Are you sure you want to delete {title}?" : "Ali ste prepričani, da želite izbrisati vprašalnik {title}?",
"Delete answer" : "Izbriši odgovor",
"There was an issue deleting this option" : "Prišlo je do napake med brisanjem možnosti",
"Error while saving the answer" : "Prišlo je do napake med shranjevanjem odgovora",
"Question number {index}" : "Vprašanje številka {index}",
"Drag to reorder the questions" : "Potegnite vprašanja in jih preuredite",
"Question title" : "Naslov vprašanja",
"Title of question number {index}" : "Naslov vprašanja številka {index}",
"Mandatory" : "Obvezno",
"Delete question" : "Izbriši vprašanje",
"A long answer for the question “{text}”" : "Dolg odgovor na vprašanje »{text}«",
"Long answer text" : "Besedilo dolgega odgovora",
"Add a new answer" : "Dodaj nov odgovor",
"A short answer for the question “{text}”" : "Kratek odgovor na vprašanje »{text}«",
"Short answer text" : "Besedilo kratkega odgovora",
"Delete this response" : "Izbriši ta odziv",
"User or group name …" : "Ime uporabnika oziroma skupine ...",
"No recommendations. Start typing." : "Ni priporočil; začnite vpisovati",
"Searching …" : "Poteka iskanje ...", "Searching …" : "Poteka iskanje ...",
"No elements found." : "Ni najdenih predmetov", "No elements found." : "Ni najdenih predmetov",
"Group" : "Skupina", "Group" : "Skupina",
"Loading {title} …" : "Poteka nalaganje {title} …",
"Toggle settings" : "Preklopi nastavitve",
"Form title" : "Naslov vprašalnika",
"Description" : "Opis", "Description" : "Opis",
"Mandatory questions" : "Obvezna vprašanja",
"Add a question" : "Dodaj vprašanje",
"There was an error while adding the new question" : "Prišlo je do napake med dodajanjem novega vprašanja",
"There was an error while removing the question" : "Prišlo je do napake med odstranjevanjem vprašanja",
"Error while saving form" : "Prišlo je do napake med shranjevanjem vprašalnika",
"Loading responses …" : "Poteka nalaganje odzivov ...",
"Back to questions" : "Nazaj na vprašanja",
"Responses for {title}" : "Odzivi za vprašalnik {title}",
"Export to CSV" : "Izvozi v datoteko CSV",
"Options" : "Možnosti", "Options" : "Možnosti",
"Delete all responses" : "Izbriši vse odzive",
"No responses yet" : "Ni še odzivov",
"Settings" : "Nastavitve", "Settings" : "Nastavitve",
"Anonymous responses" : "Anonimni odzivi",
"Set expiration date" : "Nastavi datum preteka", "Set expiration date" : "Nastavi datum preteka",
"Expiration date" : "Datum preteka", "Expiration date" : "Datum preteka",
"Sharing" : "Souporaba", "Sharing" : "Souporaba",
"Share link" : "Povezava za souporabo", "Share link" : "Povezava za souporabo",
"Submit" : "Pošlji" "Submit" : "Pošlji",
"Submit form" : "Objavi obrazec",
"Submitting form …" : "Poteka objavljanje obrazca ...",
"Error while saving question" : "Prišlo je do napake med shranjevanjem vprašanja",
"Multiple choice" : "Izbirno vprašanje",
"Checkboxes" : "Izbirna polja",
"Short answer" : "Kratek odgovor",
"Long text" : "Dolg odgovor",
"Form expired" : "Vprašalnik je potekel",
"Form not found" : "Vprašalnika ni mogoče najti",
"This form does not exist" : "Ta vprašalnik ne obstaja"
}, },
"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"); "nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);");

View file

@ -1,18 +1,74 @@
{ "translations": { { "translations": {
"Anonymous response" : "Anonimni odziv",
"Forms" : "Vprašalniki", "Forms" : "Vprašalniki",
"📝 Simple surveys and questionnaires, self-hosted" : "Enostavni vprašalnik in ankete na domačem strežniku",
"New form" : "Nov vprašalnik",
"Loading forms …" : "Poteka nalaganje vprašalnikov …",
"No forms created yet" : "Ni še ustvarjenih vprašalnikov",
"Create a form" : "Ustvarite vprašalnik",
"Select a form or create a new one" : "Izberite vprašalnik oziroma ustvarite novega",
"Create new form" : "Ustvari nov vprašalnik", "Create new form" : "Ustvari nov vprašalnik",
"An error occurred while loading the forms list" : "Med nalaganjem seznama vprašalnikov je prišlo do napake",
"Unable to create a new form" : "Novega vprašalnika ni mogoče ustvariti",
"Responses" : "Odzivi",
"Clone form" : "Kloniraj vprašalnik",
"Delete form" : "Izbriši vprašalnik", "Delete form" : "Izbriši vprašalnik",
"Form link copied" : "Povezava vprašalnika je kopirana",
"Cannot copy, please copy the link manually" : "Povezave ni mogoče kopirati. Storite to ročno.", "Cannot copy, please copy the link manually" : "Povezave ni mogoče kopirati. Storite to ročno.",
"Copy share link" : "Kopiraj povezavo souporabe",
"Are you sure you want to delete {title}?" : "Ali ste prepričani, da želite izbrisati vprašalnik {title}?",
"Delete answer" : "Izbriši odgovor",
"There was an issue deleting this option" : "Prišlo je do napake med brisanjem možnosti",
"Error while saving the answer" : "Prišlo je do napake med shranjevanjem odgovora",
"Question number {index}" : "Vprašanje številka {index}",
"Drag to reorder the questions" : "Potegnite vprašanja in jih preuredite",
"Question title" : "Naslov vprašanja",
"Title of question number {index}" : "Naslov vprašanja številka {index}",
"Mandatory" : "Obvezno",
"Delete question" : "Izbriši vprašanje",
"A long answer for the question “{text}”" : "Dolg odgovor na vprašanje »{text}«",
"Long answer text" : "Besedilo dolgega odgovora",
"Add a new answer" : "Dodaj nov odgovor",
"A short answer for the question “{text}”" : "Kratek odgovor na vprašanje »{text}«",
"Short answer text" : "Besedilo kratkega odgovora",
"Delete this response" : "Izbriši ta odziv",
"User or group name …" : "Ime uporabnika oziroma skupine ...",
"No recommendations. Start typing." : "Ni priporočil; začnite vpisovati",
"Searching …" : "Poteka iskanje ...", "Searching …" : "Poteka iskanje ...",
"No elements found." : "Ni najdenih predmetov", "No elements found." : "Ni najdenih predmetov",
"Group" : "Skupina", "Group" : "Skupina",
"Loading {title} …" : "Poteka nalaganje {title} …",
"Toggle settings" : "Preklopi nastavitve",
"Form title" : "Naslov vprašalnika",
"Description" : "Opis", "Description" : "Opis",
"Mandatory questions" : "Obvezna vprašanja",
"Add a question" : "Dodaj vprašanje",
"There was an error while adding the new question" : "Prišlo je do napake med dodajanjem novega vprašanja",
"There was an error while removing the question" : "Prišlo je do napake med odstranjevanjem vprašanja",
"Error while saving form" : "Prišlo je do napake med shranjevanjem vprašalnika",
"Loading responses …" : "Poteka nalaganje odzivov ...",
"Back to questions" : "Nazaj na vprašanja",
"Responses for {title}" : "Odzivi za vprašalnik {title}",
"Export to CSV" : "Izvozi v datoteko CSV",
"Options" : "Možnosti", "Options" : "Možnosti",
"Delete all responses" : "Izbriši vse odzive",
"No responses yet" : "Ni še odzivov",
"Settings" : "Nastavitve", "Settings" : "Nastavitve",
"Anonymous responses" : "Anonimni odzivi",
"Set expiration date" : "Nastavi datum preteka", "Set expiration date" : "Nastavi datum preteka",
"Expiration date" : "Datum preteka", "Expiration date" : "Datum preteka",
"Sharing" : "Souporaba", "Sharing" : "Souporaba",
"Share link" : "Povezava za souporabo", "Share link" : "Povezava za souporabo",
"Submit" : "Pošlji" "Submit" : "Pošlji",
"Submit form" : "Objavi obrazec",
"Submitting form …" : "Poteka objavljanje obrazca ...",
"Error while saving question" : "Prišlo je do napake med shranjevanjem vprašanja",
"Multiple choice" : "Izbirno vprašanje",
"Checkboxes" : "Izbirna polja",
"Short answer" : "Kratek odgovor",
"Long text" : "Dolg odgovor",
"Form expired" : "Vprašalnik je potekel",
"Form not found" : "Vprašalnika ni mogoče najti",
"This form does not exist" : "Ta vprašalnik ne obstaja"
},"pluralForm" :"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);" },"pluralForm" :"nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3);"
} }

View file

@ -7,4 +7,4 @@ OC.L10N.register(
"Sharing" : "ھەمبەھىر", "Sharing" : "ھەمبەھىر",
"Share link" : "Share link" "Share link" : "Share link"
}, },
"nplurals=1; plural=0;"); "nplurals=2; plural=(n != 1);");

View file

@ -4,5 +4,5 @@
"Settings" : "تەڭشەكلەر", "Settings" : "تەڭشەكلەر",
"Sharing" : "ھەمبەھىر", "Sharing" : "ھەمبەھىر",
"Share link" : "Share link" "Share link" : "Share link"
},"pluralForm" :"nplurals=1; plural=0;" },"pluralForm" :"nplurals=2; plural=(n != 1);"
} }

View file

@ -12,6 +12,7 @@ OC.L10N.register(
"Set expiration date" : "Встановити термін дії", "Set expiration date" : "Встановити термін дії",
"Expiration date" : "Термін дії", "Expiration date" : "Термін дії",
"Sharing" : "Поділитись", "Sharing" : "Поділитись",
"Share link" : "Поширити посилання" "Share link" : "Поширити посилання",
"Submit" : "Гаразд"
}, },
"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);"); "nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);");

View file

@ -10,6 +10,7 @@
"Set expiration date" : "Встановити термін дії", "Set expiration date" : "Встановити термін дії",
"Expiration date" : "Термін дії", "Expiration date" : "Термін дії",
"Sharing" : "Поділитись", "Sharing" : "Поділитись",
"Share link" : "Поширити посилання" "Share link" : "Поширити посилання",
"Submit" : "Гаразд"
},"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);" },"pluralForm" :"nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);"
} }

View file

@ -648,16 +648,8 @@ class ApiController extends Controller {
$submissions[] = $submission; $submissions[] = $submission;
} }
// Load question-texts, including deleted ones. // Load currently active questions
try { $questions = $this->formsService->getQuestions($form->getId());
$questionEntities = $this->questionMapper->findByForm($form->getId());
} catch (DoesNotExistException $e) {
//handle silently
}
$questions = [];
foreach ($questionEntities as $questionEntity) {
$questions[] = $questionEntity->read();
}
$response = [ $response = [
'submissions' => $submissions, 'submissions' => $submissions,

View file

@ -85,7 +85,12 @@ class FormsService {
$this->currentUser = $userSession->getUser(); $this->currentUser = $userSession->getUser();
} }
/**
* Load options corresponding to question
*
* @param integer $questionId
* @return array
*/
public function getOptions(int $questionId): array { public function getOptions(int $questionId): array {
$optionList = []; $optionList = [];
try { try {
@ -100,6 +105,12 @@ class FormsService {
} }
} }
/**
* Load questions corresponding to form
*
* @param integer $formId
* @return array
*/
public function getQuestions(int $formId): array { public function getQuestions(int $formId): array {
$questionList = []; $questionList = [];
try { try {

172
package-lock.json generated
View file

@ -2772,11 +2772,12 @@
} }
}, },
"@nextcloud/auth": { "@nextcloud/auth": {
"version": "1.2.3", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@nextcloud/auth/-/auth-1.3.0.tgz",
"integrity": "sha512-SN0g1nyflt2H34zkCFflOky/h0r9DNHb7T8l/JILyFTCoL8f+f67V2Q4jLLfyapEXgq0b3xG7p8FtrBX5/JhWA==", "integrity": "sha512-GfwRM9W7hat4psNdAt74UHEV+drEXQ53klCVp6JpON66ZLPeK5eJ1LQuiQDkpUxZpqNeaumXjiB98h5cug/uQw==",
"requires": { "requires": {
"@nextcloud/event-bus": "^1.1.3", "@nextcloud/event-bus": "^1.1.3",
"@nextcloud/typings": "^0.2.2",
"core-js": "^3.6.4" "core-js": "^3.6.4"
} }
}, },
@ -2808,9 +2809,9 @@
"dev": true "dev": true
}, },
"@nextcloud/dialogs": { "@nextcloud/dialogs": {
"version": "1.3.1", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-1.4.0.tgz",
"integrity": "sha512-7mr47trvaqWPcI4agSbfapbBdcSlG/2VJ0cW4MB6HXpgzHUQFpEpHRKTpucR1sU0FJTlOVb7irmJ4aH4jZJ8pg==", "integrity": "sha512-Rx4x+al/sy+vXu2p3qvEuVeeUDm5JVwa84S21Hxa+pDV3Pd93E2dJGWlZ6h++5fSXbee1sDX9t957B20kYiP3Q==",
"requires": { "requires": {
"core-js": "^3.6.4", "core-js": "^3.6.4",
"toastify-js": "^1.7.0" "toastify-js": "^1.7.0"
@ -2832,19 +2833,27 @@
} }
}, },
"@nextcloud/event-bus": { "@nextcloud/event-bus": {
"version": "1.1.4", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-1.1.4.tgz", "resolved": "https://registry.npmjs.org/@nextcloud/event-bus/-/event-bus-1.2.0.tgz",
"integrity": "sha512-It27KzmUaSQ7w22nHFwOn8XgeVG0HYYOSNG9gs4UkP5VqcZ16m4ydt3GkMpWcyFec4OUjJc+yf7omRc3pNxsSw==", "integrity": "sha512-pNS0R6Mvgj4WnbJQ8LYjxRjCbRndpwjHNyZYm0zl8U71gbHsUvQIIzTdW7WYg6Nz/FjAlrdmDXJDFLh1DDcIFA==",
"requires": { "requires": {
"@types/semver": "^6.2.1", "@types/semver": "^7.1.0",
"core-js": "^3.6.2", "core-js": "^3.6.2",
"semver": "^6.3.0" "semver": "^7.3.2"
}, },
"dependencies": { "dependencies": {
"@types/semver": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.2.0.tgz",
"integrity": "sha512-TbB0A8ACUWZt3Y6bQPstW9QNbhNeebdgLX4T/ZfkrswAfUzRiXrgd9seol+X379Wa589Pu4UEx9Uok0D4RjRCQ==",
"requires": {
"@types/node": "*"
}
},
"semver": { "semver": {
"version": "6.3.0", "version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
} }
} }
}, },
@ -2857,9 +2866,9 @@
} }
}, },
"@nextcloud/l10n": { "@nextcloud/l10n": {
"version": "1.2.3", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.2.3.tgz", "resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.3.0.tgz",
"integrity": "sha512-bd/bp/pk24Sl/Fjj3KJhDsGhc5EI1tIs34+sl4eaBNrj7SHl79K9bAzarxtABx6LSXn6pl6K/YYkKCidqPzuDQ==", "integrity": "sha512-hGk3ag9TU4cb0+ld/wziEsE+CWaS7Rpj6Y6dPv0QVfnqQ7jFCKQ62VHnuk8pFQHkmKMg3HGxkHuojumbukm42w==",
"requires": { "requires": {
"core-js": "^3.6.4", "core-js": "^3.6.4",
"node-gettext": "^3.0.0" "node-gettext": "^3.0.0"
@ -2912,18 +2921,19 @@
} }
}, },
"@nextcloud/router": { "@nextcloud/router": {
"version": "1.0.2", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@nextcloud/router/-/router-1.1.0.tgz",
"integrity": "sha512-9cGPGZx9P3G/piM3vD8/W37DFcjj4UDUIb9BiKdAG3Ir+7+iI0O/kv7SzvVrEU74a1ohp+wmDUiEQjM02cSFRQ==", "integrity": "sha512-iPHpMG9kajw8D+niR4x/d8s/R9RyUNveDsNURgcZryIjIXhAzSZZra55+Y3yInDmLhCFwboj9ZcC/2S6CzoKYA==",
"requires": { "requires": {
"core-js": "3.6.4" "core-js": "^3.6.4"
}, }
"dependencies": { },
"core-js": { "@nextcloud/typings": {
"version": "3.6.4", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", "resolved": "https://registry.npmjs.org/@nextcloud/typings/-/typings-0.2.2.tgz",
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==" "integrity": "sha512-LZrv1VV3vyDaKw4UKMcGM9dvLMpdI2tfMexHf/ixVn6OrsRDsRTbxByWMMdRF2ArHD5Q8RsICa72p6BcG9b80Q==",
} "requires": {
"@types/jquery": "2.0.54"
} }
}, },
"@nextcloud/vue": { "@nextcloud/vue": {
@ -3023,12 +3033,22 @@
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true "dev": true
}, },
"@types/jquery": {
"version": "2.0.54",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-2.0.54.tgz",
"integrity": "sha512-D/PomKwNkDfSKD13DEVQT/pq2TUjN54c6uB341fEZanIzkjfGe7UaFuuaLZbpEiS5j7Wk2MUHAZqZIoECw29lg=="
},
"@types/minimist": { "@types/minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=", "integrity": "sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY=",
"dev": true "dev": true
}, },
"@types/node": {
"version": "14.0.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.11.tgz",
"integrity": "sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg=="
},
"@types/normalize-package-data": { "@types/normalize-package-data": {
"version": "2.4.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz",
@ -3794,22 +3814,10 @@
"postcss-value-parser": "^4.1.0" "postcss-value-parser": "^4.1.0"
}, },
"dependencies": { "dependencies": {
"browserslist": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.12.0.tgz",
"integrity": "sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg==",
"dev": true,
"requires": {
"caniuse-lite": "^1.0.30001043",
"electron-to-chromium": "^1.3.413",
"node-releases": "^1.1.53",
"pkg-up": "^2.0.0"
}
},
"postcss": { "postcss": {
"version": "7.0.30", "version": "7.0.32",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.30.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz",
"integrity": "sha512-nu/0m+NtIzoubO+xdAlwZl/u5S5vi/y6BCsoL8D+8IxsD3XvBS8X4YEADNIVXKVuQvduiucnRv+vPIqj56EGMQ==", "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
"dev": true, "dev": true,
"requires": { "requires": {
"chalk": "^2.4.2", "chalk": "^2.4.2",
@ -5194,9 +5202,9 @@
"dev": true "dev": true
}, },
"entities": { "entities": {
"version": "2.0.2", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/entities/-/entities-2.0.2.tgz", "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.3.tgz",
"integrity": "sha512-dmD3AvJQBUjKpcNkoqr+x+IF0SdRtPz9Vk0uTy4yWqga9ibB6s4v++QFWNohjiUGoMlF552ZvNyXDxz5iW0qmw==", "integrity": "sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==",
"dev": true "dev": true
} }
} }
@ -7305,9 +7313,9 @@
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
}, },
"globby": { "globby": {
"version": "11.0.0", "version": "11.0.1",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.0.tgz", "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz",
"integrity": "sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg==", "integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"array-union": "^2.1.0", "array-union": "^2.1.0",
@ -7319,9 +7327,9 @@
}, },
"dependencies": { "dependencies": {
"ignore": { "ignore": {
"version": "5.1.6", "version": "5.1.8",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.6.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
"integrity": "sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA==", "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
"dev": true "dev": true
} }
} }
@ -8716,9 +8724,9 @@
} }
}, },
"merge2": { "merge2": {
"version": "1.3.0", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.3.0.tgz", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-2j4DAdlBOkiSZIsaXk4mTE3sRS02yBHAtfy127xRV3bQUFqXkjHCHLW6Scv7DwNRbIWNHH8zpnz9zMaKXIdvYw==", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true "dev": true
}, },
"micromatch": { "micromatch": {
@ -8763,9 +8771,9 @@
"dev": true "dev": true
}, },
"min-indent": { "min-indent": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true "dev": true
}, },
"minimalistic-assert": { "minimalistic-assert": {
@ -9750,9 +9758,9 @@
}, },
"dependencies": { "dependencies": {
"postcss": { "postcss": {
"version": "7.0.30", "version": "7.0.32",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.30.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz",
"integrity": "sha512-nu/0m+NtIzoubO+xdAlwZl/u5S5vi/y6BCsoL8D+8IxsD3XvBS8X4YEADNIVXKVuQvduiucnRv+vPIqj56EGMQ==", "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
"dev": true, "dev": true,
"requires": { "requires": {
"chalk": "^2.4.2", "chalk": "^2.4.2",
@ -9788,9 +9796,9 @@
}, },
"dependencies": { "dependencies": {
"postcss": { "postcss": {
"version": "7.0.30", "version": "7.0.32",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.30.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz",
"integrity": "sha512-nu/0m+NtIzoubO+xdAlwZl/u5S5vi/y6BCsoL8D+8IxsD3XvBS8X4YEADNIVXKVuQvduiucnRv+vPIqj56EGMQ==", "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
"dev": true, "dev": true,
"requires": { "requires": {
"chalk": "^2.4.2", "chalk": "^2.4.2",
@ -11338,14 +11346,14 @@
"dev": true "dev": true
}, },
"stylelint": { "stylelint": {
"version": "13.5.0", "version": "13.6.0",
"resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.5.0.tgz", "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-13.6.0.tgz",
"integrity": "sha512-+Jy7ieKAWKTf2tmcAE7jgScxH39Urb87i0bjK/enScFaGWWaFn4kAPwepGOSk2b7CLUDVt/O6kwA0x0p/V7moQ==", "integrity": "sha512-55gG2pNjVr183JJM/tlr3KAua6vTVX7Ho/lgKKuCIWszTZ1gmrXjX4Wok53SI8wRYFPbwKAcJGULQ77OJxTcNw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@stylelint/postcss-css-in-js": "^0.37.1", "@stylelint/postcss-css-in-js": "^0.37.1",
"@stylelint/postcss-markdown": "^0.36.1", "@stylelint/postcss-markdown": "^0.36.1",
"autoprefixer": "^9.7.6", "autoprefixer": "^9.8.0",
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"cosmiconfig": "^6.0.0", "cosmiconfig": "^6.0.0",
@ -11354,10 +11362,10 @@
"file-entry-cache": "^5.0.1", "file-entry-cache": "^5.0.1",
"get-stdin": "^8.0.0", "get-stdin": "^8.0.0",
"global-modules": "^2.0.0", "global-modules": "^2.0.0",
"globby": "^11.0.0", "globby": "^11.0.1",
"globjoin": "^0.1.4", "globjoin": "^0.1.4",
"html-tags": "^3.1.0", "html-tags": "^3.1.0",
"ignore": "^5.1.4", "ignore": "^5.1.8",
"import-lazy": "^4.0.0", "import-lazy": "^4.0.0",
"imurmurhash": "^0.1.4", "imurmurhash": "^0.1.4",
"known-css-properties": "^0.19.0", "known-css-properties": "^0.19.0",
@ -11368,7 +11376,7 @@
"meow": "^7.0.1", "meow": "^7.0.1",
"micromatch": "^4.0.2", "micromatch": "^4.0.2",
"normalize-selector": "^0.2.0", "normalize-selector": "^0.2.0",
"postcss": "^7.0.30", "postcss": "^7.0.32",
"postcss-html": "^0.36.0", "postcss-html": "^0.36.0",
"postcss-less": "^3.1.4", "postcss-less": "^3.1.4",
"postcss-media-query-parser": "^0.2.3", "postcss-media-query-parser": "^0.2.3",
@ -11376,7 +11384,7 @@
"postcss-resolve-nested-selector": "^0.1.1", "postcss-resolve-nested-selector": "^0.1.1",
"postcss-safe-parser": "^4.0.2", "postcss-safe-parser": "^4.0.2",
"postcss-sass": "^0.4.4", "postcss-sass": "^0.4.4",
"postcss-scss": "^2.0.0", "postcss-scss": "^2.1.1",
"postcss-selector-parser": "^6.0.2", "postcss-selector-parser": "^6.0.2",
"postcss-syntax": "^0.36.2", "postcss-syntax": "^0.36.2",
"postcss-value-parser": "^4.1.0", "postcss-value-parser": "^4.1.0",
@ -11389,7 +11397,7 @@
"sugarss": "^2.0.0", "sugarss": "^2.0.0",
"svg-tags": "^1.0.0", "svg-tags": "^1.0.0",
"table": "^5.4.6", "table": "^5.4.6",
"v8-compile-cache": "^2.1.0", "v8-compile-cache": "^2.1.1",
"write-file-atomic": "^3.0.3" "write-file-atomic": "^3.0.3"
}, },
"dependencies": { "dependencies": {
@ -11497,9 +11505,9 @@
"dev": true "dev": true
}, },
"ignore": { "ignore": {
"version": "5.1.6", "version": "5.1.8",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.6.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
"integrity": "sha512-cgXgkypZBcCnOgSihyeqbo6gjIaIyDqPQB7Ra4vhE9m6kigdGoQDMHjviFhRZo3IMlRy6yElosoviMs5YxZXUA==", "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
"dev": true "dev": true
}, },
"indent-string": { "indent-string": {
@ -11584,9 +11592,9 @@
"dev": true "dev": true
}, },
"postcss": { "postcss": {
"version": "7.0.30", "version": "7.0.32",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.30.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz",
"integrity": "sha512-nu/0m+NtIzoubO+xdAlwZl/u5S5vi/y6BCsoL8D+8IxsD3XvBS8X4YEADNIVXKVuQvduiucnRv+vPIqj56EGMQ==", "integrity": "sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==",
"dev": true, "dev": true,
"requires": { "requires": {
"chalk": "^2.4.2", "chalk": "^2.4.2",
@ -11775,9 +11783,9 @@
"dev": true "dev": true
}, },
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.1.0", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz",
"integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", "integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==",
"dev": true "dev": true
}, },
"yargs-parser": { "yargs-parser": {

View file

@ -22,14 +22,14 @@
"stylelint:fix": "stylelint css/*.css css/*.scss src/**/*.vue --fix" "stylelint:fix": "stylelint css/*.css css/*.scss src/**/*.vue --fix"
}, },
"dependencies": { "dependencies": {
"@nextcloud/auth": "^1.2.3", "@nextcloud/auth": "^1.3.0",
"@nextcloud/axios": "^1.3.2", "@nextcloud/axios": "^1.3.2",
"@nextcloud/dialogs": "^1.3.1", "@nextcloud/dialogs": "^1.4.0",
"@nextcloud/event-bus": "^1.1.4", "@nextcloud/event-bus": "^1.2.0",
"@nextcloud/initial-state": "^1.1.2", "@nextcloud/initial-state": "^1.1.2",
"@nextcloud/l10n": "^1.2.3", "@nextcloud/l10n": "^1.3.0",
"@nextcloud/moment": "^1.1.1", "@nextcloud/moment": "^1.1.1",
"@nextcloud/router": "^1.0.2", "@nextcloud/router": "^1.1.0",
"@nextcloud/vue": "^2.0.0", "@nextcloud/vue": "^2.0.0",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"crypto-js": "^4.0.0", "crypto-js": "^4.0.0",
@ -72,7 +72,7 @@
"file-loader": "^6.0.0", "file-loader": "^6.0.0",
"node-sass": "^4.14.1", "node-sass": "^4.14.1",
"sass-loader": "^8.0.2", "sass-loader": "^8.0.2",
"stylelint": "^13.5.0", "stylelint": "^13.6.0",
"stylelint-config-recommended-scss": "^4.2.0", "stylelint-config-recommended-scss": "^4.2.0",
"stylelint-scss": "^3.17.2", "stylelint-scss": "^3.17.2",
"stylelint-webpack-plugin": "^2.0.0", "stylelint-webpack-plugin": "^2.0.0",

View file

@ -184,7 +184,7 @@ export default {
} }
}, },
async copyLink() { async copyLink(event) {
if (this.$clipboard(this.formLink)) { if (this.$clipboard(this.formLink)) {
this.copySuccess = true this.copySuccess = true
this.copied = true this.copied = true
@ -193,6 +193,9 @@ export default {
this.copied = true this.copied = true
console.debug('Not possible to copy share link') console.debug('Not possible to copy share link')
} }
// Set back focus as clipboard removes focus
event.target.focus()
setTimeout(() => { setTimeout(() => {
this.copySuccess = false this.copySuccess = false
this.copied = false this.copied = false

View file

@ -118,21 +118,8 @@ export default {
// Dismiss delete key action // Dismiss delete key action
e.preventDefault() e.preventDefault()
const answer = Object.assign({}, this.answer)
const index = this.index
if (!answer.local) { this.$emit('delete', this.answer.id)
// let's not await, deleting in background
axios.delete(generateUrl('/apps/forms/api/v1/option/{id}', { id: this.answer.id }))
.catch(error => {
showError(t('forms', 'There was an issue deleting this option'))
console.error(error)
// restore option
this.$emit('restore', answer, index)
})
}
this.$emit('delete', answer.id, index)
}, },
/** /**

View file

@ -40,7 +40,8 @@
:maxlength="maxStringLengths.answerText" :maxlength="maxStringLengths.answerText"
minlength="1" minlength="1"
@input="onInput" @input="onInput"
@keydown="autoSizeText" /> @keypress="autoSizeText"
@keydown.ctrl.enter="onKeydownCtrlEnter" />
</div> </div>
</Question> </Question>
</template> </template>
@ -74,6 +75,9 @@ export default {
textarea.style.cssText = 'height:auto; padding:0' textarea.style.cssText = 'height:auto; padding:0'
textarea.style.cssText = `height: ${textarea.scrollHeight + 20}px` textarea.style.cssText = `height: ${textarea.scrollHeight + 20}px`
}, },
onKeydownCtrlEnter(event) {
this.$emit('keydown', event)
},
}, },
} }
</script> </script>

View file

@ -46,7 +46,8 @@
:name="`${id}-answer`" :name="`${id}-answer`"
:required="isRequired(answer.id)" :required="isRequired(answer.id)"
:type="isUnique ? 'radio' : 'checkbox'" :type="isUnique ? 'radio' : 'checkbox'"
@change="onChange($event, answer.id)"> @change="onChange($event, answer.id)"
@keydown.enter.exact.prevent="onKeydownEnter">
<label v-if="!edit" <label v-if="!edit"
ref="label" ref="label"
:for="`${id}-answer-${answer.id}`" :for="`${id}-answer-${answer.id}`"
@ -61,9 +62,8 @@
:index="index" :index="index"
:max-option-length="maxStringLengths.optionText" :max-option-length="maxStringLengths.optionText"
@add="addNewEntry" @add="addNewEntry"
@delete="deleteAnswer" @delete="deleteOption"
@update:answer="updateAnswer" @update:answer="updateAnswer" />
@restore="restoreAnswer" />
</template> </template>
<li v-if="(edit && !isLastEmpty) || hasNoAnswer" class="question__item"> <li v-if="(edit && !isLastEmpty) || hasNoAnswer" class="question__item">
@ -81,6 +81,10 @@
</template> </template>
<script> <script>
import { generateUrl } from '@nextcloud/router'
import { showError } from '@nextcloud/dialogs'
import axios from '@nextcloud/axios'
import AnswerInput from './AnswerInput' import AnswerInput from './AnswerInput'
import QuestionMixin from '../../mixins/QuestionMixin' import QuestionMixin from '../../mixins/QuestionMixin'
import GenRandomId from '../../utils/GenRandomId' import GenRandomId from '../../utils/GenRandomId'
@ -118,9 +122,18 @@ export default {
watch: { watch: {
edit(edit) { edit(edit) {
// When leaving edit mode, filter and delete empty options
if (!edit) { if (!edit) {
// Filter out empty options and update question const options = this.options.filter(option => {
this.$emit('update:options', this.options.filter(answer => !!answer.text)) if (!option.text) {
this.deleteOptionFromDatabase(option)
return false
}
return true
})
// update parent
this.updateOptions(options)
} }
}, },
}, },
@ -224,39 +237,65 @@ export default {
}, },
/** /**
* Restore an answer locally * Restore an option locally
* *
* @param {Object} answer the answer * @param {Object} option the option
* @param {number} index the answer index in this.options * @param {number} index the options index in this.options
*/ */
restoreAnswer(answer, index) { restoreOption(option, index) {
const options = this.options.slice() const options = this.options.slice()
options.splice(index, 0, answer) options.splice(index, 0, option)
this.updateOptions(options) this.updateOptions(options)
this.focusIndex(index) this.focusIndex(index)
}, },
/** /**
* Delete an answer locally * Delete an option
* *
* @param {number} id the answer is * @param {number} id the options id
* @param {number} index the answer index in this.options
*/ */
deleteAnswer(id, index) { deleteOption(id) {
// Remove entry // Remove entry
const options = this.options.slice() const options = this.options.slice()
const optionIndex = options.findIndex(option => option.id === id) const optionIndex = options.findIndex(option => option.id === id)
const option = Object.assign({}, this.options[optionIndex])
// delete locally
options.splice(optionIndex, 1) options.splice(optionIndex, 1)
// delete from Db
this.deleteOptionFromDatabase(option)
// Update question // Update question
this.updateOptions(options) this.updateOptions(options)
this.$nextTick(() => { this.$nextTick(() => {
this.focusIndex(index + 1) this.focusIndex(optionIndex)
}) })
}, },
/**
* Delete the option from Db in background.
* Restore option if delete not possible
*
* @param {Object} option The option to delete
*/
deleteOptionFromDatabase(option) {
const optionIndex = this.options.findIndex(opt => opt.id === option.id)
if (!option.local) {
// let's not await, deleting in background
axios.delete(generateUrl('/apps/forms/api/v1/option/{id}', { id: option.id }))
.catch(error => {
showError(t('forms', 'There was an issue deleting this option'))
console.error(error)
// restore option
this.restoreOption(option, optionIndex)
})
}
},
/** /**
* Focus the input matching the index * Focus the input matching the index
* *

View file

@ -40,7 +40,8 @@
:maxlength="maxStringLengths.answerText" :maxlength="maxStringLengths.answerText"
minlength="1" minlength="1"
type="text" type="text"
@input="onInput"> @input="onInput"
@keydown.enter.exact.prevent="onKeydownEnter">
</div> </div>
</Question> </Question>
</template> </template>

View file

@ -23,9 +23,9 @@
<template> <template>
<div class="answer"> <div class="answer">
<h4 class="question-text"> <h4 class="question-text">
{{ question.text }} {{ questionText }}
</h4> </h4>
<p>{{ answer.text }}</p> <p>{{ answerText }}</p>
</div> </div>
</template> </template>
@ -34,12 +34,12 @@ export default {
name: 'Answer', name: 'Answer',
props: { props: {
answer: { answerText: {
type: Object, type: String,
required: true, required: true,
}, },
question: { questionText: {
type: Object, type: String,
required: true, required: true,
}, },
}, },

View file

@ -35,10 +35,10 @@
</p> </p>
<Answer <Answer
v-for="answer in squashedAnswers" v-for="question in answeredQuestions"
:key="answer.questionId" :key="question.id"
:answer="answer" :answer-text="question.squashedAnswers"
:question="questionToAnswer(answer.questionId)" /> :question-text="question.text" />
</div> </div>
</template> </template>
@ -70,30 +70,37 @@ export default {
}, },
computed: { computed: {
// Format submission-timestamp to DateTime
submissionDateTime() { submissionDateTime() {
return moment(this.submission.timestamp, 'X').format('LLLL') return moment(this.submission.timestamp, 'X').format('LLLL')
}, },
squashedAnswers() {
const squashedArray = []
this.submission.answers.forEach(answer => { /**
const index = squashedArray.findIndex(ansSq => ansSq.questionId === answer.questionId) * Join answered Questions with corresponding answers.
if (index > -1) { * Multiple answers to a question are squashed into one string.
squashedArray[index].text = squashedArray[index].text.concat('; ' + answer.text) * @returns {Array}
} else { */
squashedArray.push(answer) answeredQuestions() {
const answeredQuestionsArray = []
this.questions.forEach(question => {
const answers = this.submission.answers.filter(answer => answer.questionId === question.id)
if (!answers.length) {
return // no answers, go to next question
} }
}) const squashedAnswers = answers.map(answer => answer.text).join('; ')
return squashedArray answeredQuestionsArray.push({
'id': question.id,
'text': question.text,
'squashedAnswers': squashedAnswers,
})
})
return answeredQuestionsArray
}, },
}, },
methods: { methods: {
questionToAnswer(questionId) {
return this.questions.find(question => question.id === questionId)
},
onDelete() { onDelete() {
this.$emit('delete') this.$emit('delete')
}, },

View file

@ -136,6 +136,15 @@ export default {
this.$emit('delete') this.$emit('delete')
}, },
/**
* Don't automatically submit form on Enter, parent will handle that
* To be called with prevent: @keydown.enter.prevent="onKeydownEnter"
* @param {Object} event The fired event
*/
onKeydownEnter(event) {
this.$emit('keydown', event)
},
/** /**
* Focus the first focusable element * Focus the first focusable element
*/ */

View file

@ -71,10 +71,10 @@
<section v-else> <section v-else>
<Submission <Submission
v-for="submission in submissions" v-for="submission in form.submissions"
:key="submission.id" :key="submission.id"
:submission="submission" :submission="submission"
:questions="questions" :questions="form.questions"
@delete="deleteSubmission(submission.id)" /> @delete="deleteSubmission(submission.id)" />
</section> </section>
</AppContent> </AppContent>
@ -117,14 +117,12 @@ export default {
data() { data() {
return { return {
loadingResults: true, loadingResults: true,
submissions: [],
questions: [],
} }
}, },
computed: { computed: {
noSubmissions() { noSubmissions() {
return this.submissions && this.submissions.length === 0 return this.form.submissions?.length === 0
}, },
/** /**
@ -161,13 +159,15 @@ export default {
}) })
}, },
copyShareLink() { copyShareLink(event) {
const $formLink = window.location.protocol + '//' + window.location.host + generateUrl(`/apps/forms/${this.form.hash}`) const $formLink = window.location.protocol + '//' + window.location.host + generateUrl(`/apps/forms/${this.form.hash}`)
if (this.$clipboard($formLink)) { if (this.$clipboard($formLink)) {
showSuccess(t('forms', 'Form link copied')) showSuccess(t('forms', 'Form link copied'))
} else { } else {
showError(t('forms', 'Cannot copy, please copy the link manually')) showError(t('forms', 'Cannot copy, please copy the link manually'))
} }
// Set back focus as clipboard removes focus
event.target.focus()
}, },
async loadFormResults() { async loadFormResults() {
@ -178,9 +178,8 @@ export default {
const response = await axios.get(generateUrl('/apps/forms/api/v1/submissions/{hash}', { const response = await axios.get(generateUrl('/apps/forms/api/v1/submissions/{hash}', {
hash: this.form.hash, hash: this.form.hash,
})) }))
this.submissions = response.data.submissions this.form.submissions = response.data.submissions
this.questions = response.data.questions this.form.questions = response.data.questions
console.debug(this.submissions)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
showError(t('forms', 'There was an error while loading results')) showError(t('forms', 'There was an error while loading results'))
@ -194,8 +193,8 @@ export default {
try { try {
await axios.delete(generateUrl('/apps/forms/api/v1/submission/{id}', { id })) await axios.delete(generateUrl('/apps/forms/api/v1/submission/{id}', { id }))
const index = this.submissions.findIndex(search => search.id === id) const index = this.form.submissions.findIndex(search => search.id === id)
this.submissions.splice(index, 1) this.form.submissions.splice(index, 1)
} catch (error) { } catch (error) {
console.error(error) console.error(error)
showError(t('forms', 'There was an error while removing this response')) showError(t('forms', 'There was an error while removing this response'))
@ -212,7 +211,7 @@ export default {
this.loadingResults = true this.loadingResults = true
try { try {
await axios.delete(generateUrl('/apps/forms/api/v1/submissions/{formId}', { formId: this.form.id })) await axios.delete(generateUrl('/apps/forms/api/v1/submissions/{formId}', { formId: this.form.id }))
this.submissions = [] this.form.submissions = []
} catch (error) { } catch (error) {
console.error(error) console.error(error)
showError(t('forms', 'There was an error while removing responses')) showError(t('forms', 'There was an error while removing responses'))
@ -229,19 +228,20 @@ export default {
}) })
const formattedSubmissions = [] const formattedSubmissions = []
this.submissions.forEach(submission => { this.form.submissions.forEach(submission => {
const formattedSubmission = { const formattedSubmission = {
userDisplayName: submission.userDisplayName, userDisplayName: submission.userDisplayName,
timestamp: moment(submission.timestamp, 'X').format('L LT'), timestamp: moment(submission.timestamp, 'X').format('L LT'),
} }
submission.answers.forEach(answer => { this.form.questions.forEach(question => {
const questionText = this.questions.find(question => question.id === answer.questionId).text const questionText = question.text
if (questionText in formattedSubmission) { const answers = submission.answers.filter(answer => answer.questionId === question.id)
formattedSubmission[questionText] = formattedSubmission[questionText].concat('; ').concat(answer.text) if (!answers.length) {
} else { return // no answers, go to next question
formattedSubmission[questionText] = answer.text
} }
const squashedAnswers = answers.map(answer => answer.text).join('; ')
formattedSubmission[questionText] = squashedAnswers
}) })
formattedSubmissions.push(formattedSubmission) formattedSubmissions.push(formattedSubmission)
}) })

View file

@ -323,12 +323,14 @@ export default {
return datetime < moment().add(1, 'hour').toDate() return datetime < moment().add(1, 'hour').toDate()
}, },
copyShareLink() { copyShareLink(event) {
if (this.$clipboard(this.shareLink)) { if (this.$clipboard(this.shareLink)) {
showSuccess(t('forms', 'Form link copied')) showSuccess(t('forms', 'Form link copied'))
} else { } else {
showError(t('forms', 'Cannot copy, please copy the link manually')) showError(t('forms', 'Cannot copy, please copy the link manually'))
} }
// Set back focus as clipboard removes focus
event.target.focus()
}, },
}, },
} }

View file

@ -38,7 +38,9 @@
</header> </header>
<!-- Questions list --> <!-- Questions list -->
<form v-if="!loading && !success" @submit.prevent="onSubmit"> <form v-if="!loading && !success"
ref="form"
@submit.prevent="onSubmit">
<ul> <ul>
<Questions <Questions
:is="answerTypes[question.type].component" :is="answerTypes[question.type].component"
@ -50,9 +52,12 @@
:index="index + 1" :index="index + 1"
:max-string-lengths="maxStringLengths" :max-string-lengths="maxStringLengths"
v-bind="question" v-bind="question"
:values.sync="answers[question.id]" /> :values.sync="answers[question.id]"
@keydown.enter="onKeydownEnter"
@keydown.ctrl.enter="onKeydownCtrlEnter" />
</ul> </ul>
<input class="primary" <input ref="submitButton"
class="primary"
type="submit" type="submit"
:value="t('forms', 'Submit')" :value="t('forms', 'Submit')"
:disabled="loading" :disabled="loading"
@ -154,6 +159,27 @@ export default {
}, },
methods: { methods: {
/**
* On Enter, focus next form-element
* Last form element is the submit button, the form submits on enter then
* @param {Object} event The fired event.
*/
onKeydownEnter(event) {
const formInputs = Array.from(this.$refs.form)
const sourceInputIndex = formInputs.findIndex(input => input === event.originalTarget)
// Focus next form element
formInputs[sourceInputIndex + 1].focus()
},
/**
* Ctrl+Enter typically fires submit on forms.
* Some inputs do automatically, while some need explicit handling
*/
onKeydownCtrlEnter() {
// Using button-click event to not bypass validity-checks and use our specified behaviour
this.$refs.submitButton.click()
},
/** /**
* Submit the form after the browser validated it 🚀 * Submit the form after the browser validated it 🚀
*/ */