Merge branch 'develop' into fix-warning-message

This commit is contained in:
Gervwyk 2021-05-03 15:42:05 +02:00 committed by GitHub
commit 6a3a7af2cf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 1298 additions and 284 deletions

11
.pnp.cjs generated
View File

@ -6195,7 +6195,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["mssql", "npm:6.3.1"],
["mysql", "npm:2.18.1"],
["openid-client", "npm:4.6.0"],
["oracledb", "npm:5.1.0"],
["pg", "virtual:dddca670fd0b7758fb2e1b1a3e18ac7ebd1ecd06ecdd7acec2b78bccf1d35802cb22904bfbb233b16515a81f5cb819421786d20887823d98022b367036c1ad51#npm:8.5.1"],
["saslprep", "npm:1.0.3"],
["sqlite3", "virtual:dddca670fd0b7758fb2e1b1a3e18ac7ebd1ecd06ecdd7acec2b78bccf1d35802cb22904bfbb233b16515a81f5cb819421786d20887823d98022b367036c1ad51#npm:5.0.2"],
@ -20738,7 +20737,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
["mysql", "npm:2.18.1"],
["opener", "npm:1.5.2"],
["ora", "npm:5.3.0"],
["oracledb", "npm:5.1.0"],
["pg", "virtual:dddca670fd0b7758fb2e1b1a3e18ac7ebd1ecd06ecdd7acec2b78bccf1d35802cb22904bfbb233b16515a81f5cb819421786d20887823d98022b367036c1ad51#npm:8.5.1"],
["react", "npm:17.0.2"],
["react-dom", "virtual:22157ea722f8d6428f1fcf0a6f7f6c7d6b902d9c785256c60a65fe6cd0db76ebccc7c1457ee047df0ba6909ff018e300c4f4957a60f5b670089810dfc417af9b#npm:17.0.2"],
@ -22908,15 +22906,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
"linkType": "HARD",
}]
]],
["oracledb", [
["npm:5.1.0", {
"packageLocation": "./.yarn/unplugged/oracledb-npm-5.1.0-5af4450ffa/node_modules/oracledb/",
"packageDependencies": [
["oracledb", "npm:5.1.0"]
],
"linkType": "HARD",
}]
]],
["original", [
["npm:1.0.2", {
"packageLocation": "./.yarn/cache/original-npm-1.0.2-2250635ba0-6918b9d454.zip/node_modules/original/",

View File

@ -1,6 +1,6 @@
- id: default
type: PageHeaderMenu
- id: "properties.menu"
- id: 'properties.menu'
type: PageHeaderMenu
properties:
menu:
@ -17,11 +17,11 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.logo.src"
- id: 'properties.logo.src'
type: PageHeaderMenu
properties:
logo:
src: "https://lowdefy.com/logos/name_250.png"
src: 'https://lowdefy.com/logos/name_250.png'
menu:
links:
- id: Introduction
@ -36,11 +36,11 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.logo.alt"
- id: 'properties.logo.alt'
type: PageHeaderMenu
properties:
logo:
alt: "Header logo alt text"
alt: 'Header logo alt text'
menu:
links:
- id: Introduction
@ -55,17 +55,55 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.logo.style"
- id: 'properties.logo.style'
type: PageHeaderMenu
properties:
logo:
style:
style:
border: 5px solid blue
- id: "properties.header.color"
- id: 'properties.logo.srcset'
type: PageHeaderMenu
properties:
logo:
srcset: 'https://lowdefy.com/logos/name_250.png 250w, https://lowdefy.com/logos/name_450.png 450w'
menu:
links:
- id: Introduction
type: MenuLink
pageId: introduction
properties:
icon: RocketOutlined
title: Introduction
- id: alert
type: MenuLink
pageId: Alert
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: 'properties.logo.size'
type: PageHeaderMenu
properties:
logo:
srcset: '(max-width: 767px) 40px, 768px'
menu:
links:
- id: Introduction
type: MenuLink
pageId: introduction
properties:
icon: RocketOutlined
title: Introduction
- id: alert
type: MenuLink
pageId: Alert
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: 'properties.header.color'
type: PageHeaderMenu
properties:
header:
color: "#3f51b5"
color: '#3f51b5'
menu:
links:
- id: Introduction
@ -80,7 +118,7 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.header.theme: light"
- id: 'properties.header.theme: light'
type: PageHeaderMenu
properties:
header:
@ -99,42 +137,42 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.breadcrumb.list"
- id: 'properties.breadcrumb.list'
type: PageHeaderMenu
properties:
breadcrumb:
list:
list:
- Link one
- Link two
- id: "properties.footer.style"
- id: 'properties.footer.style'
type: PageHeaderMenu
properties:
footer:
style:
style:
border: 5px solid blue
areas:
footer:
blocks: []
- id: "properties.content.style"
- id: 'properties.content.style'
type: PageHeaderMenu
properties:
content:
style:
style:
border: 5px solid blue
areas:
content:
blocks: []
- id: "properties.header.style"
- id: 'properties.header.style'
type: PageHeaderMenu
properties:
header:
style:
style:
border: 5px solid blue
areas:
header:
blocks: []
- id: "properties.style"
- id: 'properties.style'
type: PageHeaderMenu
properties:
style:
border: 5px solid blue
style:
border: 5px solid blue

View File

@ -1,6 +1,6 @@
- id: default
type: PageSiderMenu
- id: "properties.menu"
- id: 'properties.menu'
type: PageSiderMenu
properties:
menu:
@ -17,11 +17,11 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.logo.src"
- id: 'properties.logo.src'
type: PageHeaderMenu
properties:
logo:
src: "https://lowdefy.com/logos/name_250.png"
src: 'https://lowdefy.com/logos/name_250.png'
menu:
links:
- id: Introduction
@ -36,11 +36,11 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.logo.alt"
- id: 'properties.logo.alt'
type: PageHeaderMenu
properties:
logo:
alt: "Header logo alt text"
alt: 'Header logo alt text'
menu:
links:
- id: Introduction
@ -55,17 +55,55 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.logo.style"
- id: 'properties.logo.style'
type: PageHeaderMenu
properties:
logo:
style:
style:
border: 5px solid blue
- id: "properties.header.color"
- id: 'properties.logo.srcset'
type: PageHeaderMenu
properties:
logo:
srcset: 'https://lowdefy.com/logos/name_250.png 250w, https://lowdefy.com/logos/name_450.png 450w'
menu:
links:
- id: Introduction
type: MenuLink
pageId: introduction
properties:
icon: RocketOutlined
title: Introduction
- id: alert
type: MenuLink
pageId: Alert
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: 'properties.logo.size'
type: PageHeaderMenu
properties:
logo:
srcset: '(max-width: 767px) 40px, 768px'
menu:
links:
- id: Introduction
type: MenuLink
pageId: introduction
properties:
icon: RocketOutlined
title: Introduction
- id: alert
type: MenuLink
pageId: Alert
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: 'properties.header.color'
type: PageSiderMenu
properties:
header:
color: "#3f51b5"
color: '#3f51b5'
menu:
links:
- id: Introduction
@ -80,11 +118,11 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.sider.color"
- id: 'properties.sider.color'
type: PageSiderMenu
properties:
sider:
color: "#faf7a5"
color: '#faf7a5'
menu:
links:
- id: Introduction
@ -99,13 +137,13 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.menu.selectedColor"
- id: 'properties.menu.selectedColor'
type: PageSiderMenu
properties:
sider:
color: "#faf7a5"
color: '#faf7a5'
menu:
selectedColor: "#a30331"
selectedColor: '#a30331'
links:
- id: Introduction
type: MenuLink
@ -119,7 +157,7 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.header.theme: light"
- id: 'properties.header.theme: light'
type: PageSiderMenu
properties:
header:
@ -138,41 +176,41 @@
properties:
icon: ExclamationCircleOutlined
title: Alert
- id: "properties.breadcrumb.list"
- id: 'properties.breadcrumb.list'
type: PageHeaderMenu
properties:
breadcrumb:
list:
list:
- Link one
- Link two
- id: "properties.footer.style"
- id: 'properties.footer.style'
type: PageHeaderMenu
properties:
footer:
style:
style:
border: 5px solid blue
areas:
footer:
blocks: []
- id: "properties.content.style"
- id: 'properties.content.style'
type: PageHeaderMenu
properties:
content:
style:
style:
border: 5px solid blue
areas:
content:
blocks: []
- id: "properties.sider.style"
- id: 'properties.sider.style'
type: PageHeaderMenu
properties:
sider:
style:
style:
border: 5px solid blue
areas:
sider:
blocks: []
- id: "properties.sider.initialCollapsed"
- id: 'properties.sider.initialCollapsed'
type: PageHeaderMenu
properties:
sider:
@ -180,12 +218,12 @@
areas:
sider:
blocks: []
- id: "properties.sider.theme: dark"
- id: 'properties.sider.theme: dark'
type: PageHeaderMenu
properties:
sider:
theme: dark
- id: "properties.toggleSiderButton.type"
- id: 'properties.toggleSiderButton.type'
type: PageHeaderMenu
properties:
toggleSiderButton:
@ -193,7 +231,7 @@
areas:
sider:
blocks: []
- id: "properties.toggleSiderButton.hide"
- id: 'properties.toggleSiderButton.hide'
type: PageHeaderMenu
properties:
toggleSiderButton:
@ -201,17 +239,17 @@
areas:
sider:
blocks: []
- id: "properties.header.style"
- id: 'properties.header.style'
type: PageHeaderMenu
properties:
header:
style:
style:
border: 5px solid blue
areas:
header:
blocks: []
- id: "properties.style"
- id: 'properties.style'
type: PageHeaderMenu
properties:
style:
border: 5px solid blue
style:
border: 5px solid blue

View File

@ -56,12 +56,10 @@ const PageHeaderMenu = ({
justifyContent: 'flex-end',
},
logo: {
width: 130,
margin: '0px 30px',
flex: '0 1 auto',
sm: { margin: '0 10px' },
md: { margin: '0 15px' },
lg: { margin: '0 30px' },
},
lgMenu: {
flex: '1 1 auto',
@ -124,8 +122,28 @@ const PageHeaderMenu = ({
? '/public/logo-light-theme.png'
: '/public/logo-dark-theme.png')
}
srcset={
(properties.logo && (properties.logo.srcset || properties.logo.src)) ||
(get(properties, 'header.theme') === 'light'
? '/public/logo-square-light-theme.png 40w, /public/logo-light-theme.png 768w'
: '/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w')
}
sizes={
(properties.logo && properties.logo.sizes) ||
'(max-width: 767px) 40px, 768px'
}
alt={(properties.logo && properties.logo.alt) || 'Lowdefy'}
className={methods.makeCssClass([
{
width: 130,
sm: {
width:
properties.logo && properties.logo.src && !properties.logo.srcset
? 130
: 40,
},
md: { width: 130 },
},
styles.logo,
properties.logo && properties.logo.style,
])}

View File

@ -34,6 +34,14 @@
"type": "string",
"description": "Header logo source url."
},
"srcset": {
"type": "string",
"description": "Header logo srcset for logo img element."
},
"size": {
"type": "string",
"description": "Header logo size for logo img element."
},
"alt": {
"type": "string",
"default": "Lowdefy",

View File

@ -71,7 +71,6 @@ const PageSiderMenu = ({
justifyContent: 'flex-end',
},
logo: {
width: 130,
margin: '0 30px 0 0',
flex: '0 1 auto',
sm: { margin: '0 10px 0 0' },
@ -169,8 +168,28 @@ const PageSiderMenu = ({
? '/public/logo-light-theme.png'
: '/public/logo-dark-theme.png')
}
srcset={
(properties.logo && (properties.logo.srcset || properties.logo.src)) ||
(get(properties, 'header.theme') === 'light'
? '/public/logo-square-light-theme.png 40w, /public/logo-light-theme.png 768w'
: '/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w')
}
sizes={
(properties.logo && properties.logo.sizes) ||
'(max-width: 767px) 40px, 768px'
}
alt={(properties.logo && properties.logo.alt) || 'Lowdefy'}
className={methods.makeCssClass([
{
width: 130,
sm: {
width:
properties.logo && properties.logo.src && !properties.logo.srcset
? 130
: 40,
},
md: { width: 130 },
},
styles.logo,
properties.logo && properties.logo.style,
])}

View File

@ -44,6 +44,14 @@
"type": "string",
"description": "Header logo source url."
},
"srcset": {
"type": "string",
"description": "Header logo srcset for logo img element."
},
"size": {
"type": "string",
"description": "Header logo size for logo img element."
},
"alt": {
"type": "string",
"default": "Lowdefy",

View File

@ -871,6 +871,110 @@ Array [
]
`;
exports[`Mock render - properties.logo.size - value[0] - default 1`] = `
Array [
Array [
Object {
"children": <React.Fragment>
<HeaderBlock
blockId="properties.logo.size_header"
content={
Object {
"content": [Function],
}
}
events={Object {}}
list={Array []}
menus={Array []}
methods={
Object {
"makeCssClass": [Function],
"registerEvent": [Function],
"registerMethod": [Function],
"triggerEvent": [Function],
}
}
properties={
Object {
"style": Object {
"alignItems": "center",
"display": "flex",
"lg": Object {
"padding": "0 46px",
},
"md": Object {
"padding": "0 30px",
},
"padding": "0 46px",
"sm": Object {
"padding": "0 15px",
},
},
}
}
required={false}
user={Object {}}
validation={
Object {
"errors": Array [],
"status": null,
"warnings": Array [],
}
}
/>
<ContentBlock
blockId="properties.logo.size_content"
content={
Object {
"content": [Function],
}
}
events={Object {}}
list={Array []}
menus={Array []}
methods={
Object {
"makeCssClass": [Function],
"registerEvent": [Function],
"registerMethod": [Function],
"triggerEvent": [Function],
}
}
properties={
Object {
"style": Object {
"lg": Object {
"padding": "0 40px 40px 40px",
},
"md": Object {
"padding": "0 20px 20px 20px",
},
"padding": "0 40px 40px 40px",
"sm": Object {
"padding": "0 10px 10px 10px",
},
},
}
}
required={false}
user={Object {}}
validation={
Object {
"errors": Array [],
"status": null,
"warnings": Array [],
}
}
/>
</React.Fragment>,
"className": "css-vooagt",
"id": "properties.logo.size",
},
Object {},
],
]
`;
exports[`Mock render - properties.logo.src - value[0] - default 1`] = `
Array [
Array [
@ -975,6 +1079,110 @@ Array [
]
`;
exports[`Mock render - properties.logo.srcset - value[0] - default 1`] = `
Array [
Array [
Object {
"children": <React.Fragment>
<HeaderBlock
blockId="properties.logo.srcset_header"
content={
Object {
"content": [Function],
}
}
events={Object {}}
list={Array []}
menus={Array []}
methods={
Object {
"makeCssClass": [Function],
"registerEvent": [Function],
"registerMethod": [Function],
"triggerEvent": [Function],
}
}
properties={
Object {
"style": Object {
"alignItems": "center",
"display": "flex",
"lg": Object {
"padding": "0 46px",
},
"md": Object {
"padding": "0 30px",
},
"padding": "0 46px",
"sm": Object {
"padding": "0 15px",
},
},
}
}
required={false}
user={Object {}}
validation={
Object {
"errors": Array [],
"status": null,
"warnings": Array [],
}
}
/>
<ContentBlock
blockId="properties.logo.srcset_content"
content={
Object {
"content": [Function],
}
}
events={Object {}}
list={Array []}
menus={Array []}
methods={
Object {
"makeCssClass": [Function],
"registerEvent": [Function],
"registerMethod": [Function],
"triggerEvent": [Function],
}
}
properties={
Object {
"style": Object {
"lg": Object {
"padding": "0 40px 40px 40px",
},
"md": Object {
"padding": "0 20px 20px 20px",
},
"padding": "0 40px 40px 40px",
"sm": Object {
"padding": "0 10px 10px 10px",
},
},
}
}
required={false}
user={Object {}}
validation={
Object {
"errors": Array [],
"status": null,
"warnings": Array [],
}
}
/>
</React.Fragment>,
"className": "css-vooagt",
"id": "properties.logo.srcset",
},
Object {},
],
]
`;
exports[`Mock render - properties.logo.style - value[0] - default 1`] = `
Array [
Array [

View File

@ -32,10 +32,18 @@ exports[`Test Schema properties.logo.alt 1`] = `true`;
exports[`Test Schema properties.logo.alt 2`] = `null`;
exports[`Test Schema properties.logo.size 1`] = `true`;
exports[`Test Schema properties.logo.size 2`] = `null`;
exports[`Test Schema properties.logo.src 1`] = `true`;
exports[`Test Schema properties.logo.src 2`] = `null`;
exports[`Test Schema properties.logo.srcset 1`] = `true`;
exports[`Test Schema properties.logo.srcset 2`] = `null`;
exports[`Test Schema properties.logo.style 1`] = `true`;
exports[`Test Schema properties.logo.style 2`] = `null`;

View File

@ -723,6 +723,96 @@ Array [
]
`;
exports[`Mock render - properties.logo.size - value[0] - default 1`] = `
Array [
Array [
Object {
"children": <React.Fragment>
<HeaderBlock
blockId="properties.logo.size_header"
content={
Object {
"content": [Function],
}
}
events={Object {}}
list={Array []}
menus={Array []}
methods={
Object {
"makeCssClass": [Function],
"registerEvent": [Function],
"registerMethod": [Function],
"triggerEvent": [Function],
}
}
properties={
Object {
"style": Object {
"alignItems": "center",
"display": "flex",
"flexDirection": "row-reverse",
"lg": Object {
"padding": "0 46px",
},
"md": Object {
"padding": "0 30px",
},
"padding": "0 46px",
"sm": Object {
"padding": "0 15px",
},
},
}
}
required={false}
user={Object {}}
validation={
Object {
"errors": Array [],
"status": null,
"warnings": Array [],
}
}
/>
<LayoutBlock
blockId="properties.logo.size_layout"
content={
Object {
"content": [Function],
}
}
events={Object {}}
list={Array []}
menus={Array []}
methods={
Object {
"makeCssClass": [Function],
"registerEvent": [Function],
"registerMethod": [Function],
"triggerEvent": [Function],
}
}
properties={Object {}}
required={false}
user={Object {}}
validation={
Object {
"errors": Array [],
"status": null,
"warnings": Array [],
}
}
/>
</React.Fragment>,
"className": "css-vooagt",
"id": "properties.logo.size",
},
Object {},
],
]
`;
exports[`Mock render - properties.logo.src - value[0] - default 1`] = `
Array [
Array [
@ -813,6 +903,96 @@ Array [
]
`;
exports[`Mock render - properties.logo.srcset - value[0] - default 1`] = `
Array [
Array [
Object {
"children": <React.Fragment>
<HeaderBlock
blockId="properties.logo.srcset_header"
content={
Object {
"content": [Function],
}
}
events={Object {}}
list={Array []}
menus={Array []}
methods={
Object {
"makeCssClass": [Function],
"registerEvent": [Function],
"registerMethod": [Function],
"triggerEvent": [Function],
}
}
properties={
Object {
"style": Object {
"alignItems": "center",
"display": "flex",
"flexDirection": "row-reverse",
"lg": Object {
"padding": "0 46px",
},
"md": Object {
"padding": "0 30px",
},
"padding": "0 46px",
"sm": Object {
"padding": "0 15px",
},
},
}
}
required={false}
user={Object {}}
validation={
Object {
"errors": Array [],
"status": null,
"warnings": Array [],
}
}
/>
<LayoutBlock
blockId="properties.logo.srcset_layout"
content={
Object {
"content": [Function],
}
}
events={Object {}}
list={Array []}
menus={Array []}
methods={
Object {
"makeCssClass": [Function],
"registerEvent": [Function],
"registerMethod": [Function],
"triggerEvent": [Function],
}
}
properties={Object {}}
required={false}
user={Object {}}
validation={
Object {
"errors": Array [],
"status": null,
"warnings": Array [],
}
}
/>
</React.Fragment>,
"className": "css-vooagt",
"id": "properties.logo.srcset",
},
Object {},
],
]
`;
exports[`Mock render - properties.logo.style - value[0] - default 1`] = `
Array [
Array [

View File

@ -55,8 +55,10 @@ exports[`Render default - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -228,8 +230,10 @@ exports[`Render properties.breadcrumb.list - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -435,8 +439,10 @@ exports[`Render properties.content.style - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -608,8 +614,10 @@ exports[`Render properties.footer.style - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -796,8 +804,10 @@ exports[`Render properties.header.color - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -1066,8 +1076,10 @@ exports[`Render properties.header.style - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -1239,8 +1251,10 @@ exports[`Render properties.header.theme: light - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-light-theme.png"
srcset="/public/logo-square-light-theme.png 40w, /public/logo-light-theme.png 768w"
/>
</a>
</header>
@ -1499,8 +1513,10 @@ exports[`Render properties.logo.alt - value[0] 1`] = `
>
<img
alt="Header logo alt text"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -1704,6 +1720,268 @@ exports[`Render properties.logo.alt - value[0] 1`] = `
</section>
`;
exports[`Render properties.logo.size - value[0] 1`] = `
<section
className="ant-layout {\\"style\\":{\\"minHeight\\":\\"100vh\\"}}"
id="properties.logo.size"
>
<header
className="ant-layout-header {\\"style\\":[{\\"backgroundColor\\":false},null,{\\"display\\":\\"flex\\",\\"alignItems\\":\\"center\\",\\"padding\\":\\"0 46px\\",\\"sm\\":{\\"padding\\":\\"0 15px\\"},\\"md\\":{\\"padding\\":\\"0 30px\\"},\\"lg\\":{\\"padding\\":\\"0 46px\\"},\\"flexDirection\\":\\"row-reverse\\"}]} hide-on-print"
id="properties.logo.size_header"
>
<div
className="{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flex\\":\\"1 1 auto\\",\\"display\\":\\"flex\\",\\"justifyContent\\":\\"flex-end\\"}}"
>
<div
className="{\\"style\\":[{\\"display\\":\\"block\\",\\"lg\\":{\\"display\\":\\"none\\"}},{\\"paddingLeft\\":\\"1rem\\"}]}"
>
<div
id="properties.logo.size_mobile_menu"
>
<button
className="ant-btn {\\"style\\":[{},null]} ant-btn-primary ant-btn-icon-only"
id="properties.logo.size_mobile_menu_button"
onClick={[Function]}
type="button"
>
<span
aria-label="loading-3-quarters"
className="anticon anticon-loading-3-quarters anticon-spin {\\"style\\":[{},null]}"
id="properties.logo.size_mobile_menu_button_icon"
required={false}
role="img"
>
<svg
aria-hidden="true"
data-icon="loading-3-quarters"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512 1024c-69.1 0-136.2-13.5-199.3-40.2C251.7 958 197 921 150 874c-47-47-84-101.7-109.8-162.7C13.5 648.2 0 581.1 0 512c0-19.9 16.1-36 36-36s36 16.1 36 36c0 59.4 11.6 117 34.6 171.3 22.2 52.4 53.9 99.5 94.3 139.9 40.4 40.4 87.5 72.2 139.9 94.3C395 940.4 452.6 952 512 952c59.4 0 117-11.6 171.3-34.6 52.4-22.2 99.5-53.9 139.9-94.3 40.4-40.4 72.2-87.5 94.3-139.9C940.4 629 952 571.4 952 512c0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.2C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3s-13.5 136.2-40.2 199.3C958 772.3 921 827 874 874c-47 47-101.8 83.9-162.7 109.7-63.1 26.8-130.2 40.3-199.3 40.3z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
<a
href="/undefined"
onClick={[Function]}
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="(max-width: 767px) 40px, 768px"
/>
</a>
</header>
<section
className="ant-layout ant-layout-has-sider {}"
id="properties.logo.size_layout"
>
<aside
className="{\\"style\\":[{\\"overflow\\":\\"auto\\"},{\\"display\\":\\"none\\",\\"lg\\":{\\"display\\":\\"block\\"}}]} hide-on-print ant-layout-sider ant-layout-sider-light"
id="properties.logo.size_sider"
style={
Object {
"flex": "0 0 200px",
"maxWidth": "200px",
"minWidth": "200px",
"width": "200px",
}
}
>
<div
className="ant-layout-sider-children"
>
<div
style={
Object {
"display": "flex",
"flexDirection": "column",
"height": "100%",
}
}
>
<ul
className="ant-menu {\\"style\\":[{\\"lineHeight\\":\\"64px\\",\\"display\\":false},null,null,null,{\\"display\\":\\"none\\",\\"lg\\":{\\"display\\":\\"block\\"}}]} ant-menu-light ant-menu-root ant-menu-inline"
id="properties.logo.size_menu"
onMouseEnter={[Function]}
onTransitionEnd={[Function]}
role="menu"
style={Object {}}
>
<li
className="ant-menu-item"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
role="menuitem"
style={
Object {
"paddingLeft": 24,
}
}
>
<span
aria-label="loading-3-quarters"
className="anticon anticon-loading-3-quarters anticon-spin {\\"style\\":[{},null]}"
id="Introduction_icon"
required={false}
role="img"
>
<svg
aria-hidden="true"
data-icon="loading-3-quarters"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512 1024c-69.1 0-136.2-13.5-199.3-40.2C251.7 958 197 921 150 874c-47-47-84-101.7-109.8-162.7C13.5 648.2 0 581.1 0 512c0-19.9 16.1-36 36-36s36 16.1 36 36c0 59.4 11.6 117 34.6 171.3 22.2 52.4 53.9 99.5 94.3 139.9 40.4 40.4 87.5 72.2 139.9 94.3C395 940.4 452.6 952 512 952c59.4 0 117-11.6 171.3-34.6 52.4-22.2 99.5-53.9 139.9-94.3 40.4-40.4 72.2-87.5 94.3-139.9C940.4 629 952 571.4 952 512c0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.2C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3s-13.5 136.2-40.2 199.3C958 772.3 921 827 874 874c-47 47-101.8 83.9-162.7 109.7-63.1 26.8-130.2 40.3-199.3 40.3z"
/>
</svg>
</span>
<span>
<a
className="{\\"style\\":[null]}"
href="/introduction"
onClick={[Function]}
>
Introduction
</a>
</span>
</li>
<li
className="ant-menu-item"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
role="menuitem"
style={
Object {
"paddingLeft": 24,
}
}
>
<span
aria-label="loading-3-quarters"
className="anticon anticon-loading-3-quarters anticon-spin {\\"style\\":[{},null]}"
id="alert_icon"
required={false}
role="img"
>
<svg
aria-hidden="true"
data-icon="loading-3-quarters"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512 1024c-69.1 0-136.2-13.5-199.3-40.2C251.7 958 197 921 150 874c-47-47-84-101.7-109.8-162.7C13.5 648.2 0 581.1 0 512c0-19.9 16.1-36 36-36s36 16.1 36 36c0 59.4 11.6 117 34.6 171.3 22.2 52.4 53.9 99.5 94.3 139.9 40.4 40.4 87.5 72.2 139.9 94.3C395 940.4 452.6 952 512 952c59.4 0 117-11.6 171.3-34.6 52.4-22.2 99.5-53.9 139.9-94.3 40.4-40.4 72.2-87.5 94.3-139.9C940.4 629 952 571.4 952 512c0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.2C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3s-13.5 136.2-40.2 199.3C958 772.3 921 827 874 874c-47 47-101.8 83.9-162.7 109.7-63.1 26.8-130.2 40.3-199.3 40.3z"
/>
</svg>
</span>
<span>
<a
className="{\\"style\\":[null]}"
href="/Alert"
onClick={[Function]}
>
Alert
</a>
</span>
</li>
</ul>
<div
style={
Object {
"flex": "1 0 auto",
}
}
/>
<div
className="{}"
id="properties.logo.size_toggle_sider_affix"
>
<div
className=""
>
<div
style={
Object {
"background": "white",
}
}
>
<button
className="ant-btn {\\"style\\":[{},null]} ant-btn-link ant-btn-icon-only ant-btn-block"
id="properties.logo.size_toggle_sider"
onClick={[Function]}
type="button"
>
<span
aria-label="loading-3-quarters"
className="anticon anticon-loading-3-quarters anticon-spin {\\"style\\":[{},null]}"
id="properties.logo.size_toggle_sider_icon"
required={false}
role="img"
>
<svg
aria-hidden="true"
data-icon="loading-3-quarters"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512 1024c-69.1 0-136.2-13.5-199.3-40.2C251.7 958 197 921 150 874c-47-47-84-101.7-109.8-162.7C13.5 648.2 0 581.1 0 512c0-19.9 16.1-36 36-36s36 16.1 36 36c0 59.4 11.6 117 34.6 171.3 22.2 52.4 53.9 99.5 94.3 139.9 40.4 40.4 87.5 72.2 139.9 94.3C395 940.4 452.6 952 512 952c59.4 0 117-11.6 171.3-34.6 52.4-22.2 99.5-53.9 139.9-94.3 40.4-40.4 72.2-87.5 94.3-139.9C940.4 629 952 571.4 952 512c0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.2C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3s-13.5 136.2-40.2 199.3C958 772.3 921 827 874 874c-47 47-101.8 83.9-162.7 109.7-63.1 26.8-130.2 40.3-199.3 40.3z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</aside>
<main
className="ant-layout-content {\\"style\\":{\\"padding\\":\\"0 40px 40px 40px\\",\\"sm\\":{\\"padding\\":\\"0 10px 10px 10px\\"},\\"md\\":{\\"padding\\":\\"0 20px 20px 20px\\"},\\"lg\\":{\\"padding\\":\\"0 40px 40px 40px\\"}}}"
id="properties.logo.size_content"
>
<div
className="{\\"style\\":{\\"padding\\":\\"20px 0\\",\\"sm\\":{\\"padding\\":\\"5px 0\\"},\\"md\\":{\\"padding\\":\\"10px 0\\"}}}"
/>
<div
style={
Object {
"border": "1px solid red",
"padding": 10,
}
}
>
content
</div>
</main>
</section>
</section>
`;
exports[`Render properties.logo.src - value[0] 1`] = `
<section
className="ant-layout {\\"style\\":{\\"minHeight\\":\\"100vh\\"}}"
@ -1759,8 +2037,10 @@ exports[`Render properties.logo.src - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":130},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="https://lowdefy.com/logos/name_250.png"
srcset="https://lowdefy.com/logos/name_250.png"
/>
</a>
</header>
@ -1964,6 +2244,268 @@ exports[`Render properties.logo.src - value[0] 1`] = `
</section>
`;
exports[`Render properties.logo.srcset - value[0] 1`] = `
<section
className="ant-layout {\\"style\\":{\\"minHeight\\":\\"100vh\\"}}"
id="properties.logo.srcset"
>
<header
className="ant-layout-header {\\"style\\":[{\\"backgroundColor\\":false},null,{\\"display\\":\\"flex\\",\\"alignItems\\":\\"center\\",\\"padding\\":\\"0 46px\\",\\"sm\\":{\\"padding\\":\\"0 15px\\"},\\"md\\":{\\"padding\\":\\"0 30px\\"},\\"lg\\":{\\"padding\\":\\"0 46px\\"},\\"flexDirection\\":\\"row-reverse\\"}]} hide-on-print"
id="properties.logo.srcset_header"
>
<div
className="{\\"style\\":{\\"alignItems\\":\\"center\\",\\"flex\\":\\"1 1 auto\\",\\"display\\":\\"flex\\",\\"justifyContent\\":\\"flex-end\\"}}"
>
<div
className="{\\"style\\":[{\\"display\\":\\"block\\",\\"lg\\":{\\"display\\":\\"none\\"}},{\\"paddingLeft\\":\\"1rem\\"}]}"
>
<div
id="properties.logo.srcset_mobile_menu"
>
<button
className="ant-btn {\\"style\\":[{},null]} ant-btn-primary ant-btn-icon-only"
id="properties.logo.srcset_mobile_menu_button"
onClick={[Function]}
type="button"
>
<span
aria-label="loading-3-quarters"
className="anticon anticon-loading-3-quarters anticon-spin {\\"style\\":[{},null]}"
id="properties.logo.srcset_mobile_menu_button_icon"
required={false}
role="img"
>
<svg
aria-hidden="true"
data-icon="loading-3-quarters"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512 1024c-69.1 0-136.2-13.5-199.3-40.2C251.7 958 197 921 150 874c-47-47-84-101.7-109.8-162.7C13.5 648.2 0 581.1 0 512c0-19.9 16.1-36 36-36s36 16.1 36 36c0 59.4 11.6 117 34.6 171.3 22.2 52.4 53.9 99.5 94.3 139.9 40.4 40.4 87.5 72.2 139.9 94.3C395 940.4 452.6 952 512 952c59.4 0 117-11.6 171.3-34.6 52.4-22.2 99.5-53.9 139.9-94.3 40.4-40.4 72.2-87.5 94.3-139.9C940.4 629 952 571.4 952 512c0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.2C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3s-13.5 136.2-40.2 199.3C958 772.3 921 827 874 874c-47 47-101.8 83.9-162.7 109.7-63.1 26.8-130.2 40.3-199.3 40.3z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
<a
href="/undefined"
onClick={[Function]}
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="https://lowdefy.com/logos/name_250.png 250w, https://lowdefy.com/logos/name_450.png 450w"
/>
</a>
</header>
<section
className="ant-layout ant-layout-has-sider {}"
id="properties.logo.srcset_layout"
>
<aside
className="{\\"style\\":[{\\"overflow\\":\\"auto\\"},{\\"display\\":\\"none\\",\\"lg\\":{\\"display\\":\\"block\\"}}]} hide-on-print ant-layout-sider ant-layout-sider-light"
id="properties.logo.srcset_sider"
style={
Object {
"flex": "0 0 200px",
"maxWidth": "200px",
"minWidth": "200px",
"width": "200px",
}
}
>
<div
className="ant-layout-sider-children"
>
<div
style={
Object {
"display": "flex",
"flexDirection": "column",
"height": "100%",
}
}
>
<ul
className="ant-menu {\\"style\\":[{\\"lineHeight\\":\\"64px\\",\\"display\\":false},null,null,null,{\\"display\\":\\"none\\",\\"lg\\":{\\"display\\":\\"block\\"}}]} ant-menu-light ant-menu-root ant-menu-inline"
id="properties.logo.srcset_menu"
onMouseEnter={[Function]}
onTransitionEnd={[Function]}
role="menu"
style={Object {}}
>
<li
className="ant-menu-item"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
role="menuitem"
style={
Object {
"paddingLeft": 24,
}
}
>
<span
aria-label="loading-3-quarters"
className="anticon anticon-loading-3-quarters anticon-spin {\\"style\\":[{},null]}"
id="Introduction_icon"
required={false}
role="img"
>
<svg
aria-hidden="true"
data-icon="loading-3-quarters"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512 1024c-69.1 0-136.2-13.5-199.3-40.2C251.7 958 197 921 150 874c-47-47-84-101.7-109.8-162.7C13.5 648.2 0 581.1 0 512c0-19.9 16.1-36 36-36s36 16.1 36 36c0 59.4 11.6 117 34.6 171.3 22.2 52.4 53.9 99.5 94.3 139.9 40.4 40.4 87.5 72.2 139.9 94.3C395 940.4 452.6 952 512 952c59.4 0 117-11.6 171.3-34.6 52.4-22.2 99.5-53.9 139.9-94.3 40.4-40.4 72.2-87.5 94.3-139.9C940.4 629 952 571.4 952 512c0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.2C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3s-13.5 136.2-40.2 199.3C958 772.3 921 827 874 874c-47 47-101.8 83.9-162.7 109.7-63.1 26.8-130.2 40.3-199.3 40.3z"
/>
</svg>
</span>
<span>
<a
className="{\\"style\\":[null]}"
href="/introduction"
onClick={[Function]}
>
Introduction
</a>
</span>
</li>
<li
className="ant-menu-item"
onClick={[Function]}
onMouseEnter={[Function]}
onMouseLeave={[Function]}
role="menuitem"
style={
Object {
"paddingLeft": 24,
}
}
>
<span
aria-label="loading-3-quarters"
className="anticon anticon-loading-3-quarters anticon-spin {\\"style\\":[{},null]}"
id="alert_icon"
required={false}
role="img"
>
<svg
aria-hidden="true"
data-icon="loading-3-quarters"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512 1024c-69.1 0-136.2-13.5-199.3-40.2C251.7 958 197 921 150 874c-47-47-84-101.7-109.8-162.7C13.5 648.2 0 581.1 0 512c0-19.9 16.1-36 36-36s36 16.1 36 36c0 59.4 11.6 117 34.6 171.3 22.2 52.4 53.9 99.5 94.3 139.9 40.4 40.4 87.5 72.2 139.9 94.3C395 940.4 452.6 952 512 952c59.4 0 117-11.6 171.3-34.6 52.4-22.2 99.5-53.9 139.9-94.3 40.4-40.4 72.2-87.5 94.3-139.9C940.4 629 952 571.4 952 512c0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.2C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3s-13.5 136.2-40.2 199.3C958 772.3 921 827 874 874c-47 47-101.8 83.9-162.7 109.7-63.1 26.8-130.2 40.3-199.3 40.3z"
/>
</svg>
</span>
<span>
<a
className="{\\"style\\":[null]}"
href="/Alert"
onClick={[Function]}
>
Alert
</a>
</span>
</li>
</ul>
<div
style={
Object {
"flex": "1 0 auto",
}
}
/>
<div
className="{}"
id="properties.logo.srcset_toggle_sider_affix"
>
<div
className=""
>
<div
style={
Object {
"background": "white",
}
}
>
<button
className="ant-btn {\\"style\\":[{},null]} ant-btn-link ant-btn-icon-only ant-btn-block"
id="properties.logo.srcset_toggle_sider"
onClick={[Function]}
type="button"
>
<span
aria-label="loading-3-quarters"
className="anticon anticon-loading-3-quarters anticon-spin {\\"style\\":[{},null]}"
id="properties.logo.srcset_toggle_sider_icon"
required={false}
role="img"
>
<svg
aria-hidden="true"
data-icon="loading-3-quarters"
fill="currentColor"
focusable="false"
height="1em"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512 1024c-69.1 0-136.2-13.5-199.3-40.2C251.7 958 197 921 150 874c-47-47-84-101.7-109.8-162.7C13.5 648.2 0 581.1 0 512c0-19.9 16.1-36 36-36s36 16.1 36 36c0 59.4 11.6 117 34.6 171.3 22.2 52.4 53.9 99.5 94.3 139.9 40.4 40.4 87.5 72.2 139.9 94.3C395 940.4 452.6 952 512 952c59.4 0 117-11.6 171.3-34.6 52.4-22.2 99.5-53.9 139.9-94.3 40.4-40.4 72.2-87.5 94.3-139.9C940.4 629 952 571.4 952 512c0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.2C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3s-13.5 136.2-40.2 199.3C958 772.3 921 827 874 874c-47 47-101.8 83.9-162.7 109.7-63.1 26.8-130.2 40.3-199.3 40.3z"
/>
</svg>
</span>
</button>
</div>
</div>
</div>
</div>
</div>
</aside>
<main
className="ant-layout-content {\\"style\\":{\\"padding\\":\\"0 40px 40px 40px\\",\\"sm\\":{\\"padding\\":\\"0 10px 10px 10px\\"},\\"md\\":{\\"padding\\":\\"0 20px 20px 20px\\"},\\"lg\\":{\\"padding\\":\\"0 40px 40px 40px\\"}}}"
id="properties.logo.srcset_content"
>
<div
className="{\\"style\\":{\\"padding\\":\\"20px 0\\",\\"sm\\":{\\"padding\\":\\"5px 0\\"},\\"md\\":{\\"padding\\":\\"10px 0\\"}}}"
/>
<div
style={
Object {
"border": "1px solid red",
"padding": 10,
}
}
>
content
</div>
</main>
</section>
</section>
`;
exports[`Render properties.logo.style - value[0] 1`] = `
<section
className="ant-layout {\\"style\\":{\\"minHeight\\":\\"100vh\\"}}"
@ -2019,8 +2561,10 @@ exports[`Render properties.logo.style - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},{\\"border\\":\\"5px solid blue\\"}]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},{\\"border\\":\\"5px solid blue\\"}]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -2192,8 +2736,10 @@ exports[`Render properties.menu - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -2452,8 +2998,10 @@ exports[`Render properties.menu.selectedColor - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -2712,8 +3260,10 @@ exports[`Render properties.sider.color - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -2972,8 +3522,10 @@ exports[`Render properties.sider.initialCollapsed - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -3156,8 +3708,10 @@ exports[`Render properties.sider.style - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -3340,8 +3894,10 @@ exports[`Render properties.sider.theme: dark - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -3513,8 +4069,10 @@ exports[`Render properties.style - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -3686,8 +4244,10 @@ exports[`Render properties.toggleSiderButton.hide - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -3870,8 +4430,10 @@ exports[`Render properties.toggleSiderButton.type - value[0] 1`] = `
>
<img
alt="Lowdefy"
className="{\\"style\\":[{\\"width\\":130,\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
className="{\\"style\\":[{\\"width\\":130,\\"sm\\":{\\"width\\":40},\\"md\\":{\\"width\\":130}},{\\"margin\\":\\"0 30px 0 0\\",\\"flex\\":\\"0 1 auto\\",\\"sm\\":{\\"margin\\":\\"0 10px 0 0\\"},\\"md\\":{\\"margin\\":\\"0 15px 0 0\\"}},null]}"
sizes="(max-width: 767px) 40px, 768px"
src="/public/logo-dark-theme.png"
srcset="/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w"
/>
</a>
</header>
@ -4031,10 +4593,18 @@ exports[`Test Schema properties.logo.alt 1`] = `true`;
exports[`Test Schema properties.logo.alt 2`] = `null`;
exports[`Test Schema properties.logo.size 1`] = `true`;
exports[`Test Schema properties.logo.size 2`] = `null`;
exports[`Test Schema properties.logo.src 1`] = `true`;
exports[`Test Schema properties.logo.src 2`] = `null`;
exports[`Test Schema properties.logo.srcset 1`] = `true`;
exports[`Test Schema properties.logo.srcset 2`] = `null`;
exports[`Test Schema properties.logo.style 1`] = `true`;
exports[`Test Schema properties.logo.style 2`] = `null`;

View File

@ -54,17 +54,9 @@
"type": "App \"config.auth.openId.rolesField\" should be a string."
}
},
"logoutFromProvider": {
"type": "boolean",
"default": false,
"description": "If true, the user will be directed to OpenID Connect provider's logout URL. This will log the user out of the provider.",
"errorMessage": {
"type": "App \"config.auth.openId.logoutFromProvider\" should be a boolean."
}
},
"logoutRedirectUri": {
"type": "string",
"description": "The URI to redirect the user to after logout.",
"description": "The URI to redirect the user to after logout. Can be a Nunjucks template string with client_id, host, id_token_hint, and openid_domain as template data.",
"errorMessage": {
"type": "App \"config.auth.openId.logoutRedirectUri\" should be a string."
}

View File

@ -59,7 +59,6 @@
"mysql": "2.18.1",
"opener": "1.5.2",
"ora": "5.3.0",
"oracledb": "5.1.0",
"pg": "8.5.1",
"reload": "3.1.1",
"saslprep": "1.0.3",

View File

@ -419,11 +419,11 @@
- id: MySQL
type: MenuLink
pageId: MySQL
- id: OracleDB
type: MenuLink
pageId: OracleDB
properties:
title: Oracle Database
# - id: OracleDB
# type: MenuLink
# pageId: OracleDB
# properties:
# title: Oracle Database
- id: PostgreSQL
type: MenuLink
pageId: PostgreSQL

View File

@ -115,7 +115,7 @@
- _ref: connections/MongoDB.yaml
- _ref: connections/MSSQL.yaml
- _ref: connections/MySQL.yaml
- _ref: connections/OracleDB.yaml
# - _ref: connections/OracleDB.yaml
- _ref: connections/PostgreSQL.yaml
- _ref: connections/SendGridMail.yaml
- _ref: connections/SQLite.yaml

View File

@ -62,7 +62,6 @@
"mssql": "6.3.1",
"mysql": "2.18.1",
"openid-client": "4.6.0",
"oracledb": "5.1.0",
"pg": "8.5.1",
"saslprep": "1.0.3",
"sqlite3": "5.0.2"

View File

@ -14,7 +14,8 @@
limitations under the License.
*/
import { get } from '@lowdefy/helpers';
import { get, type } from '@lowdefy/helpers';
import { nunjucksFunction } from '@lowdefy/nunjucks';
import { Issuer } from 'openid-client';
import cookie from 'cookie';
@ -22,13 +23,12 @@ import { AuthenticationError, ConfigurationError } from '../context/errors';
class OpenIdController {
constructor({ development, getController, getLoader, getSecrets, gqlUri, host, setHeader }) {
const httpPrefix = development ? 'http' : 'https';
this.httpPrefix = development ? 'http' : 'https';
this.development = development;
this.componentLoader = getLoader('component');
this.getSecrets = getSecrets;
this.host = host;
this.redirectUri = `${httpPrefix}://${host}/auth/openid-callback`;
this.redirectUri = `${this.httpPrefix}://${this.host}/auth/openid-callback`;
this.gqlUri = gqlUri || '/api/graphql';
this.setHeader = setHeader;
this.tokenController = getController('token');
@ -63,7 +63,7 @@ class OpenIdController {
});
}
async authorizationUrl({ input, pageId, urlQuery }) {
async authorizationUrl({ authUrlQueryParams, input, pageId, urlQuery }) {
try {
const config = await this.getOpenIdConfig();
if (!config) return null;
@ -73,15 +73,16 @@ class OpenIdController {
pageId,
urlQuery,
});
return this.getAuthorizationUrl({ config, state });
return this.getAuthorizationUrl({ authUrlQueryParams, config, state });
} catch (error) {
throw new ConfigurationError(error);
}
}
async getAuthorizationUrl({ config, state }) {
async getAuthorizationUrl({ authUrlQueryParams, config, state }) {
const client = await this.getClient({ config });
const url = client.authorizationUrl({
...authUrlQueryParams,
redirect_uri: this.redirectUri,
response_type: 'code',
scope: config.scope || 'openid profile email',
@ -137,6 +138,17 @@ class OpenIdController {
};
}
parseLogoutUrlNunjucks({ config, idToken }) {
const template = nunjucksFunction(config.logoutRedirectUri);
const templateData = {
id_token_hint: idToken,
client_id: config.clientId,
openid_domain: config.domain,
host: encodeURIComponent(`${this.httpPrefix}://${this.host}`),
};
return template(templateData);
}
async logoutUrl({ idToken }) {
try {
const setCookieHeader = cookie.serialize('authorization', '', {
@ -149,18 +161,8 @@ class OpenIdController {
this.setHeader('Set-Cookie', setCookieHeader);
const config = await this.getOpenIdConfig();
if (!config) return null;
if (config.logoutFromProvider !== true) {
return config.logoutRedirectUri || null;
}
const client = await this.getClient({ config });
return client.endSessionUrl({
id_token_hint: idToken,
post_logout_redirect_uri: config.logoutRedirectUri,
});
if (!config || !type.isString(config.logoutRedirectUri)) return null;
return this.parseLogoutUrlNunjucks({ config, idToken });
} catch (error) {
throw new AuthenticationError(error);
}

View File

@ -24,8 +24,8 @@ jest.mock('openid-client');
const mockOpenIdAuthorizationUrl = jest.fn(
// eslint-disable-next-line camelcase
({ redirect_uri, response_type, scope, state }) =>
`${redirect_uri}:${response_type}:${scope}:${state}`
({ redirect_uri, response_type, scope, state, ...additional }) =>
`${redirect_uri}:${response_type}:${scope}:${state}:${JSON.stringify(additional)}`
);
const mockOpenIdCallback = jest.fn(() => ({
@ -33,14 +33,9 @@ const mockOpenIdCallback = jest.fn(() => ({
id_token: 'id_token',
}));
const mockEndSessionUrl = jest.fn(
({ id_token_hint, post_logout_redirect_uri }) => `${id_token_hint}:${post_logout_redirect_uri}`
);
const mockClient = jest.fn(() => ({
authorizationUrl: mockOpenIdAuthorizationUrl,
callback: mockOpenIdCallback,
endSessionUrl: mockEndSessionUrl,
}));
// eslint-disable-next-line no-undef
@ -189,7 +184,7 @@ describe('authorizationUrl', () => {
],
]);
expect(url).toEqual(
'https://host/auth/openid-callback:code:openid profile email:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnB1dCI6eyJpIjp0cnVlfSwibG93ZGVmeV9vcGVuaWRfc3RhdGVfdG9rZW4iOnRydWUsInBhZ2VJZCI6InBhZ2VJZCIsInVybFF1ZXJ5Ijp7InUiOnRydWV9LCJpYXQiOjEsImV4cCI6MzAxLCJhdWQiOiJob3N0IiwiaXNzIjoiaG9zdCJ9.-GLdtCspyagMhdx9z1VootZXXbIdLY3cbzpn5UK8eGI'
'https://host/auth/openid-callback:code:openid profile email:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnB1dCI6eyJpIjp0cnVlfSwibG93ZGVmeV9vcGVuaWRfc3RhdGVfdG9rZW4iOnRydWUsInBhZ2VJZCI6InBhZ2VJZCIsInVybFF1ZXJ5Ijp7InUiOnRydWV9LCJpYXQiOjEsImV4cCI6MzAxLCJhdWQiOiJob3N0IiwiaXNzIjoiaG9zdCJ9.-GLdtCspyagMhdx9z1VootZXXbIdLY3cbzpn5UK8eGI:{}'
);
});
@ -205,7 +200,7 @@ describe('authorizationUrl', () => {
const openIdController = createOpenIdController(context);
const url = await openIdController.authorizationUrl(authorizationUrlInput);
expect(url).toEqual(
'https://host/auth/openid-callback:code:custom scope:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnB1dCI6eyJpIjp0cnVlfSwibG93ZGVmeV9vcGVuaWRfc3RhdGVfdG9rZW4iOnRydWUsInBhZ2VJZCI6InBhZ2VJZCIsInVybFF1ZXJ5Ijp7InUiOnRydWV9LCJpYXQiOjEsImV4cCI6MzAxLCJhdWQiOiJob3N0IiwiaXNzIjoiaG9zdCJ9.-GLdtCspyagMhdx9z1VootZXXbIdLY3cbzpn5UK8eGI'
'https://host/auth/openid-callback:code:custom scope:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnB1dCI6eyJpIjp0cnVlfSwibG93ZGVmeV9vcGVuaWRfc3RhdGVfdG9rZW4iOnRydWUsInBhZ2VJZCI6InBhZ2VJZCIsInVybFF1ZXJ5Ijp7InUiOnRydWV9LCJpYXQiOjEsImV4cCI6MzAxLCJhdWQiOiJob3N0IiwiaXNzIjoiaG9zdCJ9.-GLdtCspyagMhdx9z1VootZXXbIdLY3cbzpn5UK8eGI:{}'
);
});
@ -230,6 +225,51 @@ describe('authorizationUrl', () => {
ConfigurationError
);
});
test('authorizationUrl, additional query params', async () => {
getSecrets.mockImplementation(() => secrets);
const openIdController = createOpenIdController(context);
const url = await openIdController.authorizationUrl({
authUrlQueryParams: { screen_hint: 'sign-up' },
});
expect(mockClient.mock.calls).toEqual([
[
{
client_id: 'OPENID_CLIENT_ID',
client_secret: 'OPENID_CLIENT_SECRET',
redirect_uris: ['https://host/auth/openid-callback'],
},
],
]);
expect(url).toEqual(
'https://host/auth/openid-callback:code:openid profile email:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb3dkZWZ5X29wZW5pZF9zdGF0ZV90b2tlbiI6dHJ1ZSwiaWF0IjoxLCJleHAiOjMwMSwiYXVkIjoiaG9zdCIsImlzcyI6Imhvc3QifQ.-UtxdTFvQW6pFFFHTO0EtmubPbkDl8EJQwBQA2pp_M4:{"screen_hint":"sign-up"}'
);
});
test('authorizationUrl, additional query do not overwrite fiexed params', async () => {
getSecrets.mockImplementation(() => secrets);
const openIdController = createOpenIdController(context);
const url = await openIdController.authorizationUrl({
authUrlQueryParams: {
redirect_uri: 'overwritten',
response_type: 'overwritten',
scope: 'overwritten',
state: 'overwritten',
},
});
expect(mockClient.mock.calls).toEqual([
[
{
client_id: 'OPENID_CLIENT_ID',
client_secret: 'OPENID_CLIENT_SECRET',
redirect_uris: ['https://host/auth/openid-callback'],
},
],
]);
expect(url).toEqual(
'https://host/auth/openid-callback:code:openid profile email:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb3dkZWZ5X29wZW5pZF9zdGF0ZV90b2tlbiI6dHJ1ZSwiaWF0IjoxLCJleHAiOjMwMSwiYXVkIjoiaG9zdCIsImlzcyI6Imhvc3QifQ.-UtxdTFvQW6pFFFHTO0EtmubPbkDl8EJQwBQA2pp_M4:{}'
);
});
});
describe('callback', () => {
@ -344,7 +384,7 @@ describe('callback', () => {
});
describe('logout', () => {
test('callback, no openId config', async () => {
test('logout, no openId config', async () => {
getSecrets.mockImplementation(() => ({}));
const openIdController = createOpenIdController(context);
const url = await openIdController.logoutUrl(logoutUrlInput);
@ -357,144 +397,48 @@ describe('logout', () => {
]);
});
test('callback, logoutFromProvider !== true, no logoutRedirectUri', async () => {
getSecrets.mockImplementation(() => secrets);
const openIdController = createOpenIdController(context);
const url = await openIdController.logoutUrl(logoutUrlInput);
expect(url).toEqual(null);
expect(mockEndSessionUrl.mock.calls).toEqual([]);
expect(setHeader.mock.calls).toEqual([
[
'Set-Cookie',
'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax',
],
]);
});
test('callback, logoutFromProvider !== true, with logoutRedirectUri', async () => {
test('logout with logoutRedirectUri', async () => {
getSecrets.mockImplementation(() => secrets);
mockLoadComponent.mockImplementation(() => ({
auth: {
openId: {
logoutRedirectUri: 'logoutRedirectUri',
logoutRedirectUri:
'{{ openid_domain }}/logout/?id_token_hint={{ id_token_hint }}&client_id={{ client_id }}&return_to={{ host }}%2Flogged-out',
},
},
}));
const openIdController = createOpenIdController(context);
const url = await openIdController.logoutUrl(logoutUrlInput);
expect(url).toEqual('logoutRedirectUri');
expect(mockEndSessionUrl.mock.calls).toEqual([]);
expect(setHeader.mock.calls).toEqual([
[
'Set-Cookie',
'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax',
],
]);
});
test('callback, logoutFromProvider, no logoutRedirectUri', async () => {
getSecrets.mockImplementation(() => secrets);
mockLoadComponent.mockImplementation(() => ({
auth: {
openId: {
logoutFromProvider: true,
},
},
}));
const openIdController = createOpenIdController(context);
const url = await openIdController.logoutUrl(logoutUrlInput);
expect(mockClient.mock.calls).toEqual([
[
{
client_id: 'OPENID_CLIENT_ID',
client_secret: 'OPENID_CLIENT_SECRET',
redirect_uris: ['https://host/auth/openid-callback'],
},
],
]);
expect(url).toEqual('idToken:undefined');
expect(mockEndSessionUrl.mock.calls).toEqual([
[
{
id_token_hint: 'idToken',
},
],
]);
expect(setHeader.mock.calls).toEqual([
[
'Set-Cookie',
'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax',
],
]);
});
test('callback, logoutFromProvider, with logoutRedirectUri', async () => {
getSecrets.mockImplementation(() => secrets);
mockLoadComponent.mockImplementation(() => ({
auth: {
openId: {
logoutFromProvider: true,
logoutRedirectUri: 'logoutRedirectUri',
},
},
}));
const openIdController = createOpenIdController(context);
const url = await openIdController.logoutUrl(logoutUrlInput);
expect(mockClient.mock.calls).toEqual([
[
{
client_id: 'OPENID_CLIENT_ID',
client_secret: 'OPENID_CLIENT_SECRET',
redirect_uris: ['https://host/auth/openid-callback'],
},
],
]);
expect(url).toEqual('idToken:logoutRedirectUri');
expect(mockEndSessionUrl.mock.calls).toEqual([
[
{
id_token_hint: 'idToken',
post_logout_redirect_uri: 'logoutRedirectUri',
},
],
]);
expect(setHeader.mock.calls).toEqual([
[
'Set-Cookie',
'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax',
],
]);
});
test('callback, logoutFromProvider, error', async () => {
getSecrets.mockImplementation(() => secrets);
mockLoadComponent.mockImplementation(() => ({
auth: {
openId: {
logoutFromProvider: true,
},
},
}));
const openIdController = createOpenIdController(context);
mockEndSessionUrl.mockImplementationOnce(() => {
throw new Error('OpenId End Session Error');
});
await expect(openIdController.logoutUrl(logoutUrlInput)).rejects.toThrow(AuthenticationError);
mockEndSessionUrl.mockImplementationOnce(() => {
throw new Error('OpenId End Session Error');
});
await expect(openIdController.logoutUrl(logoutUrlInput)).rejects.toThrow(
'Error: OpenId End Session Error'
expect(url).toEqual(
'OPENID_DOMAIN/logout/?id_token_hint=idToken&client_id=OPENID_CLIENT_ID&return_to=https%3A%2F%2Fhost%2Flogged-out'
);
expect(setHeader.mock.calls).toEqual([
[
'Set-Cookie',
'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax',
],
[
'Set-Cookie',
'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax',
],
]);
});
test('logout with logoutRedirectUri, development true', async () => {
getSecrets.mockImplementation(() => secrets);
mockLoadComponent.mockImplementation(() => ({
auth: {
openId: {
logoutRedirectUri:
'{{ openid_domain }}/logout/?id_token_hint={{ id_token_hint }}&client_id={{ client_id }}&return_to={{ host }}%2Flogged-out',
},
},
}));
const devOpenIdController = createOpenIdController(
testBootstrapContext({ getSecrets, host: 'host', loaders, development: true, setHeader })
);
const url = await devOpenIdController.logoutUrl(logoutUrlInput);
expect(url).toEqual(
'OPENID_DOMAIN/logout/?id_token_hint=idToken&client_id=OPENID_CLIENT_ID&return_to=http%3A%2F%2Fhost%2Flogged-out'
);
expect(setHeader.mock.calls).toEqual([
['Set-Cookie', 'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; SameSite=Lax'],
]);
});
@ -516,4 +460,18 @@ describe('logout', () => {
],
]);
});
test('logout with logoutRedirectUri, invalid template', async () => {
getSecrets.mockImplementation(() => secrets);
mockLoadComponent.mockImplementation(() => ({
auth: {
openId: {
logoutRedirectUri: '{{ openid_domain ',
},
},
}));
const openIdController = createOpenIdController(context);
expect(openIdController.logoutUrl(logoutUrlInput)).rejects.toThrow(AuthenticationError);
expect(openIdController.logoutUrl(logoutUrlInput)).rejects.toThrow('Template render error');
});
});

View File

@ -17,7 +17,6 @@
// eslint-disable-next-line no-unused-vars
import { gql } from 'apollo-server';
import runTestQuery from '../../../test/runTestQuery';
import { Issuer } from 'openid-client';
import openIdLogoutUrl from './openIdLogoutUrl';
@ -33,19 +32,6 @@ const getController = jest.fn((name) => {
}
});
// OpenID mocks
const mockEndSessionUrl = jest.fn(
({ id_token_hint, post_logout_redirect_uri }) => `${id_token_hint}:${post_logout_redirect_uri}`
);
const mockClient = jest.fn(() => ({
endSessionUrl: mockEndSessionUrl,
}));
Issuer.discover = jest.fn(() => ({
Client: mockClient,
}));
const secrets = {
OPENID_CLIENT_ID: 'OPENID_CLIENT_ID',
OPENID_CLIENT_SECRET: 'OPENID_CLIENT_SECRET',
@ -56,8 +42,8 @@ const secrets = {
const mockLoadComponent = jest.fn(() => ({
auth: {
openId: {
logoutFromProvider: true,
logoutRedirectUri: 'logoutRedirectUri',
logoutRedirectUri:
'{{ openid_domain }}/logout/?id_token_hint={{ id_token_hint }}&client_id={{ client_id }}&return_to={{ host }}%2Flogged-out',
},
},
}));
@ -97,7 +83,8 @@ test('openIdLogoutUrl graphql', async () => {
});
expect(res.errors).toBe(undefined);
expect(res.data).toEqual({
openIdLogoutUrl: 'idToken:logoutRedirectUri',
openIdLogoutUrl:
'OPENID_DOMAIN/logout/?id_token_hint=idToken&client_id=OPENID_CLIENT_ID&return_to=https%3A%2F%2Fhost%2Flogged-out',
});
expect(setHeader.mock.calls).toEqual([
['Set-Cookie', 'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax'],

View File

@ -71,6 +71,7 @@ const typeDefs = gql`
}
input OpenIdAuthorizationUrlInput {
authUrlQueryParams: JSON
input: JSON
pageId: String
urlQuery: JSON

View File

@ -24,13 +24,14 @@ const GET_LOGIN = gql`
`;
function createLogin({ client, window }) {
async function login({ input, pageId, urlQuery } = {}) {
async function login({ authUrlQueryParams, input, pageId, urlQuery } = {}) {
try {
const { data } = await client.query({
query: GET_LOGIN,
fetchPolicy: 'network-only',
variables: {
openIdAuthorizationUrlInput: {
authUrlQueryParams,
input,
pageId,
urlQuery,

View File

@ -26,17 +26,17 @@ function createLogout(lowdefy) {
async function logout() {
try {
lowdefy.user = {};
const idToken = lowdefy.localStorage.getItem('idToken');
lowdefy.localStorage.setItem(`idToken`, '');
const { data } = await lowdefy.client.query({
query: GET_LOGOUT,
fetchPolicy: 'network-only',
variables: {
openIdLogoutUrlInput: {
idToken: '',
idToken,
},
},
});
// TODO: should we call link??
lowdefy.window.location.href = data.openIdLogoutUrl || lowdefy.window.location.origin;
} catch (error) {
throw new Error(error);

View File

@ -3395,7 +3395,6 @@ __metadata:
mssql: 6.3.1
mysql: 2.18.1
openid-client: 4.6.0
oracledb: 5.1.0
pg: 8.5.1
saslprep: 1.0.3
sqlite3: 5.0.2
@ -14188,7 +14187,6 @@ fsevents@^1.2.7:
mysql: 2.18.1
opener: 1.5.2
ora: 5.3.0
oracledb: 5.1.0
pg: 8.5.1
react: 17.0.2
react-dom: 17.0.2
@ -16180,13 +16178,6 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
"oracledb@npm:5.1.0":
version: 5.1.0
resolution: "oracledb@npm:5.1.0"
checksum: c4dd2c6ac05b2c71f81ed61a17c71b694e9821f1ad2ca6b14ccc5681dd928e26fb0ba043fb0daf306b055329492186162b1aceddabd048eedd027645ed3afdaf
languageName: node
linkType: hard
"original@npm:^1.0.0":
version: 1.0.2
resolution: "original@npm:1.0.2"