Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
J
jio
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Tomáš Peterka
jio
Commits
589f75df
Commit
589f75df
authored
Mar 19, 2014
by
Tristan Cavelier
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'erp5storage'
parents
c844e2f1
0e46db8c
Changes
3
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
660 additions
and
145 deletions
+660
-145
examples/jio_dashboard.html
examples/jio_dashboard.html
+136
-42
src/jio.storage/erp5storage.js
src/jio.storage/erp5storage.js
+246
-103
src/jio.storage/erp5storage.taskmanagerview.js
src/jio.storage/erp5storage.taskmanagerview.js
+278
-0
No files found.
examples/jio_dashboard.html
View file @
589f75df
...
...
@@ -6,6 +6,10 @@
</head>
<body>
<table
border=
"1"
style=
"width: 100%;"
>
<tr>
<th
style=
"text-align: center;"
id=
"script_injection_space"
>
</th>
</tr>
<tr
style=
"font-style:italic;"
>
<th>
Storage Description
</th>
</tr>
...
...
@@ -20,9 +24,10 @@
<button
onclick=
"fillMemoryDescription()"
>
Memory
</button>
<button
onclick=
"fillLocalDescription()"
>
Local
</button>
<button
onclick=
"fillDavDescription()"
>
WebDAV
</button>
<button
onclick=
"fillDavBasicDescription()"
>
WebDAV Basic
</button>
<button
onclick=
"fillERP5Description()"
>
ERP5
</button>
<button
onclick=
"fillCustomDescription()"
>
Custom
</button>
-
<button
onclick=
"fillLastDescription()"
>
Last
</button>
<button
onclick=
"fillCustomDescription()"
>
Custom
</button>
<b
r
/><b
utton
onclick=
"fillLastDescription()"
>
Last
</button>
<button
onclick=
"loadDescription()"
>
Load
</button>
<button
onclick=
"saveDescription()"
>
Save
</button>
</td>
...
...
@@ -48,7 +53,7 @@
<button
onclick=
"post()"
>
post
</button>
<button
onclick=
"put()"
>
put
</button>
<button
onclick=
"get()"
>
get
</button>
<button
onclick=
"remove()"
>
remove
</button>
<button
onclick=
"
window.
remove()"
>
remove
</button>
-
<button
onclick=
"putAttachment()"
>
putAttachment
</button>
<button
onclick=
"getAttachment()"
>
getAttachment
</button>
<button
onclick=
"removeAttachment()"
>
removeAttachment
</button>
...
...
@@ -68,11 +73,14 @@
</table>
<br
/>
<div
style=
"text-align: center;"
>
<button
onclick=
"printLocalStorage()"
>
print localStorage
</button>
<button
onclick=
"localStorage.clear()"
>
clear localStorage
</button><br
/>
<button
onclick=
"clearlog()"
>
Clear Log
</button>
Useful functions:
<button
onclick=
"scriptLogLocalStorage()"
>
log localStorage
</button>
<button
onclick=
"localStorage.clear()"
>
clear localStorage
</button>
<button
onclick=
"scriptRemoveAllDocs()"
>
removeAllDocs
</button>
</div>
<hr
/>
<button
onclick=
"clearlog()"
>
Clear Log
</button>
<hr
/>
<div
id=
"log"
>
</div>
<script
type=
"text/javascript"
>
...
...
@@ -121,6 +129,56 @@ function error(o) {
function
clearlog
()
{
select
(
"
#log
"
).
innerHTML
=
""
;
}
function
injectScript
(
url
)
{
var
script
=
document
.
createElement
(
"
script
"
);
script
.
setAttribute
(
"
src
"
,
url
);
document
.
body
.
appendChild
(
script
);
}
function
injectLastScripts
()
{
var
i
,
scripts
=
JSON
.
parse
(
localStorage
.
getItem
(
"
jio_dashboard_injected_scripts
"
)
||
"
{}
"
);
for
(
i
in
scripts
)
{
if
(
i
)
{
injectScript
(
i
);
}
}
}
function
saveScripts
()
{
var
scripts
=
{};
[].
forEach
.
call
(
document
.
querySelectorAll
(
"
#script_injection_space input[type=
\"
text
\"
]
"
),
function
(
input
)
{
return
scripts
[
input
.
value
]
=
true
;
});
localStorage
.
setItem
(
"
jio_dashboard_injected_scripts
"
,
JSON
.
stringify
(
scripts
));
location
.
href
=
location
.
href
;
}
function
buildScriptFields
()
{
var
space
,
el
,
i
,
count
=
0
,
scripts
;
function
createInput
(
value
)
{
var
e
=
document
.
createElement
(
"
input
"
);
e
.
setAttribute
(
"
type
"
,
"
text
"
);
e
.
setAttribute
(
"
style
"
,
"
width: 98%;
"
);
if
(
value
)
{
e
.
value
=
value
;
}
count
+=
1
;
return
e
;
}
scripts
=
JSON
.
parse
(
localStorage
.
getItem
(
"
jio_dashboard_injected_scripts
"
)
||
"
{}
"
);
space
=
select
(
"
#script_injection_space
"
);
el
=
document
.
createElement
(
"
div
"
);
el
.
textContent
=
"
Additional scripts:
"
;
space
.
appendChild
(
el
);
for
(
i
in
scripts
)
{
if
(
i
)
{
space
.
appendChild
(
createInput
(
i
));
}
}
space
.
appendChild
(
createInput
());
el
=
document
.
createElement
(
"
input
"
);
el
.
setAttribute
(
"
type
"
,
"
button
"
);
el
.
value
=
"
Save scripts and refresh page
"
;
el
.
onclick
=
saveScripts
;
space
.
appendChild
(
el
);
}
// clear log on Alt+L
document
.
addEventListener
(
"
keypress
"
,
function
(
event
)
{
if
(
event
.
altKey
===
true
&&
event
.
charCode
===
108
)
{
...
...
@@ -133,6 +191,7 @@ document.addEventListener("keypress", function (event) {
<script
src=
"../src/sha256.amd.js"
></script>
<script
src=
"../jio.js"
></script>
<script
src=
"../src/jio.storage/localstorage.js"
></script>
<script
src=
"../src/jio.storage/davstorage.js"
></script>
<script
src=
"http://git.erp5.org/gitweb/uritemplate-js.git/blob_plain/HEAD:/bin/uritemplate-min.js"
></script>
<script
src=
"../lib/uri/URI.js"
></script>
<script
src=
"../src/jio.storage/erp5storage.js"
></script>
...
...
@@ -141,6 +200,9 @@ document.addEventListener("keypress", function (event) {
var
my_jio
=
null
;
injectLastScripts
();
buildScriptFields
();
function
fillMemoryDescription
()
{
select
(
"
#storagedescription
"
).
value
=
JSON
.
stringify
({
"
type
"
:
"
local
"
,
...
...
@@ -159,15 +221,20 @@ function fillLocalDescription() {
function
fillDavDescription
()
{
select
(
"
#storagedescription
"
).
value
=
JSON
.
stringify
({
"
type
"
:
"
dav
"
,
"
auth_type
"
:
"
basic
"
,
"
username
"
:
"
<username>
"
,
"
password
"
:
"
<password>
"
"
url
"
:
"
<url>
"
},
null
,
"
"
)
}
function
fillDavBasicDescription
()
{
select
(
"
#storagedescription
"
).
value
=
JSON
.
stringify
({
"
type
"
:
"
dav
"
,
"
url
"
:
"
<url>
"
,
"
basic_login
"
:
"
<btoa(username + ':' + password)>
"
},
null
,
"
"
)
}
function
fillERP5Description
()
{
select
(
"
#storagedescription
"
).
value
=
JSON
.
stringify
({
"
type
"
:
"
erp5
"
,
"
url
"
:
"
<url
/hateoas
>
"
"
url
"
:
"
<url
to hateoas web site
>
"
},
null
,
"
"
)
}
function
fillCustomDescription
()
{
...
...
@@ -204,48 +271,55 @@ function createJIO() {
}
}
function
printLocalStorage
()
{
log
(
"
localStorage content
\n
"
+
JSON
.
stringify
(
localStorage
,
null
,
"
"
));
function
logError
(
begin_date
,
err
)
{
log
(
'
time :
'
+
(
Date
.
now
()
-
begin_date
));
error
(
'
return :
'
+
JSON
.
stringify
(
err
,
null
,
"
"
));
throw
err
;
}
function
callback
(
err
,
val
,
begin_date
)
{
function
logAnswer
(
begin_date
,
val
)
{
log
(
'
time :
'
+
(
Date
.
now
()
-
begin_date
));
if
(
err
)
{
return
error
(
'
return :
'
+
JSON
.
stringify
(
err
,
null
,
"
"
));
}
log
(
'
return :
'
+
JSON
.
stringify
(
val
,
null
,
"
"
));
return
val
;
}
function
command
(
method
)
{
function
command
(
method
,
num
)
{
var
begin_date
=
Date
.
now
(),
doc
=
{},
opts
=
{};
if
(
!
my_jio
)
{
return
error
(
'
no jio set
'
);
error
(
'
no jio set
'
);
return
;
}
doc
=
JSON
.
parse
(
select
(
'
#metadata
'
).
value
);
opts
=
JSON
.
parse
(
select
(
"
#options
"
).
value
);
doc
=
select
(
'
#metadata
'
).
value
;
opts
=
select
(
"
#options
"
).
value
;
if
(
num
!==
undefined
)
{
doc
=
doc
.
replace
(
/
\\
u0000/g
,
num
);
opts
=
opts
.
replace
(
/
\\
u0000/g
,
num
);
}
doc
=
JSON
.
parse
(
doc
);
opts
=
JSON
.
parse
(
opts
);
log
(
method
+
'
\n
doc:
'
+
JSON
.
stringify
(
doc
,
null
,
"
"
)
+
'
\n
opts:
'
+
JSON
.
stringify
(
opts
,
null
,
"
"
));
if
(
method
===
"
allDocs
"
)
{
my_jio
.
allDocs
(
opts
).
then
(
function
(
answer
)
{
callback
(
undefined
,
answer
,
begin_date
);
},
function
(
error
)
{
callback
(
error
,
undefined
,
begin_date
);
});
return
my_jio
.
allDocs
(
opts
).
then
(
logAnswer
.
bind
(
null
,
begin_date
),
logError
.
bind
(
null
,
begin_date
)
);
}
else
{
my_jio
[
method
](
doc
,
opts
).
then
(
function
(
answer
)
{
callback
(
undefined
,
answer
,
begin_date
);
},
function
(
error
)
{
callback
(
error
,
undefined
,
begin_date
);
});
return
my_jio
[
method
](
doc
,
opts
).
then
(
logAnswer
.
bind
(
null
,
begin_date
),
logError
.
bind
(
null
,
begin_date
)
);
}
}
function
doCommandNTimes
(
method
)
{
var
i
=
-
1
,
n
=
0
,
lock
;
var
i
=
-
1
,
n
=
0
,
lock
,
promise_list
=
[]
;
n
=
parseInt
(
select
(
"
#times
"
).
value
,
10
);
lock
=
select
(
"
#times-lock
"
).
checked
;
if
(
!
lock
)
{
...
...
@@ -255,39 +329,59 @@ function doCommandNTimes(method) {
n
=
1
;
}
while
(
++
i
<
n
)
{
command
(
method
);
promise_list
.
push
(
command
(
method
,
i
)
);
}
return
RSVP
.
all
(
promise_list
);
}
function
post
()
{
doCommandNTimes
(
"
post
"
);
return
doCommandNTimes
(
"
post
"
);
}
function
put
()
{
doCommandNTimes
(
"
put
"
);
return
doCommandNTimes
(
"
put
"
);
}
function
get
()
{
doCommandNTimes
(
"
get
"
);
return
doCommandNTimes
(
"
get
"
);
}
function
remove
()
{
doCommandNTimes
(
"
remove
"
);
return
doCommandNTimes
(
"
remove
"
);
}
function
putAttachment
()
{
doCommandNTimes
(
"
putAttachment
"
);
return
doCommandNTimes
(
"
putAttachment
"
);
}
function
getAttachment
()
{
doCommandNTimes
(
"
getAttachment
"
);
return
doCommandNTimes
(
"
getAttachment
"
);
}
function
removeAttachment
()
{
doCommandNTimes
(
"
removeAttachment
"
);
return
doCommandNTimes
(
"
removeAttachment
"
);
}
function
allDocs
()
{
doCommandNTimes
(
"
allDocs
"
);
return
doCommandNTimes
(
"
allDocs
"
);
}
function
check
()
{
doCommandNTimes
(
"
check
"
);
return
doCommandNTimes
(
"
check
"
);
}
function
repair
()
{
doCommandNTimes
(
"
repair
"
);
return
doCommandNTimes
(
"
repair
"
);
}
//////////////////////////////////////////////////////////////////////
// scripts
function
scriptLogLocalStorage
()
{
log
(
"
localStorage content
\n
"
+
JSON
.
stringify
(
localStorage
,
null
,
"
"
));
}
function
scriptRemoveAllDocs
()
{
var
original_metadata_value
=
select
(
'
#metadata
'
).
value
;
return
command
(
"
allDocs
"
).
then
(
function
(
answer
)
{
return
RSVP
.
all
(
answer
.
data
.
rows
.
map
(
function
(
row
)
{
select
(
"
#metadata
"
).
value
=
JSON
.
stringify
({
"
_id
"
:
row
.
id
});
return
command
(
"
remove
"
);
}));;
}).
then
(
function
()
{
select
(
'
#metadata
'
).
value
=
original_metadata_value
;
});
}
//-->
</script>
...
...
src/jio.storage/erp5storage.js
View file @
589f75df
This diff is collapsed.
Click to expand it.
src/jio.storage/erp5storage.taskmanagerview.js
0 → 100644
View file @
589f75df
/*
* Copyright 2013, Nexedi SA
* Released under the LGPL license.
* http://www.gnu.org/licenses/lgpl.html
*/
/*jslint indent: 2, maxlen: 80, nomen: true */
/*global jIO, UriTemplate, FormData, RSVP, URI, DOMParser, Blob,
ProgressEvent, define, ERP5Storage */
(
function
(
dependencies
,
module
)
{
"
use strict
"
;
if
(
typeof
define
===
'
function
'
&&
define
.
amd
)
{
return
define
(
dependencies
,
module
);
}
module
(
RSVP
,
jIO
,
URI
,
UriTemplate
,
ERP5Storage
);
}([
"
rsvp
"
,
"
jio
"
,
"
uri
"
,
"
uritemplate
"
,
"
erp5storage
"
],
function
(
RSVP
,
jIO
,
URI
,
UriTemplate
,
ERP5Storage
)
{
"
use strict
"
;
var
hasOwnProperty
=
Function
.
prototype
.
call
.
bind
(
Object
.
prototype
.
hasOwnProperty
),
constant
=
{};
constant
.
task_state_to_action
=
{
// Auto Planned : ?
"
Cancelled
"
:
"
cancel
"
,
"
Confirmed
"
:
"
confirm
"
,
// Draft : ?
"
Deleted
"
:
"
delete
"
,
"
Ordered
"
:
"
order
"
,
"
Planned
"
:
"
plan
"
};
constant
.
allDocsState
=
{
"
data
"
:
{
"
total_rows
"
:
7
,
"
rows
"
:
[{
"
id
"
:
"
taskmanager:state_module/1
"
,
"
doc
"
:
{
"
type
"
:
"
State
"
,
"
title
"
:
"
Auto Planned
"
//"state": "Auto Planned"
},
"
values
"
:
{}
},
{
"
id
"
:
"
taskmanager:state_module/2
"
,
"
doc
"
:
{
"
type
"
:
"
State
"
,
"
title
"
:
"
Cancelled
"
,
//"state": "Cancelled",
"
action
"
:
constant
.
task_state_to_action
.
Cancelled
},
"
values
"
:
{}
},
{
"
id
"
:
"
taskmanager:state_module/3
"
,
"
doc
"
:
{
"
type
"
:
"
State
"
,
"
title
"
:
"
Confirmed
"
,
//"state": "Confirmed",
"
action
"
:
constant
.
task_state_to_action
.
Confirmed
},
"
values
"
:
{}
},
{
"
id
"
:
"
taskmanager:state_module/4
"
,
"
doc
"
:
{
"
type
"
:
"
State
"
,
"
title
"
:
"
Deleted
"
,
//"state": "Deleted",
"
action
"
:
constant
.
task_state_to_action
.
Deleted
},
"
values
"
:
{}
},
{
"
id
"
:
"
taskmanager:state_module/5
"
,
"
doc
"
:
{
"
type
"
:
"
State
"
,
"
title
"
:
"
Draft
"
//"state": "Draft"
},
"
values
"
:
{}
},
{
"
id
"
:
"
taskmanager:state_module/6
"
,
"
doc
"
:
{
"
type
"
:
"
State
"
,
"
title
"
:
"
Ordered
"
,
//"state": "Ordered",
"
action
"
:
constant
.
task_state_to_action
.
Ordered
},
"
values
"
:
{}
},
{
"
id
"
:
"
taskmanager:state_module/7
"
,
"
doc
"
:
{
"
type
"
:
"
State
"
,
"
title
"
:
"
Planned
"
,
//"state": "Planned",
"
action
"
:
constant
.
task_state_to_action
.
Planned
},
"
values
"
:
{}
}]
}};
constant
.
mapping_jio_to_erp5
=
{};
constant
.
mapping_erp5_to_jio
=
{};
// XXX docstring
function
addMetadataMapping
(
jio_type
,
erp5_type
)
{
if
(
typeof
jio_type
!==
"
string
"
||
typeof
erp5_type
!==
"
string
"
||
!
jio_type
||
!
erp5_type
)
{
throw
new
TypeError
(
"
addMetadataMapping(): The two arguments
"
+
"
must be non empty strings
"
);
}
if
(
constant
.
mapping_jio_to_erp5
[
jio_type
])
{
throw
new
TypeError
(
"
A mapping already exists for jIO metadata '
"
+
jio_type
+
"
'
"
);
}
if
(
constant
.
mapping_erp5_to_jio
[
erp5_type
])
{
throw
new
TypeError
(
"
A mapping already exists for ERP5 metadata '
"
+
erp5_type
+
"
'
"
);
}
constant
.
mapping_jio_to_erp5
[
jio_type
]
=
erp5_type
;
constant
.
mapping_erp5_to_jio
[
erp5_type
]
=
jio_type
;
}
addMetadataMapping
(
"
type
"
,
"
portal_type
"
);
addMetadataMapping
(
"
state
"
,
"
translated_simulation_state_title_text
"
);
addMetadataMapping
(
"
project
"
,
"
source_project_title_text
"
);
addMetadataMapping
(
"
start
"
,
"
start_date
"
);
addMetadataMapping
(
"
stop
"
,
"
stop_date
"
);
addMetadataMapping
(
"
modified
"
,
"
modification_date
"
);
addMetadataMapping
(
"
date
"
,
"
creation_date
"
);
// addMetadataMapping("location", "destination_title");
// addMetadataMapping("source", "source_title");
// addMetadataMapping("requester", "destination_decision_title");
// addMetadataMapping("contributor", "contributor_list");
// addMetadataMapping("category", "category_list");
// XXX docstring
function
toERP5Metadata
(
jio_type
)
{
/*jslint forin: true */
if
(
typeof
jio_type
===
"
string
"
)
{
return
constant
.
mapping_jio_to_erp5
[
jio_type
]
||
jio_type
;
}
var
result
=
{},
key
;
if
(
typeof
jio_type
===
"
object
"
&&
jio_type
)
{
for
(
key
in
jio_type
)
{
if
(
hasOwnProperty
(
jio_type
,
key
))
{
result
[
toERP5Metadata
(
key
)]
=
jio_type
[
key
];
}
}
}
return
result
;
}
// XXX docstring
function
toJIOMetadata
(
erp5_type
)
{
/*jslint forin: true */
if
(
typeof
erp5_type
===
"
string
"
)
{
return
constant
.
mapping_erp5_to_jio
[
erp5_type
]
||
erp5_type
;
}
var
result
=
{},
key
;
if
(
typeof
erp5_type
===
"
object
"
&&
erp5_type
)
{
for
(
key
in
erp5_type
)
{
if
(
hasOwnProperty
(
erp5_type
,
key
))
{
result
[
toJIOMetadata
(
key
)]
=
erp5_type
[
key
];
}
}
}
return
result
;
}
ERP5Storage
.
onView
.
taskmanager
=
{};
ERP5Storage
.
onView
.
taskmanager
.
get
=
function
(
param
,
options
)
{
options
.
_view
=
"
taskmanrecord
"
;
return
ERP5Storage
.
onView
[
"
default
"
].
get
.
call
(
this
,
param
,
options
).
then
(
function
(
answer
)
{
answer
.
data
=
toJIOMetadata
(
answer
.
data
);
return
answer
;
});
};
ERP5Storage
.
onView
.
taskmanager
.
post
=
function
(
metadata
,
options
)
{
metadata
=
toERP5Metadata
(
metadata
);
options
.
_view
=
"
taskmanrecord
"
;
return
ERP5Storage
.
onView
[
"
default
"
].
post
.
call
(
this
,
metadata
,
options
);
};
ERP5Storage
.
onView
.
taskmanager
.
put
=
function
(
metadata
,
options
)
{
metadata
=
toERP5Metadata
(
metadata
);
options
.
_view
=
"
taskmanrecord
"
;
return
ERP5Storage
.
onView
[
"
default
"
].
put
.
call
(
this
,
metadata
,
options
);
};
ERP5Storage
.
onView
.
taskmanager
.
allDocs
=
function
(
param
,
options
)
{
var
that
=
this
;
/*jslint unparam: true */
function
changeQueryKeysToERP5Metadata
()
{
if
(
Array
.
isArray
(
options
.
select_list
))
{
options
.
select_list
=
options
.
select_list
.
map
(
toERP5Metadata
);
}
try
{
options
.
query
=
jIO
.
QueryFactory
.
create
(
options
.
query
);
options
.
query
.
onParseSimpleQuery
=
function
(
object
)
{
object
.
parsed
.
key
=
toERP5Metadata
(
object
.
parsed
.
key
);
};
return
options
.
query
.
parse
().
then
(
function
(
query
)
{
options
.
query
=
jIO
.
QueryFactory
.
create
(
query
).
toString
();
});
}
catch
(
e
)
{
delete
options
.
query
;
return
RSVP
.
resolve
();
}
}
function
requestERP5
(
site_hal
)
{
return
jIO
.
util
.
ajax
({
"
type
"
:
"
GET
"
,
"
url
"
:
UriTemplate
.
parse
(
site_hal
.
_links
.
raw_search
.
href
)
.
expand
({
query
:
options
.
query
,
// XXX Force erp5 to return embedded document
select_list
:
options
.
select_list
||
[
"
portal_type
"
,
"
title
"
,
"
reference
"
,
"
translated_simulation_state_title_text
"
,
"
description
"
],
limit
:
options
.
limit
}),
"
xhrFields
"
:
{
withCredentials
:
true
}
});
}
function
formatAnswer
(
event
)
{
var
catalog_json
=
JSON
.
parse
(
event
.
target
.
responseText
),
data
=
catalog_json
.
_embedded
.
contents
,
count
=
data
.
length
,
i
,
uri
,
item
,
result
=
[];
for
(
i
=
0
;
i
<
count
;
i
+=
1
)
{
item
=
data
[
i
];
uri
=
new
URI
(
item
.
_links
.
self
.
href
);
delete
item
.
_links
;
item
=
toJIOMetadata
(
item
);
result
.
push
({
id
:
uri
.
segment
(
2
),
doc
:
item
,
value
:
item
});
}
return
{
"
data
"
:
{
"
rows
"
:
result
,
"
total_rows
"
:
result
.
length
}};
}
function
continueAllDocs
()
{
// Hard code for states
if
(
options
.
query
===
"
portal_type:
\"
State
\"
"
)
{
return
constant
.
allDocsState
;
}
return
ERP5Storage
.
getSiteDocument
(
that
.
_url
).
then
(
requestERP5
).
then
(
formatAnswer
);
}
return
changeQueryKeysToERP5Metadata
().
then
(
continueAllDocs
);
};
}));
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment