Merge pull request #237 from nextcloud/enh/navigation

Cleanup and change routes
This commit is contained in:
John Molakvoæ 2020-03-26 17:58:17 +01:00 committed by GitHub
commit 2be165dd47
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 1466 additions and 1072 deletions

39
.gitignore vendored
View file

@ -1,23 +1,26 @@
.DS_Store
.sass-cache/
.project/
.idea/
build/
css/*.map
js/*forms.*
js/chunks/
js/*.svg
nbproject/
node_modules/
npm-debug.log
Thumbs.db
yarn-error.log
*.cmd
*.lock
*.iml
vendor/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
composer.lock
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.suo
.svn
vendor/
.marginalia
build/
coverage/
cypress/screenshots
cypress/snapshots
# Compiled files
js/

View file

@ -24,14 +24,15 @@
return [
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
['name' => 'page#goto_form', 'url' => '/form/{hash}', 'verb' => 'GET'],
['name' => 'page#create_form', 'url' => '/new', 'verb' => 'GET'],
['name' => 'page#edit_form', 'url' => '/edit/{hash}', 'verb' => 'GET'],
['name' => 'page#clone_form', 'url' => '/clone/{hash}', 'verb' => 'GET'],
['name' => 'page#getResult', 'url' => '/results/{id}', 'verb' => 'GET'],
// Before /{hash} to avoid conflict
['name' => 'page#createForm', 'url' => '/new', 'verb' => 'GET'],
['name' => 'page#editForm', 'url' => '/{hash}/edit/', 'verb' => 'GET'],
['name' => 'page#cloneForm', 'url' => '/{hash}/clone/', 'verb' => 'GET'],
['name' => 'page#getResult', 'url' => '/{hash}/results/', 'verb' => 'GET'],
['name' => 'page#goto_form', 'url' => '/{hash}', 'verb' => 'GET'],
['name' => 'page#delete_form', 'url' => '/delete', 'verb' => 'POST'],
['name' => 'page#insert_vote', 'url' => '/insert/vote', 'verb' => 'POST'],
['name' => 'page#search', 'url' => '/search', 'verb' => 'POST'],
['name' => 'page#get_display_name', 'url' => '/get/displayname', 'verb' => 'POST'],
@ -42,10 +43,10 @@ return [
['name' => 'api#get_votes', 'url' => '/get/votes/{formId}', 'verb' => 'GET'],
['name' => 'api#get_shares', 'url' => '/get/shares/{formId}', 'verb' => 'GET'],
['name' => 'api#get_event', 'url' => '/get/event/{formId}', 'verb' => 'GET'],
['name' => 'api#remove_form', 'url' => '/forms/{id}', 'verb' => 'DELETE'],
['name' => 'api#get_forms', 'url' => '/get/forms', 'verb' => 'GET'],
['name' => 'api#newForm', 'url' => 'api/v1/form', 'verb' => 'POST'],
['name' => 'api#deleteForm', 'url' => 'api/v1/form/{id}', 'verb' => 'DELETE'],
['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'],
]

View file

@ -1,33 +0,0 @@
$bg-no: #ffede9;
$bg-maybe: #fcf7e1;
$bg-unvoted: #fff4c8;
$bg-yes: #ebf5d6;
$bg-information: #b19c3e;
$fg-no: #f45573;
$fg-maybe: #f0db98;
$fg-unvoted: #f0db98;
$fg-yes: #49bc49;
// Icon definitions
@include icon-black-white('app', 'forms', 2);
.icon-yes {
@include icon-color('checkmark', 'actions', $fg-yes, 1, true);
}
.icon-comment-yes {
@include icon-color('comment', 'actions', $fg-yes, 1, true);
}
.icon-comment-no {
@include icon-color('comment', 'actions', $fg-no, 1, true);
}
.icon-no {
@include icon-color('close', 'actions', $fg-no, 1, true);
}
.icon-maybe {
@include icon-color('maybe-vote-variant', 'forms', $fg-maybe);
}

23
css/forms.scss Normal file
View file

@ -0,0 +1,23 @@
/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
@import 'icons'

24
css/icons.scss Normal file
View file

@ -0,0 +1,24 @@
// Icon definitions
@include icon-black-white('forms', 'forms', 3);
@include icon-black-white('clone', 'forms', 1);
.icon-yes {
@include icon-color('checkmark', 'actions', $color-success, 1, true);
}
.icon-comment-yes {
@include icon-color('comment', 'actions', $color-success, 1, true);
}
.icon-comment-no {
@include icon-color('comment', 'actions', $color-error, 1, true);
}
.icon-no {
@include icon-color('close', 'actions', $color-error, 1, true);
}
.icon-maybe {
@include icon-color('maybe-vote-variant', 'forms', $color-warning);
}

View file

@ -1,214 +0,0 @@
@import 'colors.scss';
$border_current_user: 2px solid;
$border_user: 1px solid var(--color-border-dark);
$user-column-width: 265px;
#forms-sidebar {
width: 520px;
flex-grow: 0;
flex-shrink: 1;
min-width: 300px;
border-left: 1px solid var(--color-border);
transition: margin-right 300ms;
z-index: 500;
> div,
> ul {
padding: 8px;
}
}
.authorRow {
align-items: center;
.author {
margin-left: 8px;
opacity: 0.5;
flex-grow: 1;
&.external {
margin-right: 33px;
opacity: 1;
> input {
width: 100%;
}
}
}
}
.detailsView {
z-index: 1000 !important;
.close.flex-row {
justify-content: flex-end;
margin: 8px 8px 0 0;
}
.header.flex-row {
flex-direction: row;
flex-grow: 0;
align-items: flex-start;
margin-left: 0;
margin-top: 0;
padding: 0 17px;
}
.formInformation {
width: 220px;
flex-grow: 1;
flex-shrink: 1;
padding-right: 15px;
.authorRow {
.leftLabel {
margin-right: 4px;
}
}
.cloud {
margin: 4px 0;
> span {
color: var(--color-primary-text);
margin: 2px;
padding: 2px 4px;
border-radius: var(--border-radius);
float: left;
text-shadow: 1px 1px var(--color-box-shadow);
background-color: var(--color-loading-light);
}
.open {
background-color: $fg-yes;
}
.expired {
background-color: $fg-no;
}
.information {
background-color: $bg-information;
}
}
}
#expired_info {
margin: 0 15px;
}
.formActions {
display: flex;
flex-direction: column;
margin-right: 15px;
.close {
margin: 15px;
background-position: right top;
height: 30px;
}
> ul > li {
&:focus,
&:hover,
&.active,
a.selected {
&,
> a {
opacity: 1;
box-shadow: inset 4px 0 var(--color-primary);
}
}
> a[class*='icon-'],
> ul > li > a[class*='icon-'],
> a[style*='background-image'],
> ul > li > a[style*='background-image'] {
padding-left: 44px;
}
> a,
> ul > li > a {
background-size: 16px 16px;
background-position: 14px center;
background-repeat: no-repeat;
display: block;
justify-content: space-between;
line-height: 44px;
min-height: 44px;
padding: 0 12px;
overflow: hidden;
box-sizing: border-box;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--color-main-text);
opacity: 0.57;
flex: 1 1 0;
z-index: 100;
}
a,
.app-navigation-entry-deleted {
padding-left: 44px !important;
}
}
}
#configurationsTabView {
.configBox {
padding: 8px 8px;
> .title {
font-weight: bold;
margin-bottom: 4px;
}
> div {
padding-left: 4px;
}
input.hasDatepicker {
margin-left: 17px;
}
&.oneline {
width: 100%;
}
}
}
#commentsTabView {
.newCommentForm div.message:empty:before {
content: attr(data-placeholder);
color: grey;
}
#commentBox {
border: 1px solid var(--color-border-dark);
border-radius: var(--border-radius);
padding: 7px 6px;
margin: 3px 3px 3px 40px;
cursor: text;
}
.comment {
margin-bottom: 30px;
.date {
right: 0;
top: 5px;
opacity: 0.5;
}
}
.message {
margin-left: 40px;
flex-grow: 1;
flex-shrink: 1;
}
.new-comment {
.submitComment {
align-self: last baseline;
width: 30px;
margin: 0;
padding: 7px 9px;
background-color: transparent;
border: none;
opacity: 0.3;
}
.icon-loading-small {
float: left;
margin-top: 10px;
display: none;
}
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 B

View file

@ -1 +1,8 @@
<svg width="32" height="32" version="1.1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="m5 3a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3zm7 1c-0.554 0-1 0.446-1 1v2c0 0.554 0.446 1 1 1h16c0.554 0 1-0.446 1-1v-2c0-0.554-0.446-1-1-1h-16zm-7 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3zm7 1c-0.554 0-1 0.446-1 1v2c0 0.554 0.446 1 1 1h16c0.554 0 1-0.446 1-1v-2c0-0.554-0.446-1-1-1h-16zm-7 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3zm7 1c-0.554 0-1 0.446-1 1v2c0 0.554 0.446 1 1 1h16c0.554 0 1-0.446 1-1v-2c0-0.554-0.446-1-1-1h-16z" fill="#fff" style="paint-order:markers fill stroke"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32">
<circle cx="6" cy="6" r="3" fill="#fff"/>
<circle cx="6" cy="16" r="3" fill="#fff"/>
<circle cx="6" cy="26" r="3" fill="#fff"/>
<rect width="18" height="4" x="11" y="4" rx="1" ry="1" fill="#fff"/>
<rect width="18" height="4" x="11" y="14" rx="1" ry="1" fill="#fff"/>
<rect width="18" height="4" x="11" y="24" rx="1" ry="1" fill="#fff"/>
</svg>

Before

Width:  |  Height:  |  Size: 647 B

After

Width:  |  Height:  |  Size: 420 B

1
img/clone.svg Normal file
View file

@ -0,0 +1 @@
<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M11.8 13.8H2.2V4.2h9.6m1.2 0c0-.67-.53-1.2-1.2-1.2H2.2C1.53 3 1 3.53 1 4.2v9.6c0 .67.53 1.2 1.2 1.2h9.6c.67 0 1.2-.53 1.2-1.2"/><path d="m4.2 1c-0.67 0-1.2 0.54-1.2 1.2h10.8v10.8c0.67 0 1.2-0.53 1.2-1.2v-9.6c0-0.67-0.53-1.2-1.2-1.2z"/></svg>

After

Width:  |  Height:  |  Size: 327 B

View file

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg10"
viewBox="0 0 32 32"
x="0px"
y="0px"
enable-background="new 0 0 595.275 311.111"
width="32"
height="32"
xml:space="preserve"
version="1.1"
sodipodi:docname="favicon-mask.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)"><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1046"
id="namedview15"
showgrid="false"
inkscape:zoom="3.6875"
inkscape:cx="11.825118"
inkscape:cy="3.2468217"
inkscape:window-x="0"
inkscape:window-y="34"
inkscape:window-maximized="1"
inkscape:current-layer="svg10" /><metadata
id="metadata16"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
id="defs14" /><path
d="M 5 0 C 2.23 0 0 2.23 0 5 L 0 27 C 0 29.77 2.23 32 5 32 L 27 32 C 29.77 32 32 29.77 32 27 L 32 5 C 32 2.23 29.77 0 27 0 L 5 0 z M 5 3 A 3 3 0 0 1 8 6 A 3 3 0 0 1 5 9 A 3 3 0 0 1 2 6 A 3 3 0 0 1 5 3 z M 12 4 L 28 4 C 28.554 4 29 4.446 29 5 L 29 7 C 29 7.554 28.554 8 28 8 L 12 8 C 11.446 8 11 7.554 11 7 L 11 5 C 11 4.446 11.446 4 12 4 z M 5 13 A 3 3 0 0 1 8 16 A 3 3 0 0 1 5 19 A 3 3 0 0 1 2 16 A 3 3 0 0 1 5 13 z M 12 14 L 28 14 C 28.554 14 29 14.446 29 15 L 29 17 C 29 17.554 28.554 18 28 18 L 12 18 C 11.446 18 11 17.554 11 17 L 11 15 C 11 14.446 11.446 14 12 14 z M 5 23 A 3 3 0 0 1 8 26 A 3 3 0 0 1 5 29 A 3 3 0 0 1 2 26 A 3 3 0 0 1 5 23 z M 12 24 L 28 24 C 28.554 24 29 24.446 29 25 L 29 27 C 29 27.554 28.554 28 28 28 L 12 28 C 11.446 28 11 27.554 11 27 L 11 25 C 11 24.446 11.446 24 12 24 z "
id="rect2" /></svg>

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View file

@ -1 +0,0 @@
<svg width="128" height="128" enable-background="new 0 0 595.275 311.111" version="1.1" viewBox="0 0 128 128" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="128" height="128" rx="20" ry="20" fill="#0082c9"/><path d="m20 12a12 12 0 0 0-12 12 12 12 0 0 0 12 12 12 12 0 0 0 12-12 12 12 0 0 0-12-12zm28 4c-2.216 0-4 1.784-4 4v8c0 2.216 1.784 4 4 4h64c2.216 0 4-1.784 4-4v-8c0-2.216-1.784-4-4-4zm-28 36a12 12 0 0 0-12 12 12 12 0 0 0 12 12 12 12 0 0 0 12-12 12 12 0 0 0-12-12zm28 4c-2.216 0-4 1.784-4 4v8c0 2.216 1.784 4 4 4h64c2.216 0 4-1.784 4-4v-8c0-2.216-1.784-4-4-4zm-28 36a12 12 0 0 0-12 12 12 12 0 0 0 12 12 12 12 0 0 0 12-12 12 12 0 0 0-12-12zm28 4c-2.216 0-4 1.784-4 4v8c0 2.216 1.784 4 4 4h64c2.216 0 4-1.784 4-4v-8c0-2.216-1.784-4-4-4z" fill="#fff" style="paint-order:markers fill stroke"/></svg>

Before

Width:  |  Height:  |  Size: 824 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 313 B

View file

@ -1 +0,0 @@
<svg width="32" height="32" enable-background="new 0 0 595.275 311.111" version="1.1" viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="32" height="32" rx="5" ry="5" fill="#0082c9"/><path d="m5 3a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3zm7 1c-0.554 0-1 0.446-1 1v2c0 0.554 0.446 1 1 1h16c0.554 0 1-0.446 1-1v-2c0-0.554-0.446-1-1-1zm-7 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3zm7 1c-0.554 0-1 0.446-1 1v2c0 0.554 0.446 1 1 1h16c0.554 0 1-0.446 1-1v-2c0-0.554-0.446-1-1-1zm-7 9a3 3 0 0 0-3 3 3 3 0 0 0 3 3 3 3 0 0 0 3-3 3 3 0 0 0-3-3zm7 1c-0.554 0-1 0.446-1 1v2c0 0.554 0.446 1 1 1h16c0.554 0 1-0.446 1-1v-2c0-0.554-0.446-1-1-1z" fill="#fff" style="paint-order:markers fill stroke"/></svg>

Before

Width:  |  Height:  |  Size: 759 B

8
img/forms.svg Normal file
View file

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32">
<circle cx="6" cy="6" r="3" fill="#fff"/>
<circle cx="6" cy="16" r="3" fill="#fff"/>
<circle cx="6" cy="26" r="3" fill="#fff"/>
<rect width="18" height="4" x="11" y="4" rx="1" ry="1" fill="#fff"/>
<rect width="18" height="4" x="11" y="14" rx="1" ry="1" fill="#fff"/>
<rect width="18" height="4" x="11" y="24" rx="1" ry="1" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 420 B

View file

@ -1,46 +0,0 @@
{
"curly": true,
"eqeqeq": true,
"immed": true,
"indent": 4,
"latedef": true,
"noarg": true,
"noempty": true,
"nonew": true,
"plusplus": false,
"node": true,
"undef": true,
"unused": false,
"strict": false,
"maxparams": false,
"maxdepth": 4,
"esversion": 6,
"browser": true,
"devel": true,
"jquery": true,
"jasmine": true,
"laxbreak": true,
"globals": {
"jQuery": true,
"ICAL": true,
"jstz": true,
"moment": true,
"angular": true,
"app": true,
"OC": true,
"oc_current_user":true,
"oc_requesttoken": true,
"requestToken": true,
"inject": true,
"module": true,
"t": true,
"it": true,
"exports": true,
"escapeHTML": true,
"possible": true,
"dav": true,
"hslToRgb": true,
"autosize": true,
"_": true
}
}

View file

@ -1,14 +0,0 @@
function deleteForm($formEl) {
var str = t('forms', 'Do you really want to delete this new form?') + '\n\n' + $($formEl).attr('data-value');
if (confirm(str)) {
var form = document.form_delete_form;
var hiddenId = document.createElement("input");
hiddenId.setAttribute("name", "formId");
hiddenId.setAttribute("type", "hidden");
form.appendChild(hiddenId);
form.elements.formId.value = $formEl.id.split('_')[2];
form.submit();
}
}

View file

@ -389,8 +389,9 @@ class ApiController extends Controller {
* @NoAdminRequired
* @param int $formId
* @return DataResponse
* TODO: use hash instead of id ?
*/
public function removeForm(int $id) {
public function deleteForm(int $id) {
try {
$formToDelete = $this->eventMapper->find($id);
} catch (DoesNotExistException $e) {
@ -533,11 +534,6 @@ class ApiController extends Controller {
$this->eventMapper->insert($event);
return new Http\JSONResponse([
'id' => $event->getId(),
'hash' => $event->getHash(),
]);
return new Http\JSONResponse($this->getForm($event->getHash()));
}
}

View file

@ -7,6 +7,7 @@
* @author Inigo Jiron <ijiron@terpmail.umd.edu>
* @author Natalie Gilbert
* @author Affan Hussain
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
@ -86,40 +87,63 @@ class PageController extends Controller {
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*/
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function index(): TemplateResponse {
return new TemplateResponse('forms', 'forms.tmpl',
['urlGenerator' => $this->urlGenerator]);
}
/**
* @NoAdminRequired
*/
public function createForm(): TemplateResponse {
return new TemplateResponse('forms', 'forms.tmpl',
['urlGenerator' => $this->urlGenerator]);
}
/**
* @NoAdminRequired
*/
public function cloneForm(): TemplateResponse {
return new TemplateResponse('forms', 'forms.tmpl',
['urlGenerator' => $this->urlGenerator]);
Util::addScript($this->appName, 'forms');
Util::addStyle($this->appName, 'icons');
return new TemplateResponse($this->appName, 'main');
}
/**
* @NoAdminRequired
* @param string $hash
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function editForm($hash): TemplateResponse {
return new TemplateResponse('forms', 'forms.tmpl', [
'urlGenerator' => $this->urlGenerator,
'hash' => $hash
]);
public function createForm(): TemplateResponse {
Util::addScript($this->appName, 'forms');
Util::addStyle($this->appName, 'icons');
return new TemplateResponse($this->appName, 'main');
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function cloneForm(): TemplateResponse {
Util::addScript($this->appName, 'forms');
Util::addStyle($this->appName, 'icons');
return new TemplateResponse($this->appName, 'main');
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function editForm(): TemplateResponse {
Util::addScript($this->appName, 'forms');
Util::addStyle($this->appName, 'icons');
return new TemplateResponse($this->appName, 'main');
}
/**
* @NoAdminRequired
* @NoCSRFRequired
*
* @return TemplateResponse
*/
public function getResult(): TemplateResponse {
Util::addScript($this->appName, 'forms');
Util::addStyle($this->appName, 'icons');
return new TemplateResponse($this->appName, 'main');
}
/**
@ -402,13 +426,4 @@ class PageController extends Controller {
}
return false;
}
/**
* @NoAdminRequired
* @param int $id
* @return TemplateResponse
*/
public function getResult(int $id): TemplateResponse {
return new TemplateResponse('forms', 'forms.tmpl');
}
}

326
package-lock.json generated
View file

@ -1668,6 +1668,22 @@
"integrity": "sha512-f+sKpdLZXkODV+OY39K1M+Spmd4RgxmtEXmNn4Bviv4R7uBFHXuw+JX9ZdfDeOryfHjJ/TRQxQEp0GMpBwZFUw==",
"dev": true
},
"@nextcloud/dialogs": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@nextcloud/dialogs/-/dialogs-1.2.2.tgz",
"integrity": "sha512-N8A8J8UKSvz/hqNcm7gwpm70uAAsx0wurjhdYZ989jaMho+H/Hinjd2jkbV8UnsYYw0x/vWvEX5t6Lwbv08K0g==",
"requires": {
"core-js": "3.6.4",
"toastify-js": "^1.7.0"
},
"dependencies": {
"core-js": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw=="
}
}
},
"@nextcloud/eslint-config": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@nextcloud/eslint-config/-/eslint-config-1.0.0.tgz",
@ -1706,17 +1722,18 @@
}
},
"@nextcloud/l10n": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.0.0.tgz",
"integrity": "sha512-A6zUwFWgwvQ5q86GdcfgD2t3uZ+H/k45S0OUlS6iMf0p5br9IrovF7hdv5+jaJcdQpkpOX2AKuordC1KheNGGA==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.1.1.tgz",
"integrity": "sha512-aVGvOhXgBzGtgMTOIVov1T7jt35FjX2vCqWCT0C/t6T6VgRn6l2Wb3/jzQycyUgGe71VPqS/4TpSH21yyH99YA==",
"requires": {
"core-js": "3.5.0"
"core-js": "3.6.4",
"node-gettext": "^2.0.0"
},
"dependencies": {
"core-js": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.5.0.tgz",
"integrity": "sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw=="
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
"integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw=="
}
}
},
@ -1729,6 +1746,23 @@
"core-js": "3.6.1",
"i18next": "19.0.2",
"moment": "2.24.0"
},
"dependencies": {
"@nextcloud/l10n": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.0.0.tgz",
"integrity": "sha512-A6zUwFWgwvQ5q86GdcfgD2t3uZ+H/k45S0OUlS6iMf0p5br9IrovF7hdv5+jaJcdQpkpOX2AKuordC1KheNGGA==",
"requires": {
"core-js": "3.5.0"
},
"dependencies": {
"core-js": {
"version": "3.5.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.5.0.tgz",
"integrity": "sha512-Ifh3kj78gzQ7NAoJXeTu+XwzDld0QRIwjBLRqAMhuLhP3d2Av5wmgE9ycfnvK6NAEjTkQ1sDPeoEZAWO3Hx1Uw=="
}
}
}
}
},
"@nextcloud/router": {
@ -3759,6 +3793,11 @@
"integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=",
"dev": true
},
"debounce": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.0.tgz",
"integrity": "sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg=="
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
@ -4421,15 +4460,6 @@
}
}
},
"eslint-plugin-nextcloud": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-nextcloud/-/eslint-plugin-nextcloud-0.3.0.tgz",
"integrity": "sha512-LUD2qdirGL0BRt4uaMDGxen17mWVq9JwuGDt7P7Celz7bzdu0X48RrS8mhXn9e0w78+nYN5kPoULG2Bw04r4HA==",
"dev": true,
"requires": {
"requireindex": "~1.2.0"
}
},
"eslint-plugin-node": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz",
@ -5279,22 +5309,26 @@
"dependencies": {
"abbrev": {
"version": "1.1.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"optional": true
},
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"optional": true
},
"aproba": {
"version": "1.2.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"optional": true
},
"are-we-there-yet": {
"version": "1.1.5",
"bundled": true,
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"optional": true,
"requires": {
"delegates": "^1.0.0",
@ -5303,12 +5337,14 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
@ -5317,32 +5353,38 @@
},
"chownr": {
"version": "1.1.3",
"bundled": true,
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
"integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==",
"optional": true
},
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"optional": true
},
"core-util-is": {
"version": "1.0.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"optional": true
},
"debug": {
"version": "3.2.6",
"bundled": true,
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"optional": true,
"requires": {
"ms": "^2.1.1"
@ -5350,22 +5392,26 @@
},
"deep-extend": {
"version": "0.6.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"optional": true
},
"delegates": {
"version": "1.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"optional": true
},
"detect-libc": {
"version": "1.0.3",
"bundled": true,
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=",
"optional": true
},
"fs-minipass": {
"version": "1.2.7",
"bundled": true,
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz",
"integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==",
"optional": true,
"requires": {
"minipass": "^2.6.0"
@ -5373,12 +5419,14 @@
},
"fs.realpath": {
"version": "1.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"optional": true
},
"gauge": {
"version": "2.7.4",
"bundled": true,
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"optional": true,
"requires": {
"aproba": "^1.0.3",
@ -5393,7 +5441,8 @@
},
"glob": {
"version": "7.1.6",
"bundled": true,
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"optional": true,
"requires": {
"fs.realpath": "^1.0.0",
@ -5406,12 +5455,14 @@
},
"has-unicode": {
"version": "2.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"optional": true
},
"iconv-lite": {
"version": "0.4.24",
"bundled": true,
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"optional": true,
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
@ -5419,7 +5470,8 @@
},
"ignore-walk": {
"version": "3.0.3",
"bundled": true,
"resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
"integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
"optional": true,
"requires": {
"minimatch": "^3.0.4"
@ -5427,7 +5479,8 @@
},
"inflight": {
"version": "1.0.6",
"bundled": true,
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"optional": true,
"requires": {
"once": "^1.3.0",
@ -5436,17 +5489,20 @@
},
"inherits": {
"version": "2.0.4",
"bundled": true,
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"optional": true
},
"ini": {
"version": "1.3.5",
"bundled": true,
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"optional": true
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
@ -5454,12 +5510,14 @@
},
"isarray": {
"version": "1.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"optional": true
},
"minimatch": {
"version": "3.0.4",
"bundled": true,
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
@ -5467,12 +5525,14 @@
},
"minimist": {
"version": "0.0.8",
"bundled": true,
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"optional": true
},
"minipass": {
"version": "2.9.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz",
"integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==",
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
@ -5481,7 +5541,8 @@
},
"minizlib": {
"version": "1.3.3",
"bundled": true,
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz",
"integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==",
"optional": true,
"requires": {
"minipass": "^2.9.0"
@ -5489,7 +5550,8 @@
},
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"optional": true,
"requires": {
"minimist": "0.0.8"
@ -5497,12 +5559,14 @@
},
"ms": {
"version": "2.1.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"optional": true
},
"needle": {
"version": "2.4.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
"integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==",
"optional": true,
"requires": {
"debug": "^3.2.6",
@ -5512,7 +5576,8 @@
},
"node-pre-gyp": {
"version": "0.14.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz",
"integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==",
"optional": true,
"requires": {
"detect-libc": "^1.0.2",
@ -5529,7 +5594,8 @@
},
"nopt": {
"version": "4.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
"integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
"optional": true,
"requires": {
"abbrev": "1",
@ -5538,7 +5604,8 @@
},
"npm-bundled": {
"version": "1.1.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz",
"integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==",
"optional": true,
"requires": {
"npm-normalize-package-bin": "^1.0.1"
@ -5546,12 +5613,14 @@
},
"npm-normalize-package-bin": {
"version": "1.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
"integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
"optional": true
},
"npm-packlist": {
"version": "1.4.7",
"bundled": true,
"resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz",
"integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==",
"optional": true,
"requires": {
"ignore-walk": "^3.0.1",
@ -5560,7 +5629,8 @@
},
"npmlog": {
"version": "4.1.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"optional": true,
"requires": {
"are-we-there-yet": "~1.1.2",
@ -5571,17 +5641,20 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"optional": true
},
"object-assign": {
"version": "4.1.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"optional": true
},
"once": {
"version": "1.4.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"optional": true,
"requires": {
"wrappy": "1"
@ -5589,17 +5662,20 @@
},
"os-homedir": {
"version": "1.0.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
"optional": true
},
"os-tmpdir": {
"version": "1.0.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
"optional": true
},
"osenv": {
"version": "0.1.5",
"bundled": true,
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"optional": true,
"requires": {
"os-homedir": "^1.0.0",
@ -5608,17 +5684,20 @@
},
"path-is-absolute": {
"version": "1.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"optional": true
},
"process-nextick-args": {
"version": "2.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"optional": true
},
"rc": {
"version": "1.2.8",
"bundled": true,
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"optional": true,
"requires": {
"deep-extend": "^0.6.0",
@ -5629,14 +5708,16 @@
"dependencies": {
"minimist": {
"version": "1.2.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"optional": true
}
}
},
"readable-stream": {
"version": "2.3.6",
"bundled": true,
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"optional": true,
"requires": {
"core-util-is": "~1.0.0",
@ -5650,7 +5731,8 @@
},
"rimraf": {
"version": "2.7.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"optional": true,
"requires": {
"glob": "^7.1.3"
@ -5658,37 +5740,44 @@
},
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"optional": true
},
"sax": {
"version": "1.2.4",
"bundled": true,
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"optional": true
},
"semver": {
"version": "5.7.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
"optional": true
},
"set-blocking": {
"version": "2.0.0",
"bundled": true,
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"optional": true
},
"signal-exit": {
"version": "3.0.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
"optional": true
},
"string-width": {
"version": "1.0.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
@ -5698,7 +5787,8 @@
},
"string_decoder": {
"version": "1.1.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"optional": true,
"requires": {
"safe-buffer": "~5.1.0"
@ -5706,7 +5796,8 @@
},
"strip-ansi": {
"version": "3.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
@ -5714,12 +5805,14 @@
},
"strip-json-comments": {
"version": "2.0.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"optional": true
},
"tar": {
"version": "4.4.13",
"bundled": true,
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz",
"integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==",
"optional": true,
"requires": {
"chownr": "^1.1.1",
@ -5733,12 +5826,14 @@
},
"util-deprecate": {
"version": "1.0.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"optional": true
},
"wide-align": {
"version": "1.1.3",
"bundled": true,
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"optional": true,
"requires": {
"string-width": "^1.0.2 || 2"
@ -5746,12 +5841,14 @@
},
"wrappy": {
"version": "1.0.2",
"bundled": true,
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"optional": true
},
"yallist": {
"version": "3.1.1",
"bundled": true,
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"optional": true
}
}
@ -10977,6 +11074,11 @@
}
}
},
"toastify-js": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/toastify-js/-/toastify-js-1.7.0.tgz",
"integrity": "sha512-GmPy4zJ/ulCfmCHlfCtgcB+K2xhx2AXW3T/ZZOSjyjaIGevhz+uvR8HSCTay/wBq4tt2mUnBqlObP1sSWGlsnQ=="
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
@ -11280,6 +11382,64 @@
}
}
},
"url-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.0.0.tgz",
"integrity": "sha512-sPsoBs8NkSJt9k/2zLUMDAf0rYaG00EtrFQpHRIphKrR6stGsO92LUJf/uUeQNKEoxqoJ4R4qDLqHl+AOEqolA==",
"dev": true,
"requires": {
"loader-utils": "^2.0.0",
"mime-types": "^2.1.26",
"schema-utils": "^2.6.5"
},
"dependencies": {
"ajv": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz",
"integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"emojis-list": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz",
"integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==",
"dev": true
},
"fast-deep-equal": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==",
"dev": true
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"schema-utils": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.5.tgz",
"integrity": "sha512-5KXuwKziQrTVHh8j/Uxz+QUbxkaLW9X/86NBlx/gnKgtsZA2GIVMUn17qWhRFwF8jdYb3Dig5hRO/W5mZqy6SQ==",
"dev": true,
"requires": {
"ajv": "^6.12.0",
"ajv-keywords": "^3.4.1"
}
}
}
},
"use": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",

View file

@ -59,7 +59,7 @@
"main": "src/js/main.js",
"scripts": {
"build": "NODE_ENV=production webpack --progress --hide-modules --config webpack.prod.js",
"dev": "NODE_ENV=development webpack --progress --watch --config webpack.dev.js",
"dev": "NODE_ENV=development webpack --progress --config webpack.dev.js",
"watch": "NODE_ENV=development webpack --progress --watch --config webpack.dev.js",
"lint": "eslint --ext .js,.vue src",
"lint:fix": "eslint --ext .js,.vue src --fix",
@ -69,9 +69,14 @@
"test:coverage": "jest --coverage"
},
"dependencies": {
"@nextcloud/auth": "^1.2.2",
"@nextcloud/axios": "^1.3.2",
"@nextcloud/dialogs": "^1.2.2",
"@nextcloud/l10n": "^1.1.1",
"@nextcloud/moment": "^1.1.0",
"@nextcloud/router": "^1.0.2",
"@nextcloud/vue": "^1.4.1",
"debounce": "^1.2.0",
"json2csv": "5.0.0",
"vue": "^2.6.11",
"vue-clipboard2": "^0.3.1",
@ -98,7 +103,6 @@
"eslint-import-resolver-webpack": "^0.12.1",
"eslint-loader": "^3.0.3",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-nextcloud": "^0.3.0",
"eslint-plugin-node": "^10.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
@ -110,6 +114,7 @@
"stylelint-config-recommended-scss": "^3.3.0",
"stylelint-scss": "^3.16.0",
"stylelint-webpack-plugin": "^0.10.5",
"url-loader": "^4.0.0",
"vue-loader": "^15.9.1",
"vue-style-loader": "^4.1.1",
"vue-template-compiler": "^2.6.11",

162
src/Forms.vue Normal file
View file

@ -0,0 +1,162 @@
<!--
- @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
-
- @author René Gieling <github@dartcafe.de>
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<Content app-name="forms">
<AppNavigation>
<AppNavigationNew button-class="icon-add" :text="t('forms', 'New form')" @click="onNewForm" />
<AppNavigationForm v-for="form in formattedForms"
:key="form.id"
:form="form"
@delete="onDeleteForm" />
</AppNavigation>
<!-- No forms & loading emptycontents -->
<AppContent v-if="loading || noForms || (!hash && $route.name !== 'create')">
<EmptyContent v-if="loading" icon="icon-loading">
{{ t('forms', 'Loading forms …') }}
</EmptyContent>
<EmptyContent v-else-if="noForms">
{{ t('forms', 'No forms in here') }}
<template #desc>
<button class="primary" @click="onNewForm">
{{ t('forms', 'Create a new one') }}
</button>
</template>
</EmptyContent>
<EmptyContent v-else>
{{ t('forms', 'Please select a form') }}
</EmptyContent>
</AppContent>
<!-- No errors show router content -->
<template v-else>
<router-view :form="selectedForm" />
<router-view :form="selectedForm" name="sidebar" />
</template>
</Content>
</template>
<script>
import { showError } from '@nextcloud/dialogs'
import { generateUrl } from '@nextcloud/router'
import axios from '@nextcloud/axios'
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
import AppNavigationNew from '@nextcloud/vue/dist/Components/AppNavigationNew'
import Content from '@nextcloud/vue/dist/Components/Content'
import AppNavigationForm from './components/AppNavigationForm'
import EmptyContent from './components/EmptyContent'
import { formatForm } from './utils/FormsUtils'
export default {
name: 'Forms',
components: {
AppNavigationForm,
AppContent,
AppNavigation,
AppNavigationNew,
Content,
EmptyContent,
},
data() {
return {
loading: true,
forms: [],
}
},
computed: {
noForms() {
return this.forms && this.forms.length === 0
},
formattedForms() {
return this.forms.map(formatForm)
},
hash() {
return this.$route.params.hash
},
selectedForm() {
// TODO: replace with form.hash
return this.forms.find(form => form.event.hash === this.hash)
},
},
beforeMount() {
this.loadForms()
},
methods: {
/**
* Initial forms load
*/
async loadForms() {
this.loading = true
try {
const response = await axios.get(generateUrl('apps/forms/get/forms'))
this.forms = response.data
} catch (error) {
showError(t('forms', 'An error occured while loading the forms list'))
console.error(error)
} finally {
this.loading = false
}
},
/**
*
*/
async onNewForm() {
try {
// Request a new empty form
const response = await axios.post(generateUrl('/apps/forms/api/v1/form'))
const newForm = response.data
this.forms.push(newForm)
this.$router.push({ name: 'edit', params: { hash: newForm.event.hash } })
} catch (error) {
showError(t('forms', 'Unable to create a new form'))
console.error(error)
}
},
/**
* Remove form from forms list after successful server deletion request
*
* @param {Number} id the form id
*/
async onDeleteForm(id) {
const formIndex = this.forms.findIndex(form => form.id === id)
this.forms.splice(formIndex, 1)
},
},
}
</script>

View file

@ -0,0 +1,179 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<AppNavigationItem
ref="navigationItem"
:exact="true"
:icon="loading ? 'icon-loading-small' : ''"
:title="form.title"
:to="{ name: 'edit', params: { hash: form.hash } }">
<AppNavigationIconBullet slot="icon" :color="bulletColor" />
<template v-if="!loading" #actions>
<ActionLink
:href="formLink"
:icon="copied && copySuccess ? 'icon-checkmark-color' : 'icon-clippy'"
target="_blank"
@click.stop.prevent="copyLink">
{{ clipboardTooltip }}
</ActionLink>
<ActionRouter :close-after-click="true"
:exact="true"
icon="icon-forms"
:to="{ name: 'results', params: { hash: form.hash } }">
{{ t('forms', 'Show results') }}
</ActionRouter>
<!-- <ActionRouter :close-after-click="true"
:exact="true"
icon="icon-clone"
:to="{ name: 'clone', params: { hash: form.hash } }">
{{ t('forms', 'Clone form') }}
</ActionRouter> -->
<ActionSeparator />
<ActionButton :close-after-click="true" icon="icon-delete" @click="onDeleteForm">
{{ t('forms', 'Delete form') }}
</ActionButton>
</template>
</AppNavigationItem>
</template>
<script>
import { generateUrl } from '@nextcloud/router'
import { showError, showSuccess } from '@nextcloud/dialogs'
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
import ActionLink from '@nextcloud/vue/dist/Components/ActionLink'
import ActionRouter from '@nextcloud/vue/dist/Components/ActionRouter'
import ActionSeparator from '@nextcloud/vue/dist/Components/ActionSeparator'
import AppNavigationIconBullet from '@nextcloud/vue/dist/Components/AppNavigationIconBullet'
import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
import axios from '@nextcloud/axios'
import Vue from 'vue'
import VueClipboard from 'vue-clipboard2'
Vue.use(VueClipboard)
export default {
name: 'AppNavigationForm',
components: {
AppNavigationItem,
AppNavigationIconBullet,
ActionButton,
ActionLink,
ActionRouter,
ActionSeparator,
},
props: {
form: {
type: Object,
required: true,
},
},
data() {
return {
copySuccess: true,
copied: false,
loading: false,
}
},
computed: {
/**
* Map form state to bullet color
*
* @returns {string} hex color
*/
bulletColor() {
const style = getComputedStyle(document.body)
if (this.form.expired) {
return style.getPropertyValue('--color-error').slice(-6)
}
return style.getPropertyValue('--color-success').slice(-6)
},
/**
* Return the form share link
* @returns {string}
*/
formLink() {
return window.location.protocol + '//' + window.location.host + generateUrl(`/apps/forms/${this.form.hash}`)
},
/**
* Clipboard v-tooltip message
* @returns {string}
*/
clipboardTooltip() {
if (this.copied) {
return this.copySuccess
? t('forms', 'Form link copied')
: t('forms', 'Cannot copy, please copy the link manually')
}
return t('forms', 'Copy to clipboard')
},
},
methods: {
async onDeleteForm() {
if (!confirm(t('forms', 'Are you sure you want to delete the form “{title}”', { title: this.form.title }))) {
return
}
// All good, let's delete
this.loading = true
try {
await axios.delete(generateUrl('/apps/forms/api/v1/form/{id}', { id: this.form.id }))
this.$emit('delete', this.form.id)
showSuccess(t('forms', 'Deleted form “{title}”', { title: this.form.title }))
} catch (error) {
showError(t('forms', 'Error while deleting form “{title}”', { title: this.form.title }))
console.error(error.response)
} finally {
this.loading = false
}
},
async copyLink() {
try {
await this.$copyText(this.formLink)
// make sure the menu stays open despite the click outside
this.$refs.navigationItem.menuOpened = true
this.copySuccess = true
this.copied = true
} catch (error) {
this.copySuccess = false
this.copied = true
console.error(error)
} finally {
setTimeout(() => {
this.copySuccess = false
this.copied = false
}, 4000)
}
},
},
}
</script>

View file

@ -1,7 +1,7 @@
<!--
- @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author René Gieling <github@dartcafe.de>
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
@ -12,34 +12,40 @@
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div id="app-forms">
<router-view />
<div class="emptycontent" role="note">
<div :class="icon" role="img" />
<h2><slot /></h2>
<p v-show="$slots.desc">
<slot name="desc" />
</p>
</div>
</template>
<script>
export default {
name: 'App',
name: 'EmptyContent',
props: {
icon: {
type: String,
default: 'icon-forms',
},
},
}
</script>
<style lang="scss">
#app-forms {
width: 100%;
display: flex;
.emptycontent {
margin-top: 20vh;
}
#app-content {
width: 100%;
display: flex;
}
</style>

View file

@ -1,39 +0,0 @@
<!--
- @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
-
- @author René Gieling <github@dartcafe.de>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<div class="forms-sidebar">
<slot />
</div>
</template>
<style lang="scss">
.forms-sidebar {
min-width: 300px;
border-left: 1px solid var(--color-border);
z-index: 500;
> ul,
> div {
padding: 8px;
}
}
</style>

View file

@ -163,16 +163,6 @@ export default {
}
},
formType() {
if (this.form.event.type === 'textForm') {
// TRANSLATORS This means that this is the type of the form. Another type is a 'date form'.
return t('forms', 'Text form')
} else {
// TRANSLATORS This means that this is the type of the form. Another type is a 'text form'.
return t('forms', 'Text form')
}
},
timeSpanCreated() {
return moment(this.form.event.created, 'YYYY-MM-DD HH:mm')
},

View file

@ -65,6 +65,7 @@
<script>
import Multiselect from '@nextcloud/vue/dist/Components/Multiselect'
// TODO: replace with same design as core sharing
import UserDiv from './_base-UserDiv'
import axios from '@nextcloud/axios'

View file

@ -1,8 +1,8 @@
/* jshint esversion: 6 */
/**
* @copyright Copyright (c) 2018 René Gieling <github@dartcafe.de>
*
* @author René Gieling <github@dartcafe.de>
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
@ -21,37 +21,47 @@
*
*/
import Vue from 'vue'
import router from './router'
import App from './App'
import { generateFilePath } from '@nextcloud/router'
import { getRequestToken } from '@nextcloud/auth'
import { translate, translatePlural } from '@nextcloud/l10n'
import '@nextcloud/dialogs/styles/toast.scss'
import Vue from 'vue'
import VueClipboard from 'vue-clipboard2'
import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip'
import router from './router'
import Forms from './Forms'
import Modal from './plugins/plugin.js'
// TODO: not use global registration
Vue.directive('tooltip', Tooltip)
Vue.use(VueClipboard)
Vue.use(Modal)
Vue.prototype.t = t
Vue.prototype.n = n
Vue.prototype.t = translate
Vue.prototype.n = translatePlural
// TODO: see if necessary
Vue.prototype.OC = OC
Vue.prototype.OCA = OCA
// CSP config for webpack dynamic chunk loading
// eslint-disable-next-line
__webpack_nonce__ = btoa(OC.requestToken)
__webpack_nonce__ = btoa(getRequestToken())
// Correct the root of the app for chunk loading
// OC.linkTo matches the apps folders
// OC.generateUrl ensure the index.php (or not)
// We do not want the index.php since we're loading files
// eslint-disable-next-line
__webpack_public_path__ = OC.linkTo('forms', 'js/')
__webpack_public_path__ = generateFilePath('forms', '', 'js/')
/* eslint-disable-next-line no-new */
new Vue({
el: '#app-forms',
router: router,
render: h => h(App),
el: '#content',
// eslint-disable-next-line vue/match-component-file-name
name: 'FormsRoot',
router,
render: h => h(Forms),
})

34
src/mixins/ViewsMixin.js Normal file
View file

@ -0,0 +1,34 @@
/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export default {
props: {
hash: {
type: String,
default: null,
},
form: {
type: Object,
// TODO: use default Form object ?
default: {},
},
},
}

53
src/models/Form.js Normal file
View file

@ -0,0 +1,53 @@
/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
export default class {
#data
/**
* Construct the form
* @param {Object} data the form data
*/
constructor(data) {
// Id check
if (!('id' in data && typeof data.id === 'number')) {
throw new Error('A new form must at least contain a valid id')
}
// Hash check
if (!('hash' in data && typeof data.id === 'string')) {
throw new Error('A new form must at least contain a valid hash')
}
this.#data = data
}
get id() {
return this.#data.id
}
get hash() {
return this.#data.hash
}
}

View file

@ -1,4 +1,3 @@
/* jshint esversion: 6 */
// we need our modal component
import ModalDialog from './ModalDialog'

View file

@ -1,4 +1,3 @@
/* jshint esversion: 6 */
/**
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
* @copyright Copyright (c) 2018 John Molakvoæ <skjnldsv@protonmail.com>
@ -22,59 +21,66 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
import Vue from 'vue'
import Router from 'vue-router'
import { generateUrl } from '@nextcloud/router'
import Create from './views/Create'
import Results from './views/Results'
import Sidebar from './views/Sidebar'
// Dynamic loading
const Create = () => import('./views/Create')
const List = () => import('./views/List')
const Results = () => import('./views/Results')
Vue.use(Router)
export default new Router({
mode: 'history',
base: OC.generateUrl(''),
// if index.php is in the url AND we got this far, then it's working:
// let's keep using index.php in the url
base: generateUrl('/apps/forms', ''),
linkActiveClass: 'active',
routes: [
{
path: '/:index(index.php/)?apps/forms/',
components: {
default: List,
},
props: false,
name: 'list',
path: '/',
name: 'root',
},
{
path: '/:index(index.php/)?apps/forms/edit/:hash',
path: '/new',
components: {
default: Create,
sidebar: Sidebar,
},
props: true,
name: 'edit',
},
{
path: '/:index(index.php/)?apps/forms/results/:hash',
components: {
default: Results,
},
props: false,
name: 'results',
},
{
path: '/:index(index.php/)?apps/forms/clone/:hash',
components: {
default: Create,
},
props: true,
name: 'clone',
},
{
path: '/:index(index.php/)?apps/forms/new',
components: {
default: Create,
},
props: false,
name: 'create',
},
{
path: '/:hash',
name: 'fill',
props: true,
},
{
path: '/:hash/edit',
components: {
default: Create,
sidebar: Sidebar,
},
name: 'edit',
props: true,
},
{
path: '/:hash/results',
component: Results,
name: 'results',
props: true,
},
{
path: '/:hash/clone',
components: {
default: Create,
sidebar: Sidebar,
},
name: 'clone',
props: true,
},
],
})

View file

@ -0,0 +1,53 @@
/**
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import axios from '@nextcloud/axios'
/**
* Get the forms list
*
* @returns {Array}
*/
const getForms = async function() {
try {
const response = await axios.get(OC.generateUrl('apps/forms/get/forms'))
return response.data
} catch (error) {
console.error(error)
throw Error(t('forms', 'Unable to fetch the forms list'))
}
}
/**
* Delete a form
*
* @param {int} id the form id to delete
*/
const deleteForm = async function(id) {
try {
axios.delete(OC.generateUrl('apps/forms/forms/{id}', { id }))
} catch (error) {
console.error(error)
throw Error(t('forms', 'Unable to delete the form'))
}
}
export { deleteForm, getForms }

44
src/utils/FormsUtils.js Normal file
View file

@ -0,0 +1,44 @@
/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* Format a form object prior to forms v2.0
*
* @param {Object} form the form raw object
* @returns {Object} properly formatted form object
*/
const formatForm = function(form) {
// clone form
const newForm = Object.assign({}, form, form.event)
// migrate object architecture
Object.assign(newForm, {
questions: form.options && form.options.formQuizQuestions,
})
// cleanup
delete newForm.options
delete newForm.event
return newForm
}
export { formatForm }

View file

@ -26,17 +26,7 @@
-->
<template>
<div id="app-content">
<Controls :intitle="title">
<template slot="after">
<button :disabled="writingForm" class="button btn primary" @click="writeForm(form.mode)">
<span>{{ saveButtonTitle }}</span>
<span v-if="writingForm" class="icon-loading-small" />
</button>
<button class="button symbol icon-settings" @click="switchSidebar" />
</template>
</Controls>
<AppContent>
<div class="workbench">
<div>
<h2>{{ t('forms', 'Form description') }}</h2>
@ -53,7 +43,7 @@
<div>
<h2>{{ t('forms', 'Make a Form') }}</h2>
<div v-show="form.event.type === 'quizForm'" id="quiz-form-selector-text">
<div id="quiz-form-selector-text">
<!--shows inputs for question types: drop down box to select the type, text box for question, and button to add-->
<label for="ans-type">Answer Type: </label>
<select v-model="selected">
@ -64,7 +54,7 @@
{{ option.text }}
</option>
</select>
<input v-model="newQuizQuestion" :placeholder=" t('forms', 'Add Question') " @keyup.enter="addQuestion()">
<input v-model="newQuizQuestion" :placeholder="t('forms', 'Add Question')" @keyup.enter="addQuestion()">
<button id="questButton"
@click="addQuestion()">
{{ t('forms', 'Add Question') }}
@ -72,7 +62,6 @@
</div>
<!--Transition group to list the already added questions (in the form of quizFormItems)-->
<transitionGroup
v-show="form.mode == 'create'"
id="form-list"
name="list"
tag="ul"
@ -89,195 +78,40 @@
</transitionGroup>
</div>
</div>
<SideBar v-if="sidebar">
<div v-if="adminMode" class="warning">
{{ t('forms', 'You are editing in admin mode') }}
</div>
<UserDiv :user-id="form.event.owner" :description="t('forms', 'Owner')" />
<ul class="tabHeaders">
<li class="tabHeader selected" data-tabid="configurationsTabView" data-tabindex="0">
<a href="#">
{{ t('forms', 'Configuration') }}
</a>
</li>
</ul>
<div v-if="protect">
<span>{{ t('forms', 'Configuration is locked. Changing options may result in unwanted behaviour, but you can unlock it anyway.') }}</span>
<button @click="protect=false">
{{ t('forms', 'Unlock configuration ') }}
</button>
</div>
<div id="configurationsTabView" class="tab">
<div class="configBox ">
<label class="title icon-settings">
{{ t('forms', 'Form configurations') }}
</label>
<input id="anonymous"
v-model="form.event.isAnonymous"
:disabled="protect"
type="checkbox"
class="checkbox">
<label for="anonymous" class="title">
{{ t('forms', 'Anonymous form') }}
</label>
<input id="unique"
v-model="form.event.unique"
:disabled="form.event.access !== 'registered' || form.event.isAnonymous"
type="checkbox"
class="checkbox">
<label for="unique" class="title">
<span>{{ t('forms', 'Only allow one submission per user') }}</span>
</label>
<input v-show="form.event.isAnonymous"
id="trueAnonymous"
v-model="form.event.fullAnonymous"
:disabled="protect"
type="checkbox"
class="checkbox">
<input id="expiration"
v-model="form.event.expiration"
:disabled="protect"
type="checkbox"
class="checkbox">
<label class="title" for="expiration">
{{ t('forms', 'Expires') }}
</label>
<DatetimePicker v-show="form.event.expiration"
v-model="form.event.expirationDate"
v-bind="expirationDatePicker"
:disabled="protect"
:time-picker-options="{ start: '00:00', step: '00:05', end: '23:55' }"
style="width:170px" />
</div>
<div class="configBox">
<label class="title icon-user">
{{ t('forms', 'Access') }}
</label>
<input id="private"
v-model="form.event.access"
:disabled="protect"
type="radio"
value="registered"
class="radio">
<label for="private" class="title">
<div class="title icon-group" />
<span>{{ t('forms', 'Registered users only') }}</span>
</label>
<input id="public"
v-model="form.event.access"
:disabled="protect"
type="radio"
value="public"
class="radio">
<label for="public" class="title">
<div class="title icon-link" />
<span>{{ t('forms', 'Public access') }}</span>
</label>
<input id="select"
v-model="form.event.access"
:disabled="protect"
type="radio"
value="select"
class="radio">
<label for="select" class="title">
<div class="title icon-shared" />
<span>{{ t('forms', 'Only shared') }}</span>
</label>
</div>
</div>
<ShareDiv v-show="form.event.access === 'select'"
:active-shares="form.shares"
:placeholder="t('forms', 'Name of user or group')"
:hide-names="true"
@update-shares="updateShares"
@remove-share="removeShare" />
</SideBar>
<LoadingOverlay v-if="loadingForm" />
</div>
</AppContent>
</template>
<script>
import axios from '@nextcloud/axios'
import DatetimePicker from '@nextcloud/vue/dist/Components/DatetimePicker'
import moment from '@nextcloud/moment'
import debounce from 'debounce'
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import { showError, showSuccess } from '@nextcloud/dialogs'
import Controls from '../components/_base-Controls'
import LoadingOverlay from '../components/_base-LoadingOverlay'
import QuizFormItem from '../components/quizFormItem'
import ShareDiv from '../components/shareDiv'
import SideBar from '../components/_base-SideBar'
import UserDiv from '../components/_base-UserDiv'
import ViewsMixin from '../mixins/ViewsMixin'
export default {
name: 'Create',
components: {
Controls,
DatetimePicker,
LoadingOverlay,
AppContent,
QuizFormItem,
ShareDiv,
SideBar,
UserDiv,
},
mixins: [ViewsMixin],
data() {
return {
move: {
step: 1,
unit: 'week',
units: ['minute', 'hour', 'day', 'week', 'month', 'year'],
},
form: {
mode: 'create',
votes: [],
shares: [],
grantedAs: 'owner',
id: 0,
result: 'new',
event: {
id: 0,
hash: '',
type: 'quizForm',
title: '',
description: '',
created: '',
access: 'public',
unique: false,
expiration: false,
expirationDate: '',
expired: false,
isAnonymous: false,
fullAnonymous: false,
owner: undefined,
},
options: {
formQuizQuestions: [],
},
},
lang: '',
locale: '',
placeholder: '',
newQuizAnswer: '',
newQuizQuestion: '',
nextQuizAnswerId: 1,
nextQuizQuestionId: 1,
protect: false,
writingForm: false,
loadingForm: true,
sidebar: false,
titleEmpty: false,
indexPage: '',
longDateFormat: '',
dateTimeFormat: '',
selected: '',
uniqueName: false,
uniqueAns: false,
@ -293,10 +127,6 @@ export default {
},
computed: {
adminMode() {
return (this.form.event.owner !== OC.getCurrentUser().uid && OC.isUserAdmin())
},
langShort() {
return this.lang.split('-')[0]
},
@ -324,38 +154,6 @@ export default {
return moment.localeData(moment.locale(this.locale))
},
expirationDatePicker() {
return {
editable: true,
minuteStep: 1,
type: 'datetime',
format: moment.localeData().longDateFormat('L') + ' ' + moment.localeData().longDateFormat('LT'),
lang: this.lang.split('-')[0],
placeholder: t('forms', 'Expiration date'),
timePickerOptions: {
start: '00:00',
step: '00:30',
end: '23:30',
},
}
},
optionDatePicker() {
return {
editable: false,
minuteStep: 1,
type: 'datetime',
format: moment.localeData().longDateFormat('L') + ' ' + moment.localeData().longDateFormat('LT'),
lang: this.lang.split('-')[0],
placeholder: t('forms', 'Click to add a date'),
timePickerOptions: {
start: '00:00',
step: '00:30',
end: '23:30',
},
}
},
},
watch: {
@ -363,37 +161,25 @@ export default {
// only used when the title changes after page load
document.title = t('forms', 'Forms') + ' - ' + this.title
},
form: {
deep: true,
handler: function() {
this.debounceWriteForm()
},
},
},
created() {
this.indexPage = OC.generateUrl('apps/forms/')
this.lang = OC.getLanguage()
try {
this.locale = OC.getLocale()
} catch (e) {
if (e instanceof TypeError) {
this.locale = this.lang
} else {
/* eslint-disable-next-line no-console */
console.log(e)
}
}
moment.locale(this.locale)
this.longDateFormat = moment.localeData().longDateFormat('L')
this.dateTimeFormat = moment.localeData().longDateFormat('L') + ' ' + moment.localeData().longDateFormat('LT')
if (this.$route.name === 'create') {
// TODO: manage this from Forms.vue, request a new form to the server
this.form.event.owner = OC.getCurrentUser().uid
this.loadingForm = false
} else if (this.$route.name === 'edit') {
this.loadForm(this.$route.params.hash)
this.protect = true
// TODO: fetch & update form?
this.form.mode = 'edit'
} else if (this.$route.name === 'clone') {
this.loadForm(this.$route.params.hash)
}
if (window.innerWidth > 1024) {
this.sidebar = true
// TODO: CLONE
}
},
@ -403,18 +189,6 @@ export default {
this.sidebar = !this.sidebar
},
addShare(item) {
this.form.shares.push(item)
},
updateShares(share) {
this.form.shares = share.slice(0)
},
removeShare(item) {
this.form.shares.splice(this.form.shares.indexOf(item), 1)
},
checkNames() {
this.uniqueName = true
this.form.options.formQuizQuestions.forEach(q => {
@ -427,9 +201,9 @@ export default {
addQuestion() {
this.checkNames()
if (this.selected === '') {
OC.Notification.showTemporary(t('forms', 'Select a question type!'))
showError(t('forms', 'Select a question type!'), { duration: 3000 })
} else if (!this.uniqueName) {
OC.Notification.showTemporary(t('forms', 'Cannot have the same question!'))
showError(t('forms', 'Cannot have the same question!'))
} else {
if (this.newQuizQuestion !== null & this.newQuizQuestion !== '' & (/\S/.test(this.newQuizQuestion))) {
this.form.options.formQuizQuestions.push({
@ -460,7 +234,7 @@ export default {
addAnswer(item, question) {
this.checkAnsNames(item, question)
if (!this.uniqueAnsName) {
OC.Notification.showTemporary(t('forms', 'Two answers cannot be the same!'))
showError(t('forms', 'Two answers cannot be the same!'), { duration: 3000 })
} else {
if (item.newQuizAnswer !== null & item.newQuizAnswer !== '' & (/\S/.test(item.newQuizAnswer))) {
item.formQuizAnswers.push({
@ -486,24 +260,22 @@ export default {
})
},
writeForm(mode) {
debounceWriteForm: debounce(function() {
this.writeForm()
}, 200),
writeForm() {
this.allHaveAns()
if (mode !== '') {
this.form.mode = mode
}
if (this.form.event.title.length === 0 | !(/\S/.test(this.form.event.title))) {
this.titleEmpty = true
OC.Notification.showTemporary(t('forms', 'Title must not be empty!'))
} else if (this.form.options.formQuizQuestions.length === 0) {
OC.Notification.showTemporary(t('forms', 'Must have at least one question!'))
showError(t('forms', 'Title must not be empty!'), { duration: 3000 })
} else if (!this.haveAns) {
OC.Notification.showTemporary(t('forms', 'All questions need answers!'))
showError(t('forms', 'All questions need answers!'), { duration: 3000 })
} else if (this.form.event.expiration & this.form.event.expirationDate === '') {
OC.Notification.showTemporary(t('forms', 'Need to pick an expiration date!'))
showError(t('forms', 'Need to pick an expiration date!'), { duration: 3000 })
} else {
this.writingForm = true
this.titleEmpty = false
// this.form.event.expirationDate = moment(this.form.event.expirationDate).utc()
axios.post(OC.generateUrl('apps/forms/write/form'), this.form)
.then((response) => {
@ -511,201 +283,130 @@ export default {
this.form.event.hash = response.data.hash
this.form.event.id = response.data.id
this.writingForm = false
OC.Notification.showTemporary(t('forms', '%n successfully saved', 1, this.form.event.title))
// window.location.href = OC.generateUrl('apps/forms/edit/' + this.form.event.hash)
this.$router.push('/apps/forms/')
showSuccess(t('forms', '%n successfully saved', 1, this.form.event.title), { duration: 3000 })
}, (error) => {
this.form.event.hash = ''
this.writingForm = false
OC.Notification.showTemporary(t('forms', 'Error on saving form, see console'))
showError(t('forms', 'Error on saving form, see console'))
/* eslint-disable-next-line no-console */
console.log(error.response)
})
}
},
loadForm(hash) {
this.loadingForm = true
axios.get(OC.generateUrl('apps/forms/get/form/' + hash))
.then((response) => {
this.form = response.data
if (this.form.event.expirationDate !== null) {
this.form.event.expirationDate = new Date(moment.utc(this.form.event.expirationDate))
} else {
this.form.event.expirationDate = ''
}
if (this.$route.name === 'clone') {
this.form.event.owner = OC.getCurrentUser().uid
this.form.event.title = t('forms', 'Clone of %n', 1, this.form.event.title)
this.form.event.id = 0
this.form.id = 0
this.form.event.hash = ''
this.form.grantedAs = 'owner'
this.form.result = 'new'
this.form.mode = 'create'
this.form.votes = []
}
this.loadingForm = false
this.newQuizAnswer = ''
this.newQuizQuestion = ''
}, (error) => {
/* eslint-disable-next-line no-console */
console.log(error.response)
this.form.event.hash = ''
this.loadingForm = false
})
},
},
}
</script>
<style lang="scss">
#app-content {
input.hasTimepicker {
width: 75px;
}
input.hasTimepicker {
width: 75px;
}
}
.warning {
color: var(--color-error);
font-weight: bold;
color: var(--color-error);
font-weight: bold;
}
.forms-content {
display: flex;
padding-top: 45px;
flex-grow: 1;
display: flex;
padding-top: 45px;
flex-grow: 1;
}
input[type="text"] {
display: block;
width: 100%;
display: block;
width: 100%;
}
.workbench {
margin-top: 45px;
display: flex;
flex-grow: 1;
flex-wrap: wrap;
overflow-x: hidden;
display: flex;
flex-grow: 1;
flex-wrap: wrap;
overflow-x: hidden;
> div {
min-width: 245px;
max-width: 540px;
display: flex;
flex-direction: column;
flex-grow: 1;
padding: 8px;
}
}
.forms-sidebar {
margin-top: 45px;
width: 25%;
.configBox {
display: flex;
flex-direction: column;
padding: 8px;
& > * {
padding-left: 21px;
}
& > .title {
display: flex;
background-position: 0 2px;
padding-left: 24px;
opacity: 0.7;
font-weight: bold;
margin-bottom: 4px;
& > span {
padding-left: 4px;
}
}
}
}
input,
textarea {
&.error {
border: 2px solid var(--color-error);
box-shadow: 1px 0 var(--border-radius) var(--color-box-shadow);
}
> div {
min-width: 245px;
max-width: 540px;
display: flex;
flex-direction: column;
flex-grow: 1;
padding: 8px;
}
}
/* Transitions for inserting and removing list items */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
transition: all 0.5s ease;
}
.list-enter,
.list-leave-to {
opacity: 0;
opacity: 0;
}
.list-move {
transition: transform 0.5s;
transition: transform 0.5s;
}
/* */
#form-item-selector-text {
> input {
width: 100%;
}
> input {
width: 100%;
}
}
.form-table {
> li {
display: flex;
align-items: baseline;
padding-left: 8px;
padding-right: 8px;
line-height: 24px;
min-height: 24px;
border-bottom: 1px solid var(--color-border);
overflow: hidden;
white-space: nowrap;
> li {
display: flex;
align-items: baseline;
padding-left: 8px;
padding-right: 8px;
line-height: 24px;
min-height: 24px;
border-bottom: 1px solid var(--color-border);
overflow: hidden;
white-space: nowrap;
&:active,
&:hover {
transition: var(--background-dark) 0.3s ease;
background-color: var(--color-background-dark); //$hover-color;
&:active,
&:hover {
transition: var(--background-dark) 0.3s ease;
background-color: var(--color-background-dark); //$hover-color;
}
}
> div {
display: flex;
flex-grow: 1;
font-size: 1.2em;
opacity: 0.7;
white-space: normal;
padding-right: 4px;
&.avatar {
flex-grow: 0;
}
}
> div {
display: flex;
flex-grow: 1;
font-size: 1.2em;
opacity: 0.7;
white-space: normal;
padding-right: 4px;
&.avatar {
flex-grow: 0;
}
}
> div:nth-last-child(1) {
justify-content: center;
flex-grow: 0;
flex-shrink: 0;
}
}
> div:nth-last-child(1) {
justify-content: center;
flex-grow: 0;
flex-shrink: 0;
}
}
}
button {
&.button-inline {
border: 0;
background-color: transparent;
}
&.button-inline {
border: 0;
background-color: transparent;
}
}
.tab {
display: flex;
flex-wrap: wrap;
display: flex;
flex-wrap: wrap;
}
.selectUnit {
display: flex;

31
src/views/Fill.vue Normal file
View file

@ -0,0 +1,31 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<span>TODO</span>
</template>
<script>
export default {
name: 'Fill',
}
</script>

View file

@ -22,15 +22,7 @@
-->
<template>
<div id="app-content">
<Controls>
<router-link :to="{ name: 'create'}" class="button">
<span class="symbol icon-add" />
<span class="hidden-visually">
{{ t('forms', 'New') }}
</span>
</router-link>
</Controls>
<AppContent>
<div v-if="noForms" class="">
<div class="icon-forms" />
<h2> {{ t('No existing forms.') }} </h2>
@ -56,54 +48,69 @@
</transition-group>
<LoadingOverlay v-if="loading" />
<modal-dialog />
</div>
</AppContent>
</template>
<script>
import { showError } from '@nextcloud/dialogs'
import axios from '@nextcloud/axios'
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
import FormListItem from '../components/formListItem'
import Controls from '../components/_base-Controls'
import axios from '@nextcloud/axios'
import LoadingOverlay from '../components/_base-LoadingOverlay'
import ViewsMixin from '../mixins/ViewsMixin'
export default {
name: 'List',
components: {
Controls,
AppContent,
FormListItem,
LoadingOverlay,
},
mixins: [ViewsMixin],
data() {
return {
noForms: false,
loading: true,
forms: [],
}
},
created() {
this.indexPage = OC.generateUrl('apps/forms/')
computed: {
noForms() {
return this.forms && this.forms.length > 0
},
},
beforeMount() {
this.loadForms()
},
methods: {
loadForms() {
/**
* Initial forms load
*/
async loadForms() {
this.loading = true
axios.get(OC.generateUrl('apps/forms/get/forms'))
.then((response) => {
this.forms = response.data
this.loading = false
}, (error) => {
/* eslint-disable-next-line no-console */
console.log(error.response)
this.loading = false
})
try {
const response = await axios.get(OC.generateUrl('apps/forms/get/forms'))
this.forms = response.data
} catch (error) {
showError(t('forms', 'An error occured while loading the forms list'))
console.error(error)
} finally {
this.loading = false
}
},
/**
* Open the help page
*/
helpPage() {
window.open('https://github.com/affan98/forms/blob/master/Forms_Support.md')
window.open('https://github.com/nextcloud/forms/blob/master/Forms_Support.md')
},
viewFormResults(index, event, name) {
this.$router.push({
name: name,

View file

@ -23,7 +23,7 @@
-->
<template>
<div>
<AppContent>
<div>
<button class="button btn primary" @click="download">
<span>{{ "Export to CSV" }}</span>
@ -51,7 +51,7 @@
<LoadingOverlay v-if="loading" />
<modal-dialog />
</div>
</div>
</AppContent>
</template>
<script>
@ -59,15 +59,20 @@ import ResultItem from '../components/resultItem'
import json2csvParser from 'json2csv'
import axios from '@nextcloud/axios'
import LoadingOverlay from '../components/_base-LoadingOverlay'
import ViewsMixin from '../mixins/ViewsMixin'
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
export default {
name: 'Results',
components: {
AppContent,
ResultItem,
LoadingOverlay,
},
mixins: [ViewsMixin],
data() {
return {
loading: true,

237
src/views/Sidebar.vue Normal file
View file

@ -0,0 +1,237 @@
<!--
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
-
- @author John Molakvoæ <skjnldsv@protonmail.com>
-
- @license GNU AGPL version 3 or any later version
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->
<template>
<AppSidebar :title="form.event.title">
<div class="configBox ">
<label class="title icon-settings">
{{ t('forms', 'Form configurations') }}
</label>
<input id="anonymous"
v-model="form.event.isAnonymous"
type="checkbox"
class="checkbox">
<label for="anonymous" class="title">
{{ t('forms', 'Anonymous form') }}
</label>
<input id="unique"
v-model="form.event.unique"
:disabled="form.event.access !== 'registered' || form.event.isAnonymous"
type="checkbox"
class="checkbox">
<label for="unique" class="title">
<span>{{ t('forms', 'Only allow one submission per user') }}</span>
</label>
<input v-show="form.event.isAnonymous"
id="trueAnonymous"
v-model="form.event.fullAnonymous"
type="checkbox"
class="checkbox">
<input id="expiration"
v-model="form.event.expiration"
type="checkbox"
class="checkbox">
<label class="title" for="expiration">
{{ t('forms', 'Expires') }}
</label>
<DatetimePicker v-show="form.event.expiration"
v-model="form.event.expirationDate"
v-bind="expirationDatePicker"
:time-picker-options="{ start: '00:00', step: '00:05', end: '23:55' }"
style="width:170px" />
</div>
<div class="configBox">
<label class="title icon-user">
{{ t('forms', 'Access') }}
</label>
<input id="private"
v-model="form.event.access"
type="radio"
value="registered"
class="radio">
<label for="private" class="title">
<div class="title icon-group" />
<span>{{ t('forms', 'Registered users only') }}</span>
</label>
<input id="public"
v-model="form.event.access"
type="radio"
value="public"
class="radio">
<label for="public" class="title">
<div class="title icon-link" />
<span>{{ t('forms', 'Public access') }}</span>
</label>
<input id="select"
v-model="form.event.access"
type="radio"
value="select"
class="radio">
<label for="select" class="title">
<div class="title icon-shared" />
<span>{{ t('forms', 'Only shared') }}</span>
</label>
</div>
<ShareDiv v-show="form.event.access === 'select'"
:active-shares="form.shares"
:placeholder="t('forms', 'Name of user or group')"
:hide-names="true"
@update-shares="updateShares"
@remove-share="removeShare" />
</AppSidebar>
</template>
<script>
import AppSidebar from '@nextcloud/vue/dist/Components/AppSidebar'
import DatetimePicker from '@nextcloud/vue/dist/Components/DatetimePicker'
import moment from '@nextcloud/moment'
import ShareDiv from '../components/shareDiv'
import ViewsMixin from '../mixins/ViewsMixin'
export default {
name: 'Sidebar',
components: {
AppSidebar,
DatetimePicker,
ShareDiv,
},
mixins: [ViewsMixin],
data() {
return {
lang: '',
locale: '',
longDateFormat: '',
dateTimeFormat: '',
}
},
computed: {
expirationDatePicker() {
return {
editable: true,
minuteStep: 1,
type: 'datetime',
format: moment.localeData().longDateFormat('L') + ' ' + moment.localeData().longDateFormat('LT'),
lang: this.lang.split('-')[0],
placeholder: t('forms', 'Expiration date'),
timePickerOptions: {
start: '00:00',
step: '00:30',
end: '23:30',
},
}
},
optionDatePicker() {
return {
editable: false,
minuteStep: 1,
type: 'datetime',
format: moment.localeData().longDateFormat('L') + ' ' + moment.localeData().longDateFormat('LT'),
lang: this.lang.split('-')[0],
placeholder: t('forms', 'Click to add a date'),
timePickerOptions: {
start: '00:00',
step: '00:30',
end: '23:30',
},
}
},
},
created() {
this.lang = OC.getLanguage()
try {
this.locale = OC.getLocale()
} catch (e) {
if (e instanceof TypeError) {
this.locale = this.lang
} else {
/* eslint-disable-next-line no-console */
console.log(e)
}
}
moment.locale(this.locale)
this.longDateFormat = moment.localeData().longDateFormat('L')
this.dateTimeFormat = moment.localeData().longDateFormat('L') + ' ' + moment.localeData().longDateFormat('LT')
},
methods: {
addShare(item) {
this.form.shares.push(item)
},
updateShares(share) {
this.form.shares = share.slice(0)
},
removeShare(item) {
this.form.shares.splice(this.form.shares.indexOf(item), 1)
},
},
}
</script>
<style lang="scss" scoped>
.configBox {
display: flex;
flex-direction: column;
padding: 8px;
& > * {
padding-left: 21px;
}
& > .title {
display: flex;
background-position: 0 2px;
padding-left: 24px;
margin-bottom: 4px;
& > span {
padding-left: 4px;
}
}
}
input,
textarea {
&.error {
border: 2px solid var(--color-error);
box-shadow: 1px 0 var(--border-radius) var(--color-box-shadow);
}
}
</style>

25
templates/main.php Normal file
View file

@ -0,0 +1,25 @@
<?php
/**
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
*
* @author John Molakvoæ <skjnldsv@protonmail.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
?>
<div id="content"></div>

View file

@ -8,48 +8,49 @@ module.exports = {
path: path.resolve(__dirname, './js'),
publicPath: '/js/',
filename: 'forms.js',
chunkFilename: 'chunks/[name].js'
chunkFilename: 'chunks/[name].js',
},
module: {
rules: [
{
test: /\.css$/,
use: ['vue-style-loader', 'css-loader']
use: ['vue-style-loader', 'css-loader'],
},
{
test: /\.scss$/,
use: ['vue-style-loader', 'css-loader', 'sass-loader']
use: ['vue-style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.(js|vue)$/,
use: 'eslint-loader',
exclude: /node_modules/,
enforce: 'pre'
enforce: 'pre',
},
{
test: /\.vue$/,
loader: 'vue-loader',
exclude: /node_modules/
exclude: /node_modules/,
},
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
exclude: /node_modules/,
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader',
loader: 'url-loader',
options: {
name: '[name].[ext]?[hash]'
}
}
]
limit: 8192,
},
},
],
},
plugins: [
new VueLoaderPlugin(),
new StyleLintPlugin()
new StyleLintPlugin(),
],
resolve: {
extensions: ['*', '.js', '.vue']
}
extensions: ['*', '.js', '.vue'],
symlinks: false,
},
}

View file

@ -7,7 +7,7 @@ module.exports = merge(common, {
devServer: {
historyApiFallback: true,
noInfo: true,
overlay: true
overlay: true,
},
devtool: 'source-map'
devtool: 'source-map',
})

View file

@ -3,5 +3,5 @@ const merge = require('webpack-merge')
const common = require('./webpack.common.js')
module.exports = merge(common, {
mode: 'production'
mode: 'production',
})