Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Luciano Barletta
/
mini-web
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
0
Wiki
Network
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Commit bbb9f7e9
authored
2019-12-10 15:11:21 +0000
by
Luciano Barletta
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
terminado
1 parent
031e4964
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
209 additions
and
160 deletions
README.md
deploy.py
static/Scripts/ArmadoDeForm.js
static/Style/ArmadoDeForm.css
templates/field.html
templates/form.html
templates/generate.html
templates/login.html
README.md
View file @
bbb9f7e
...
@@ -9,7 +9,8 @@ En root se encuentra el controlador que toma el JSON y devuelve la página. El f
...
@@ -9,7 +9,8 @@ En root se encuentra el controlador que toma el JSON y devuelve la página. El f
JSON = {
JSON = {
'title' : "Título del Formulario",
'title' : "Título del Formulario",
'color' : "Color preferido",
'color' : "Color preferido",
'tabs' :
\[
Tabs]
'tabs' :
\[
Tabs],
'send' : "MiUrl.com"
}
}
Donde cada Tab es de la forma:
Donde cada Tab es de la forma:
...
@@ -28,3 +29,14 @@ Campo = {
...
@@ -28,3 +29,14 @@ Campo = {
'required' : True|False,
'required' : True|False,
'options' :
\[
"Opción1","Opción2"...]
'options' :
\[
"Opción1","Opción2"...]
}
}
Placeholder no es obligatorio. Si required no está será interpretado como False. Options es válido solo en algunos casos pero no causará error.
## <myserver>/login
Formulario especial para login. Se envía solamente la siguiente información.
{
'color' : "Color preferido",
'send' : "MiUrl.com"
}
\ No newline at end of file
\ No newline at end of file
deploy.py
View file @
bbb9f7e
...
@@ -11,48 +11,27 @@ qwertyuiopasdfghjklzxcvbnm\
...
@@ -11,48 +11,27 @@ qwertyuiopasdfghjklzxcvbnm\
QWERTYUIOPASDFGHJKLZXCVBNM"
QWERTYUIOPASDFGHJKLZXCVBNM"
sessions
=
{}
sessions
=
{}
@app.after_request
loginfields
=
[
def
after_request
(
response
):
{
# headers permitidos para la conversación
"title"
:
"Usuario"
,
response
.
headers
.
add
(
'Access-Control-Allow-Headers'
,
'
\
"type"
:
"text"
,
Access-Control-Allow-Methods,
\
"required"
:
True
Access-Control-Allow-Origin,
\
},
Content-Type'
)
{
# orígenes permitidos para CORS
"title"
:
"Contraseña"
,
response
.
headers
.
add
(
'Access-Control-Allow-Origin'
,
'*'
)
"type"
:
"password"
,
# métodos permitidos
"required"
:
True
response
.
headers
.
add
(
'Access-Control-Allow-Methods'
,
'GET,POST'
)
}
return
response
]
def
validate
(
name
,
psw
):
if
name
==
"admin"
and
psw
==
"admin"
:
return
True
return
False
def
token
():
@app.route
(
'/datos'
,
methods
=
[
'GET'
,
'POST'
])
result
=
""
def
datos
():
i
=
0
print
(
request
.
json
)
while
i
<
TOKEN_LENGHT
:
return
"OK"
char
=
random
.
randrange
(
0
,
len
(
TOKEN_STRING
))
result
+=
str
(
TOKEN_STRING
[
char
])
i
+=
1
return
result
@app.route
(
'/login'
,
methods
=
[
'POST'
])
@app.route
(
'/login'
,
methods
=
[
'
GET'
,
'
POST'
])
def
login
():
def
login
():
data
=
request
.
json
return
render_template
(
"login.html"
,
title
=
"Login"
,
fields
=
loginfields
,
color
=
"blue"
,
send
=
"http://192.168.15.119:5000/datos"
)
if
validate
(
data
[
'name'
],
data
[
'pass'
]):
sessions
[
'name'
]
=
token
()
return
json
.
dumps
({
"error_code"
:
0
,
"error"
:
""
,
"token"
:
sessions
[
'name'
]
})
return
json
.
dumps
({
"error_code"
:
1
,
"error"
:
"fallo en la validacion"
,
"token"
:
None
})
@app.route
(
'/'
,
methods
=
[
'GET'
,
'POST'
])
@app.route
(
'/'
,
methods
=
[
'GET'
,
'POST'
])
def
main
():
def
main
():
...
@@ -68,7 +47,7 @@ def main():
...
@@ -68,7 +47,7 @@ def main():
},
},
{
{
"title"
:
"mail"
,
"title"
:
"mail"
,
"type"
:
"mail"
,
"type"
:
"
e
mail"
,
"placeholder"
:
"ejemplo@gmail.com"
,
"placeholder"
:
"ejemplo@gmail.com"
,
}
}
]
]
...
@@ -79,7 +58,7 @@ def main():
...
@@ -79,7 +58,7 @@ def main():
{
{
"title"
:
"género"
,
"title"
:
"género"
,
"type"
:
"select"
,
"type"
:
"select"
,
"placeholder"
:
"G
è
nero"
,
"placeholder"
:
"G
é
nero"
,
"options"
:
[
"options"
:
[
"Hombre"
,
"Hombre"
,
"Mujer"
,
"Mujer"
,
...
@@ -92,7 +71,6 @@ def main():
...
@@ -92,7 +71,6 @@ def main():
"options"
:
[
"options"
:
[
"Hombres"
,
"Hombres"
,
"Mujeres"
,
"Mujeres"
,
"#other"
"#other"
],
],
"required"
:
True
"required"
:
True
...
@@ -100,11 +78,21 @@ def main():
...
@@ -100,11 +78,21 @@ def main():
]
]
},
},
{
{
"title"
:
"third"
,
"title"
:
"Fecha y hora"
,
"fields"
:
[]
"fields"
:
[
{
"title"
:
"Dia"
,
"type"
:
"date"
,
"placeholder"
:
"2019-12-10"
},
{
"title"
:
"Hora"
,
"type"
:
"time"
}
]
}
}
]
]
return
render_template
(
"form.html"
,
tabs
=
data
,
title
=
"Formulario de Prueba"
,
color
=
"#33aaff"
)
return
render_template
(
"form.html"
,
tabs
=
data
,
title
=
"Formulario de Prueba"
,
color
=
"#33aaff"
,
send
=
"http://192.168.15.119:5000/datos"
)
if
__name__
==
"__main__"
:
if
__name__
==
"__main__"
:
app
.
run
(
"0.0.0.0"
)
app
.
run
(
"0.0.0.0"
)
\ No newline at end of file
\ No newline at end of file
static/Scripts/ArmadoDeForm.js
View file @
bbb9f7e
...
@@ -12,17 +12,21 @@ var getDescendantByAttribute = (elem,attr,val) => {
...
@@ -12,17 +12,21 @@ var getDescendantByAttribute = (elem,attr,val) => {
},
null
);
},
null
);
}
}
function
HabilitarTab
(
bc
,
tc
,
n
){
function
HabilitarTab
(
tc
,
n
){
if
(
typeof
n
!=
"number"
)
return
console
.
log
(
"El entero es invalido"
);
if
(
typeof
n
!=
"number"
)
return
console
.
log
(
"El entero es invalido"
);
for
(
let
it
=
0
;
it
<
bc
.
children
.
length
&&
it
<
tc
.
children
.
length
;
it
++
)
{
for
(
let
it
=
0
;
it
<
tc
.
children
.
length
;
it
++
)
{
if
(
it
==
n
)
{
if
(
it
==
n
)
tc
.
children
[
it
].
style
.
display
=
"block"
;
bc
.
children
[
it
].
classList
.
add
(
"BotonesActive"
);
else
tc
.
children
[
it
].
style
.
display
=
"none"
;
tc
.
children
[
it
].
style
.
display
=
"block"
;
}
else
{
bc
.
children
[
it
].
classList
.
remove
(
"BotonesActive"
);
tc
.
children
[
it
].
style
.
display
=
"none"
;
}
}
}
function
HabilitarButton
(
bc
,
n
){
if
(
typeof
n
!=
"number"
)
return
console
.
log
(
"El entero es invalido"
);
for
(
let
it
=
0
;
it
<
bc
.
children
.
length
;
it
++
)
{
if
(
it
==
n
)
bc
.
children
[
it
].
classList
.
add
(
"BotonesActive"
);
else
bc
.
children
[
it
].
classList
.
remove
(
"BotonesActive"
);
}
}
}
}
...
@@ -38,3 +42,90 @@ function Otro(otroselect){
...
@@ -38,3 +42,90 @@ function Otro(otroselect){
else
other
.
style
.
display
=
"none"
;
else
other
.
style
.
display
=
"none"
;
}
}
}
}
var
accessDataContainer
=
tab
=>
getDescendantByAttribute
(
tab
,
"class"
,
"FieldsContainer"
);
function
Boton
(
tc
,
url
)
{
button
=
document
.
createElement
(
"button"
);
button
.
value
=
"Enviar"
;
button
.
setAttribute
(
"onclick"
,
"LeerYEnviar(document.getElementById('TabsContainer'),'"
+
url
+
"')"
);
button
.
setAttribute
(
"class"
,
"SendButton"
);
button
.
innerText
=
"Enviar"
;
tc
.
lastElementChild
.
children
[
0
].
appendChild
(
button
);
}
function
NoValido
(
input
,
msg
){
let
tab
=
getAncestorByAttribute
(
input
,
"class"
,
"Tabs"
)
let
n
=
parseInt
(
tab
.
id
[
tab
.
id
.
length
-
1
])
HabilitarTab
(
document
.
getElementById
(
'TabsContainer'
),
n
)
HabilitarButton
(
document
.
getElementById
(
'BotonesContainer'
),
n
)
input
.
setCustomValidity
(
msg
)
input
.
reportValidity
()
input
.
oninput
=
()
=>
input
.
setCustomValidity
(
''
);
}
function
CheckboxValidity
(
checkboxContainer
){
return
Array
.
from
(
checkboxContainer
.
children
).
reduce
(
(
last
,
checkbox
)
=>
checkbox
.
className
!=
"Checkbox"
?
last
:
checkbox
.
lastElementChild
.
firstElementChild
.
checked
||
last
,
false
)
}
function
GetCheckboxes
(
field
)
{
let
data
=
[];
Array
.
from
(
field
.
children
).
forEach
(
child
=>
{
if
(
child
.
className
!=
"Checkbox"
)
return
;
if
(
child
.
lastElementChild
.
firstElementChild
.
checked
)
data
.
push
(
child
.
firstElementChild
.
innerText
.
replace
(
/
[\n\s\*]
*/g
,
""
));
if
(
data
[
data
.
length
-
1
]
==
"Otro"
)
data
[
data
.
length
-
1
]
=
getAncestorByAttribute
(
child
,
"class"
,
"FieldInput"
).
lastElementChild
.
value
;
});
return
data
;
}
function
LeerYEnviar
(
tc
,
url
){
let
data
=
{};
let
valid
=
true
;
Array
.
from
(
tc
.
children
).
forEach
(
tab
=>
Array
.
from
(
accessDataContainer
(
tab
).
children
).
forEach
(
field
=>
{
if
(
valid
==
false
)
return
;
if
(
field
.
className
==
"Field"
)
{
let
name
=
getDescendantByAttribute
(
field
,
"class"
,
"FieldTitle"
).
innerText
.
replace
(
/
[\n\s\*]
*/g
,
""
);
let
input
=
getDescendantByAttribute
(
field
,
"class"
,
"FieldInput"
).
children
[
0
];
if
(
input
.
className
==
"Checkbox"
)
{
if
(
CheckboxValidity
(
input
.
parentElement
)
==
false
)
{
valid
=
false
;
NoValido
(
input
.
lastElementChild
.
firstElementChild
,
"Debe seleccionar al menos una casilla"
);
}
data
[
name
]
=
GetCheckboxes
(
input
.
parentElement
);
}
else
if
(
input
.
nodeName
==
"SELECT"
)
{
if
(
input
.
checkValidity
()
==
false
)
{
valid
=
false
;
NoValido
(
input
,
"Debe llenar este campo"
);
}
data
[
name
]
=
input
.
selectedOptions
[
0
].
value
;
if
(
data
[
name
]
==
"Otro"
)
data
[
name
]
=
getAncestorByAttribute
(
input
,
"class"
,
"FieldInput"
).
lastElementChild
.
value
;
}
else
{
if
(
input
.
checkValidity
()
==
false
)
{
valid
=
false
;
if
(
input
.
type
==
"email"
)
NoValido
(
input
,
"Mail inválido"
);
else
NoValido
(
input
,
"Mail inválido"
);
}
data
[
name
]
=
input
.
value
}
}
})
);
if
(
valid
==
false
)
return
;
console
.
log
(
data
);
http
=
new
XMLHttpRequest
();
http
.
open
(
"POST"
,
url
,
true
);
http
.
setRequestHeader
(
"Content-Type"
,
"application/json"
);
http
.
onload
=
()
=>
{
if
(
http
.
status
==
200
)
alert
(
"Enviado con éxito"
);
};
http
.
send
(
JSON
.
stringify
(
data
));
}
\ No newline at end of file
\ No newline at end of file
static/Style/ArmadoDeForm.css
View file @
bbb9f7e
...
@@ -48,7 +48,7 @@ body {
...
@@ -48,7 +48,7 @@ body {
}
}
.Botones
{
.Botones
{
border-radius
:
5px
5
px
0px
0px
;
border-radius
:
10px
10
px
0px
0px
;
background-color
:
rgb
(
168
,
168
,
168
);
background-color
:
rgb
(
168
,
168
,
168
);
border
:
none
;
border
:
none
;
border-bottom
:
2px
solid
rgb
(
120
,
120
,
120
);
border-bottom
:
2px
solid
rgb
(
120
,
120
,
120
);
...
@@ -57,7 +57,7 @@ body {
...
@@ -57,7 +57,7 @@ body {
.BotonesActive
{
.BotonesActive
{
background-color
:
var
(
--Color
);
background-color
:
var
(
--Color
);
height
:
1
05
%
;
height
:
1
10
%
;
border-bottom
:
none
;
border-bottom
:
none
;
}
}
...
@@ -84,6 +84,7 @@ body {
...
@@ -84,6 +84,7 @@ body {
margin-bottom
:
2%
;
margin-bottom
:
2%
;
min-height
:
70%
;
min-height
:
70%
;
text-align
:
center
;
text-align
:
center
;
position
:
relative
;
}
}
.TabTitle
{
.TabTitle
{
...
@@ -151,6 +152,13 @@ body {
...
@@ -151,6 +152,13 @@ body {
background
:
rgb
(
91
,
145
,
255
);
background
:
rgb
(
91
,
145
,
255
);
}
}
.Other
{
display
:
none
;
margin-top
:
20
!important
;
margin-bottom
:
20
!important
;
margin
:
auto
;
}
.Checkbox
{
.Checkbox
{
width
:
46%
;
width
:
46%
;
float
:
left
;
float
:
left
;
...
@@ -178,8 +186,21 @@ body {
...
@@ -178,8 +186,21 @@ body {
margin
:
auto
;
margin
:
auto
;
}
}
.Other
{
.SendButton
{
display
:
none
;
position
:
absolute
;
bottom
:
5%
;
left
:
42%
;
width
:
16%
;
height
:
30px
;
font-size
:
15px
;
background-color
:
white
;
border-radius
:
2px
;
border
:
2px
solid
var
(
--Color
);
}
.Required
{
font-weight
:
bold
;
color
:
red
;
}
}
@media
(
max-width
:
500px
)
{
@media
(
max-width
:
500px
)
{
...
...
templates/field.html
View file @
bbb9f7e
...
@@ -3,8 +3,10 @@
...
@@ -3,8 +3,10 @@
<div
class=
"Field"
>
<div
class=
"Field"
>
<div
class=
"FieldTitle"
>
<div
class=
"FieldTitle"
>
{{ title|title }}
{{ title|title }}
{% if required %}
<span
class=
"Required"
>
*
</span>
{% endif %}
</div>
</div>
<div
class=
"FieldInput"
>
<div
class=
"FieldInput"
>
{% if type == "select" %}
{% if type == "select" %}
<select
onchange=
"Otro(this)"
{%
if
required
%}
required
{%
endif
%}
>
<select
onchange=
"Otro(this)"
{%
if
required
%}
required
{%
endif
%}
>
...
...
templates/form.html
View file @
bbb9f7e
...
@@ -9,21 +9,20 @@
...
@@ -9,21 +9,20 @@
<title>
Generador de Formularios
</title>
<title>
Generador de Formularios
</title>
<link
rel=
"icon"
href=
"{{url_for('static',filename='Assets/ICONO ANACSOFT 48 SIN TRASNSF.png')}}"
type=
"image/png"
>
<link
rel=
"icon"
href=
"{{url_for('static',filename='Assets/ICONO ANACSOFT 48 SIN TRASNSF.png')}}"
type=
"image/png"
>
</head>
</head>
<body
onload=
"HabilitarTab(
<body
onload=
"
document.getElementById('BotonesContainer'),
HabilitarTab(document.getElementById('TabsContainer'),0),
document.getElementById('TabsContainer'),
HabilitarButton(document.getElementById('BotonesContainer'),0),
0
document.documentElement.style.setProperty('--Color','{{ color }}'),
), document.documentElement.style.setProperty('--Color','{{ color }}')"
>
Boton(document.getElementById('TabsContainer'), '{{ send }}')"
>
<h1
style=
"margin: 2% 0% 2% 0; font-size: 5vw;"
>
{{ title|title }}
</h1>
<h1
style=
"margin: 2% 0% 2% 0; font-size: 5vw;"
>
{{ title|title }}
</h1>
<section
id=
"BotonesContainer"
class=
"BotonesContainer"
>
<section
id=
"BotonesContainer"
class=
"BotonesContainer"
>
{% set m = tabs|length %}
{% set m = tabs|length %}
{% for i in range(m) %}
{% for i in range(m) %}
<button
id=
"Boton{{i}}"
class=
"Botones"
style=
"width: {{ 100/m }}%;"
<button
id=
"Boton{{i}}"
class=
"Botones"
style=
"width: {{ 100/m }}%;"
onclick=
"HabilitarTab(
onclick=
"
document.getElementById('BotonesContainer'),
HabilitarTab(document.getElementById('TabsContainer'),parseInt(this.id[this.id.length - 1])),
document.getElementById('TabsContainer'),
HabilitarButton(document.getElementById('BotonesContainer'),parseInt(this.id[this.id.length - 1]))"
>
parseInt(this.id[this.id.length - 1])
)"
>
{% endfor %}
{% endfor %}
</section>
</section>
<section
id=
"TabsContainer"
>
<section
id=
"TabsContainer"
>
...
...
templates/generate.html
deleted
100644 → 0
View file @
031e496
<html
lang=
"en"
>
<head>
<script
src=
"{{url_for('static',filename='Scripts/construct.js')}}"
></script>
<script
src=
"{{url_for('static',filename='Scripts/ArmadoDeForm.js')}}"
></script>
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"ie=edge"
>
<link
rel=
"stylesheet"
href=
"{{url_for('static',filename='Style/Templates.css')}}"
>
<link
rel=
"stylesheet"
href=
"{{url_for('static',filename='Style/ArmadoDeForm.css')}}"
>
<link
href=
"https://fonts.googleapis.com/css?family=Montserrat&display=swap"
rel=
"stylesheet"
>
<title>
Generador de Formularios
</title>
<link
rel=
"icon"
href=
"{{url_for('static',filename='Assets/ICONO ANACSOFT 48 SIN TRASNSF.png')}}"
type=
"image/png"
>
</head>
<body>
<section
id=
"ArmarFormContainer"
>
<h1>
Generador de Formularios
</h1>
<h3>
Para comenzar por favor cree un nuevo tab
</h3>
<br>
<div
id=
"tabs"
></div>
<div
style=
"text-align: center;"
>
<button
class=
"NewTabButton"
onclick=
"let t = document.getElementById('tabs'); addTab(t, maxChild(t, accessNumber) + 1)"
>
+
</button>
</div>
<p
id=
"ErrorMessage"
style=
"font-weight: bold; text-align: center; color: red;"
></p>
<h3
id=
"ContinuarText"
>
Cuando se encuentre conforme con el formulario presione Continuar
</h3>
<button
id=
"ContinuarButton"
onclick=
"ArmarForm( generate(document.getElementById('tabs')) )"
>
Continuar
</button>
</section>
<section
id=
"FormularioResultante"
style=
"display: none;"
>
<h1
style=
"margin: 2% 0% 2% 0;"
>
Su Formulario Ya Esta Listo!
</h1>
<section
id=
"BotonesContainer"
class=
"BotonesContainer"
>
</section>
<section
id=
"TabsContainer"
>
</section>
<div
class=
"BotonesContainer BotonResultCont"
>
<button
class=
"BotonFormResultante"
>
Editar
</button>
<button
class=
"BotonFormResultante"
style=
"background-color: var(--Color);"
>
Continuar
</button>
</div>
</section>
</body>
<!-- templates -->
<template
id=
"tabTemplate"
>
<div
class=
"TabCards"
>
<input
type=
"number"
class=
"TabNumber"
onchange=
"sortChildren(getAncestorByAttribute(this,'name','Tab').parentElement, accessNumber)"
>
<input
class=
"TabTitle"
placeholder=
"Titulo del Tab"
type=
"text"
>
<button
class=
"RemoveTabButton"
onclick=
"removeTab(this)"
>
X
</button>
<br>
<div
style=
"text-align: center;"
>
<button
class=
"NewInputButton"
onclick=
"addField(this, maxChild(this.parentElement,accessNumber) + 1)"
>
+
</button>
</div>
</div>
</template>
<template
id=
"fieldTemplate"
>
<div
class=
"InputContainer"
>
<input
type=
"number"
class=
"InputNumber"
onchange=
"sortChildren(getAncestorByAttribute(this,'name','Field').parentElement, accessNumber)"
>
<input
type=
"text"
id=
"Titulo"
class=
"Input"
placeholder=
"Título"
>
<select
name=
"input"
class=
"Input"
>
<option
disabled
selected
value=
""
>
Input
</option>
<option
id=
"text"
value=
"text"
>
Texto
</option>
<option
id=
"number"
value=
"number"
>
Numero
</option>
<option
id=
"mail"
value=
"mail"
>
Mail
</option>
<option
id=
"password"
value=
"password"
>
Contraseña
</option>
<option
id=
"date"
value=
"date"
>
Fecha
</option>
<option
id=
"time"
value=
"time"
>
Hora
</option>
<option
id=
"textarea"
value=
"textarea"
>
Párrafo
</option>
<option
id=
"checkbox"
value=
"checkbox"
>
Selección
</option>
</select>
Obligatorio :
<input
type=
"checkbox"
>
<button
class=
"RemoveInputButton"
onclick=
"let f = getAncestorByAttribute(this,'name','Field'); f.parentElement.removeChild(f)"
>
-
</button>
</div>
</template>
</html>
\ No newline at end of file
\ No newline at end of file
templates/login.html
0 → 100644
View file @
bbb9f7e
<html
lang=
"en"
>
<head>
<script
src=
"{{url_for('static',filename='Scripts/ArmadoDeForm.js')}}"
></script>
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"ie=edge"
>
<link
rel=
"stylesheet"
href=
"{{url_for('static',filename='Style/ArmadoDeForm.css')}}"
>
<link
href=
"https://fonts.googleapis.com/css?family=Montserrat&display=swap"
rel=
"stylesheet"
>
<title>
Generador de Formularios
</title>
<link
rel=
"icon"
href=
"{{url_for('static',filename='Assets/ICONO ANACSOFT 48 SIN TRASNSF.png')}}"
type=
"image/png"
>
</head>
<body
onload=
"
HabilitarTab(document.getElementById('TabsContainer'),0),
document.documentElement.style.setProperty('--Color','{{ color }}'),
Boton(document.getElementById('TabsContainer'), '{{ send }}')"
>
<section
id=
"TabsContainer"
>
{% from "tab.html" import tab %}
<div
id=
"Tab0"
class=
"Tabs"
>
<div
class=
"TabCard"
>
{{ tab(title=title, fields=fields) }}
</div>
</div>
</section>
</body>
</html>
\ No newline at end of file
\ No newline at end of file
Write
Preview
Styling with
Markdown
is supported
Attach a file
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 post a comment