diff --git a/package-lock.json b/package-lock.json
index e5e85fc76b..99461f2cd0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -300,9 +300,9 @@
 			}
 		},
 		"@babel/helper-create-class-features-plugin": {
-			"version": "7.17.1",
-			"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.1.tgz",
-			"integrity": "sha512-JBdSr/LtyYIno/pNnJ75lBcqc3Z1XXujzPanHqjvvrhOA+DTceTFuJi8XjmWTZh4r3fsdfqaCMN0iZemdkxZHQ==",
+			"version": "7.17.6",
+			"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz",
+			"integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==",
 			"requires": {
 				"@babel/helper-annotate-as-pure": "^7.16.7",
 				"@babel/helper-environment-visitor": "^7.16.7",
@@ -396,9 +396,9 @@
 			}
 		},
 		"@babel/helper-module-transforms": {
-			"version": "7.16.7",
-			"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.7.tgz",
-			"integrity": "sha512-gaqtLDxJEFCeQbYp9aLAefjhkKdjKcdh6DB7jniIGU3Pz52WAmP268zK0VgPz9hUNkMSYeH976K2/Y6yPadpng==",
+			"version": "7.17.6",
+			"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.6.tgz",
+			"integrity": "sha512-2ULmRdqoOMpdvkbT8jONrZML/XALfzxlb052bldftkicAUy8AxSCkD5trDPQcwHNmolcl7wP6ehNqMlyUw6AaA==",
 			"requires": {
 				"@babel/helper-environment-visitor": "^7.16.7",
 				"@babel/helper-module-imports": "^7.16.7",
@@ -406,8 +406,8 @@
 				"@babel/helper-split-export-declaration": "^7.16.7",
 				"@babel/helper-validator-identifier": "^7.16.7",
 				"@babel/template": "^7.16.7",
-				"@babel/traverse": "^7.16.7",
-				"@babel/types": "^7.16.7"
+				"@babel/traverse": "^7.17.3",
+				"@babel/types": "^7.17.0"
 			}
 		},
 		"@babel/helper-optimise-call-expression": {
@@ -553,11 +553,11 @@
 			}
 		},
 		"@babel/plugin-proposal-class-static-block": {
-			"version": "7.16.7",
-			"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.16.7.tgz",
-			"integrity": "sha512-dgqJJrcZoG/4CkMopzhPJjGxsIe9A8RlkQLnL/Vhhx8AA9ZuaRwGSlscSh42hazc7WSrya/IK7mTeoF0DP9tEw==",
+			"version": "7.17.6",
+			"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.17.6.tgz",
+			"integrity": "sha512-X/tididvL2zbs7jZCeeRJ8167U/+Ac135AM6jCAx6gYXDUviZV5Ku9UDvWS2NCuWlFjIRXklYhwo6HhAC7ETnA==",
 			"requires": {
-				"@babel/helper-create-class-features-plugin": "^7.16.7",
+				"@babel/helper-create-class-features-plugin": "^7.17.6",
 				"@babel/helper-plugin-utils": "^7.16.7",
 				"@babel/plugin-syntax-class-static-block": "^7.14.5"
 			}
@@ -9930,14 +9930,14 @@
 			"integrity": "sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw=="
 		},
 		"@oclif/parser": {
-			"version": "3.8.6",
-			"resolved": "https://registry.npmjs.org/@oclif/parser/-/parser-3.8.6.tgz",
-			"integrity": "sha512-tXb0NKgSgNxmf6baN6naK+CCwOueaFk93FG9u202U7mTBHUKsioOUlw1SG/iPi9aJM3WE4pHLXmty59pci0OEw==",
+			"version": "3.8.7",
+			"resolved": "https://registry.npmjs.org/@oclif/parser/-/parser-3.8.7.tgz",
+			"integrity": "sha512-b11xBmIUK+LuuwVGJpFs4LwQN2xj2cBWj2c4z1FtiXGrJ85h9xV6q+k136Hw0tGg1jQoRXuvuBnqQ7es7vO9/Q==",
 			"requires": {
-				"@oclif/errors": "^1.2.2",
+				"@oclif/errors": "^1.3.5",
 				"@oclif/linewrap": "^1.0.0",
 				"chalk": "^4.1.0",
-				"tslib": "^2.0.0"
+				"tslib": "^2.3.1"
 			},
 			"dependencies": {
 				"ansi-styles": {
@@ -10812,6 +10812,11 @@
 						}
 					}
 				},
+				"acorn": {
+					"version": "8.7.0",
+					"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
+					"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ=="
+				},
 				"braces": {
 					"version": "2.3.2",
 					"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
@@ -11041,10 +11046,11 @@
 					}
 				},
 				"terser": {
-					"version": "5.10.0",
-					"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
-					"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
+					"version": "5.11.0",
+					"resolved": "https://registry.npmjs.org/terser/-/terser-5.11.0.tgz",
+					"integrity": "sha512-uCA9DLanzzWSsN1UirKwylhhRz3aKPInlfmpGfw8VN6jHsAtu8HJtIpeeHHK23rxnE/cDc+yvmq5wqkIC6Kn0A==",
 					"requires": {
+						"acorn": "^8.5.0",
 						"commander": "^2.20.0",
 						"source-map": "~0.7.2",
 						"source-map-support": "~0.5.20"
@@ -11722,6 +11728,11 @@
 				"webpack-virtual-modules": "^0.2.2"
 			},
 			"dependencies": {
+				"acorn": {
+					"version": "8.7.0",
+					"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz",
+					"integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ=="
+				},
 				"ansi-styles": {
 					"version": "4.3.0",
 					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -11900,10 +11911,11 @@
 					}
 				},
 				"terser": {
-					"version": "5.10.0",
-					"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
-					"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
+					"version": "5.11.0",
+					"resolved": "https://registry.npmjs.org/terser/-/terser-5.11.0.tgz",
+					"integrity": "sha512-uCA9DLanzzWSsN1UirKwylhhRz3aKPInlfmpGfw8VN6jHsAtu8HJtIpeeHHK23rxnE/cDc+yvmq5wqkIC6Kn0A==",
 					"requires": {
+						"acorn": "^8.5.0",
 						"commander": "^2.20.0",
 						"source-map": "~0.7.2",
 						"source-map-support": "~0.5.20"
@@ -12592,15 +12604,20 @@
 				"@types/node": "*"
 			}
 		},
+		"@types/linkify-it": {
+			"version": "3.0.2",
+			"resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz",
+			"integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA=="
+		},
 		"@types/localtunnel": {
 			"version": "1.9.0",
 			"resolved": "https://registry.npmjs.org/@types/localtunnel/-/localtunnel-1.9.0.tgz",
 			"integrity": "sha512-3YxO7RHRrmtYNX6Rhkr97bnXHrF1Ckfo4axENWLcBXWi+8B1WsNbqPqe5Eg6TA5survjAWWvLTu1KQesuLHVgQ=="
 		},
 		"@types/lodash": {
-			"version": "4.14.178",
-			"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz",
-			"integrity": "sha512-0d5Wd09ItQWH1qFbEyQ7oTQ3GZrMfth5JkbN3EvTKLXcHLRDSXeLnlvlOn0wvxVIwK5o2M8JzP/OWz7T3NRsbw=="
+			"version": "4.14.179",
+			"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz",
+			"integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w=="
 		},
 		"@types/lodash.camelcase": {
 			"version": "4.3.6",
@@ -12647,6 +12664,15 @@
 				"@types/node": "*"
 			}
 		},
+		"@types/markdown-it": {
+			"version": "12.2.3",
+			"resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz",
+			"integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==",
+			"requires": {
+				"@types/linkify-it": "*",
+				"@types/mdurl": "*"
+			}
+		},
 		"@types/mdast": {
 			"version": "3.0.10",
 			"resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz",
@@ -12655,6 +12681,11 @@
 				"@types/unist": "*"
 			}
 		},
+		"@types/mdurl": {
+			"version": "1.0.2",
+			"resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz",
+			"integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA=="
+		},
 		"@types/mime": {
 			"version": "1.3.2",
 			"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz",
@@ -13646,15 +13677,6 @@
 					"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
 					"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
 				},
-				"ansi-styles": {
-					"version": "4.3.0",
-					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
-					"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
-					"optional": true,
-					"requires": {
-						"color-convert": "^2.0.1"
-					}
-				},
 				"array-union": {
 					"version": "1.0.2",
 					"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
@@ -13690,21 +13712,6 @@
 						}
 					}
 				},
-				"color-convert": {
-					"version": "2.0.1",
-					"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
-					"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
-					"optional": true,
-					"requires": {
-						"color-name": "~1.1.4"
-					}
-				},
-				"color-name": {
-					"version": "1.1.4",
-					"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
-					"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
-					"optional": true
-				},
 				"commander": {
 					"version": "2.20.3",
 					"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
@@ -13767,58 +13774,6 @@
 						"worker-rpc": "^0.1.0"
 					}
 				},
-				"fork-ts-checker-webpack-plugin-v5": {
-					"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
-					"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
-					"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
-					"optional": true,
-					"requires": {
-						"@babel/code-frame": "^7.8.3",
-						"@types/json-schema": "^7.0.5",
-						"chalk": "^4.1.0",
-						"cosmiconfig": "^6.0.0",
-						"deepmerge": "^4.2.2",
-						"fs-extra": "^9.0.0",
-						"memfs": "^3.1.2",
-						"minimatch": "^3.0.4",
-						"schema-utils": "2.7.0",
-						"semver": "^7.3.2",
-						"tapable": "^1.0.0"
-					},
-					"dependencies": {
-						"chalk": {
-							"version": "4.1.2",
-							"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
-							"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
-							"optional": true,
-							"requires": {
-								"ansi-styles": "^4.1.0",
-								"supports-color": "^7.1.0"
-							}
-						},
-						"semver": {
-							"version": "7.3.5",
-							"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
-							"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
-							"optional": true,
-							"requires": {
-								"lru-cache": "^6.0.0"
-							}
-						}
-					}
-				},
-				"fs-extra": {
-					"version": "9.1.0",
-					"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
-					"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
-					"optional": true,
-					"requires": {
-						"at-least-node": "^1.0.0",
-						"graceful-fs": "^4.2.0",
-						"jsonfile": "^6.0.1",
-						"universalify": "^2.0.0"
-					}
-				},
 				"glob-parent": {
 					"version": "3.1.0",
 					"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz",
@@ -13853,12 +13808,6 @@
 						"slash": "^2.0.0"
 					}
 				},
-				"has-flag": {
-					"version": "4.0.0",
-					"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-					"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
-					"optional": true
-				},
 				"ignore": {
 					"version": "4.0.6",
 					"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@@ -13887,16 +13836,6 @@
 					"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
 					"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
 				},
-				"jsonfile": {
-					"version": "6.1.0",
-					"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
-					"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
-					"optional": true,
-					"requires": {
-						"graceful-fs": "^4.1.6",
-						"universalify": "^2.0.0"
-					}
-				},
 				"micromatch": {
 					"version": "3.1.10",
 					"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
@@ -13932,17 +13871,6 @@
 						}
 					}
 				},
-				"schema-utils": {
-					"version": "2.7.0",
-					"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
-					"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
-					"optional": true,
-					"requires": {
-						"@types/json-schema": "^7.0.4",
-						"ajv": "^6.12.2",
-						"ajv-keywords": "^3.4.1"
-					}
-				},
 				"semver": {
 					"version": "5.7.1",
 					"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
@@ -13953,15 +13881,6 @@
 					"resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
 					"integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A=="
 				},
-				"supports-color": {
-					"version": "7.2.0",
-					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-					"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-					"optional": true,
-					"requires": {
-						"has-flag": "^4.0.0"
-					}
-				},
 				"to-regex-range": {
 					"version": "2.1.1",
 					"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
@@ -14055,12 +13974,6 @@
 					"requires": {
 						"tslib": "^1.8.1"
 					}
-				},
-				"universalify": {
-					"version": "2.0.0",
-					"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
-					"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
-					"optional": true
 				}
 			}
 		},
@@ -14869,11 +14782,11 @@
 			},
 			"dependencies": {
 				"postcss": {
-					"version": "8.4.6",
-					"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz",
-					"integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==",
+					"version": "8.4.7",
+					"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.7.tgz",
+					"integrity": "sha512-L9Ye3r6hkkCeOETQX6iOaWZgjp3LL6Lpqm6EtgbKrgqGGteRMNb9vzBfRL96YOSu8o7x3MfIH9Mo5cPJFGrW6A==",
 					"requires": {
-						"nanoid": "^3.2.0",
+						"nanoid": "^3.3.1",
 						"picocolors": "^1.0.0",
 						"source-map-js": "^1.0.2"
 					}
@@ -16000,9 +15913,9 @@
 			"integrity": "sha512-uUbetCWczQHbsKyX1C99XpQHBM8SWfovvaZhPIj23/1uV7SQf0WeRZbiLpw0JZm+LHTChfNgrLfDJOVoU2kU+A=="
 		},
 		"aws-sdk": {
-			"version": "2.1077.0",
-			"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1077.0.tgz",
-			"integrity": "sha512-orJvJROs8hJaQRfHsX7Zl5PxEgrD/uTXyqXz9Yu9Io5VVxzvnOty9oHmvEMSlgTIf1qd01gnev/vpvP1HgzKtw==",
+			"version": "2.1082.0",
+			"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1082.0.tgz",
+			"integrity": "sha512-aDrUZ63O/ocuC827ursDqFQAm3jhqsJu1DvMCCFg73y+FK9pXXNHp2mwdi3UeeHvtfxISCLCjuyO3VFd/tpVfA==",
 			"requires": {
 				"buffer": "4.9.2",
 				"events": "1.1.1",
@@ -20356,6 +20269,11 @@
 			"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
 			"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="
 		},
+		"cssfilter": {
+			"version": "0.0.10",
+			"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
+			"integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4="
+		},
 		"cssnano": {
 			"version": "4.1.11",
 			"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.11.tgz",
@@ -21426,9 +21344,9 @@
 			"integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA=="
 		},
 		"electron-to-chromium": {
-			"version": "1.4.71",
-			"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.71.tgz",
-			"integrity": "sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw=="
+			"version": "1.4.73",
+			"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.73.tgz",
+			"integrity": "sha512-RlCffXkE/LliqfA5m29+dVDPB2r72y2D2egMMfIy3Le8ODrxjuZNVo4NIC2yPL01N4xb4nZQLwzi6Z5tGIGLnA=="
 		},
 		"element-resize-detector": {
 			"version": "1.2.4",
@@ -21439,9 +21357,9 @@
 			}
 		},
 		"element-ui": {
-			"version": "2.13.2",
-			"resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.13.2.tgz",
-			"integrity": "sha512-r761DRPssMPKDiJZWFlG+4e4vr0cRG/atKr3Eqr8Xi0tQMNbtmYU1QXvFnKiFPFFGkgJ6zS6ASkG+sellcoHlQ==",
+			"version": "2.15.7",
+			"resolved": "https://registry.npmjs.org/element-ui/-/element-ui-2.15.7.tgz",
+			"integrity": "sha512-+J6rnXajxzLwV6w8Q6bf7Yqzk1FO1ewbIrCy/4B5alnd7tj8WEpfQoAvISirVaUGVGy77d9Ji3o2bF4f0AsJLQ==",
 			"requires": {
 				"async-validator": "~1.8.1",
 				"babel-helper-vue-jsx-merge-props": "^2.0.0",
@@ -23534,6 +23452,124 @@
 				}
 			}
 		},
+		"fork-ts-checker-webpack-plugin-v5": {
+			"version": "npm:fork-ts-checker-webpack-plugin@5.2.1",
+			"resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-5.2.1.tgz",
+			"integrity": "sha512-SVi+ZAQOGbtAsUWrZvGzz38ga2YqjWvca1pXQFUArIVXqli0lLoDQ8uS0wg0kSpcwpZmaW5jVCZXQebkyUQSsw==",
+			"optional": true,
+			"requires": {
+				"@babel/code-frame": "^7.8.3",
+				"@types/json-schema": "^7.0.5",
+				"chalk": "^4.1.0",
+				"cosmiconfig": "^6.0.0",
+				"deepmerge": "^4.2.2",
+				"fs-extra": "^9.0.0",
+				"memfs": "^3.1.2",
+				"minimatch": "^3.0.4",
+				"schema-utils": "2.7.0",
+				"semver": "^7.3.2",
+				"tapable": "^1.0.0"
+			},
+			"dependencies": {
+				"ansi-styles": {
+					"version": "4.3.0",
+					"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+					"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+					"optional": true,
+					"requires": {
+						"color-convert": "^2.0.1"
+					}
+				},
+				"chalk": {
+					"version": "4.1.2",
+					"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+					"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+					"optional": true,
+					"requires": {
+						"ansi-styles": "^4.1.0",
+						"supports-color": "^7.1.0"
+					}
+				},
+				"color-convert": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+					"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+					"optional": true,
+					"requires": {
+						"color-name": "~1.1.4"
+					}
+				},
+				"color-name": {
+					"version": "1.1.4",
+					"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+					"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+					"optional": true
+				},
+				"fs-extra": {
+					"version": "9.1.0",
+					"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+					"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+					"optional": true,
+					"requires": {
+						"at-least-node": "^1.0.0",
+						"graceful-fs": "^4.2.0",
+						"jsonfile": "^6.0.1",
+						"universalify": "^2.0.0"
+					}
+				},
+				"has-flag": {
+					"version": "4.0.0",
+					"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+					"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+					"optional": true
+				},
+				"jsonfile": {
+					"version": "6.1.0",
+					"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+					"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+					"optional": true,
+					"requires": {
+						"graceful-fs": "^4.1.6",
+						"universalify": "^2.0.0"
+					}
+				},
+				"schema-utils": {
+					"version": "2.7.0",
+					"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
+					"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
+					"optional": true,
+					"requires": {
+						"@types/json-schema": "^7.0.4",
+						"ajv": "^6.12.2",
+						"ajv-keywords": "^3.4.1"
+					}
+				},
+				"semver": {
+					"version": "7.3.5",
+					"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+					"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+					"optional": true,
+					"requires": {
+						"lru-cache": "^6.0.0"
+					}
+				},
+				"supports-color": {
+					"version": "7.2.0",
+					"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+					"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+					"optional": true,
+					"requires": {
+						"has-flag": "^4.0.0"
+					}
+				},
+				"universalify": {
+					"version": "2.0.0",
+					"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+					"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+					"optional": true
+				}
+			}
+		},
 		"form-data": {
 			"version": "4.0.0",
 			"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -31263,11 +31299,6 @@
 			"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
 			"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
 		},
-		"json3": {
-			"version": "3.3.3",
-			"resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz",
-			"integrity": "sha512-c7/8mbUsKigAbLkD5B010BK4D9LZm7A1pNItkEwiUZRpIN66exu/e7YQWysGun+TRKaJp8MhemM+VkfWv42aCA=="
-		},
 		"json5": {
 			"version": "2.2.0",
 			"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
@@ -32042,6 +32073,11 @@
 			"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
 			"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
 		},
+		"lodash.orderby": {
+			"version": "4.6.0",
+			"resolved": "https://registry.npmjs.org/lodash.orderby/-/lodash.orderby-4.6.0.tgz",
+			"integrity": "sha1-5pfwTOXXhSL1TZM4syuBozk+TrM="
+		},
 		"lodash.set": {
 			"version": "4.3.2",
 			"resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",
@@ -32071,6 +32107,11 @@
 				"lodash._reinterpolate": "^3.0.0"
 			}
 		},
+		"lodash.throttle": {
+			"version": "4.1.1",
+			"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
+			"integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ="
+		},
 		"lodash.transform": {
 			"version": "4.6.0",
 			"resolved": "https://registry.npmjs.org/lodash.transform/-/lodash.transform-4.6.0.tgz",
@@ -32437,6 +32478,45 @@
 			"resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz",
 			"integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg=="
 		},
+		"markdown-it": {
+			"version": "12.3.2",
+			"resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz",
+			"integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==",
+			"requires": {
+				"argparse": "^2.0.1",
+				"entities": "~2.1.0",
+				"linkify-it": "^3.0.1",
+				"mdurl": "^1.0.1",
+				"uc.micro": "^1.0.5"
+			},
+			"dependencies": {
+				"argparse": {
+					"version": "2.0.1",
+					"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+					"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+				},
+				"entities": {
+					"version": "2.1.0",
+					"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
+					"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
+				}
+			}
+		},
+		"markdown-it-emoji": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/markdown-it-emoji/-/markdown-it-emoji-2.0.0.tgz",
+			"integrity": "sha512-39j7/9vP/CPCKbEI44oV8yoPJTpvfeReTn/COgRhSpNrjWF3PfP/JUxxB0hxV6ynOY8KH8Y8aX9NMDdo6z+6YQ=="
+		},
+		"markdown-it-link-attributes": {
+			"version": "4.0.0",
+			"resolved": "https://registry.npmjs.org/markdown-it-link-attributes/-/markdown-it-link-attributes-4.0.0.tgz",
+			"integrity": "sha512-ssjxSLlLfQBkX6BvAx1rCPrx7ZoK91llQQvS3P7KXvlbnVD34OUkfXwWecN7su/7mrI/HOW0RI5szdJOIqYC3w=="
+		},
+		"markdown-it-task-lists": {
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/markdown-it-task-lists/-/markdown-it-task-lists-2.1.1.tgz",
+			"integrity": "sha512-TxFAc76Jnhb2OUu+n3yz9RMu4CwGfaT788br6HhEDlvWfdeJcLUsxk1Hgw2yJio0OXsxv7pyIPmvECY7bMbluA=="
+		},
 		"markdown-to-jsx": {
 			"version": "7.1.6",
 			"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.6.tgz",
@@ -33245,11 +33325,11 @@
 			"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
 		},
 		"mssql": {
-			"version": "6.4.0",
-			"resolved": "https://registry.npmjs.org/mssql/-/mssql-6.4.0.tgz",
-			"integrity": "sha512-Mtgu3PXqoaL7aHCMurttvEHibjvz5XKjlR6ZCDyAeKtDBORpxm88JyzEU2EESVf7588GulYKc7Gr+Txf5CICBQ==",
+			"version": "6.4.1",
+			"resolved": "https://registry.npmjs.org/mssql/-/mssql-6.4.1.tgz",
+			"integrity": "sha512-G1I7mM0gfxcH5TGSNoVmxq13Mve5YnQgRAlonqaMlHEjHjMn1g04bsrIQbVHFRdI6++dw/FGWlh8GoItJMoUDw==",
 			"requires": {
-				"debug": "^4.3.2",
+				"debug": "^4.3.3",
 				"tarn": "^1.1.5",
 				"tedious": "^6.7.1"
 			}
@@ -37481,9 +37561,9 @@
 			},
 			"dependencies": {
 				"history": {
-					"version": "5.2.0",
-					"resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz",
-					"integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==",
+					"version": "5.3.0",
+					"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
+					"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
 					"requires": {
 						"@babel/runtime": "^7.7.6"
 					}
@@ -37500,9 +37580,9 @@
 			},
 			"dependencies": {
 				"history": {
-					"version": "5.2.0",
-					"resolved": "https://registry.npmjs.org/history/-/history-5.2.0.tgz",
-					"integrity": "sha512-uPSF6lAJb3nSePJ43hN3eKj1dTWpN9gMod0ZssbFTIsen+WehTmEadgL+kg78xLJFdRfrrC//SavDzmRVdE+Ig==",
+					"version": "5.3.0",
+					"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
+					"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
 					"requires": {
 						"@babel/runtime": "^7.7.6"
 					}
@@ -37814,20 +37894,13 @@
 			"integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg=="
 		},
 		"refractor": {
-			"version": "3.5.0",
-			"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.5.0.tgz",
-			"integrity": "sha512-QwPJd3ferTZ4cSPPjdP5bsYHMytwWYnAN5EEnLtGvkqp/FCCnGsBgxrm9EuIDnjUC3Uc/kETtvVi7fSIVC74Dg==",
+			"version": "3.6.0",
+			"resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz",
+			"integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==",
 			"requires": {
 				"hastscript": "^6.0.0",
 				"parse-entities": "^2.0.0",
-				"prismjs": "~1.25.0"
-			},
-			"dependencies": {
-				"prismjs": {
-					"version": "1.25.0",
-					"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz",
-					"integrity": "sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg=="
-				}
+				"prismjs": "~1.27.0"
 			}
 		},
 		"regenerate": {
@@ -38244,9 +38317,9 @@
 					}
 				},
 				"yargs-parser": {
-					"version": "21.0.0",
-					"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.0.tgz",
-					"integrity": "sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA=="
+					"version": "21.0.1",
+					"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.0.1.tgz",
+					"integrity": "sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg=="
 				}
 			}
 		},
@@ -38744,9 +38817,9 @@
 			}
 		},
 		"sass": {
-			"version": "1.49.8",
-			"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.8.tgz",
-			"integrity": "sha512-NoGOjvDDOU9og9oAxhRnap71QaTjjlzrvLnKecUJ3GxhaQBrV6e7gPuSPF28u1OcVAArVojPAe4ZhOXwwC4tGw==",
+			"version": "1.49.9",
+			"resolved": "https://registry.npmjs.org/sass/-/sass-1.49.9.tgz",
+			"integrity": "sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A==",
 			"requires": {
 				"chokidar": ">=3.0.0 <4.0.0",
 				"immutable": "^4.0.0",
@@ -39475,16 +39548,15 @@
 			}
 		},
 		"sockjs-client": {
-			"version": "1.5.2",
-			"resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.5.2.tgz",
-			"integrity": "sha512-ZzRxPBISQE7RpzlH4tKJMQbHM9pabHluk0WBaxAQ+wm/UieeBVBou0p4wVnSQGN9QmpAZygQ0cDIypWuqOFmFQ==",
+			"version": "1.6.0",
+			"resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.0.tgz",
+			"integrity": "sha512-qVHJlyfdHFht3eBFZdKEXKTlb7I4IV41xnVNo8yUKA1UHcPJwgW2SvTq9LhnjjCywSkSK7c/e4nghU0GOoMCRQ==",
 			"requires": {
-				"debug": "^3.2.6",
-				"eventsource": "^1.0.7",
-				"faye-websocket": "^0.11.3",
+				"debug": "^3.2.7",
+				"eventsource": "^1.1.0",
+				"faye-websocket": "^0.11.4",
 				"inherits": "^2.0.4",
-				"json3": "^3.3.3",
-				"url-parse": "^1.5.3"
+				"url-parse": "^1.5.10"
 			},
 			"dependencies": {
 				"debug": {
@@ -41482,9 +41554,9 @@
 			"integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="
 		},
 		"uglify-js": {
-			"version": "3.15.1",
-			"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.1.tgz",
-			"integrity": "sha512-FAGKF12fWdkpvNJZENacOH0e/83eG6JyVQyanIJaBXCN1J11TUQv1T1/z8S+Z0CG0ZPk1nPcreF/c7lrTd0TEQ==",
+			"version": "3.15.2",
+			"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.2.tgz",
+			"integrity": "sha512-peeoTk3hSwYdoc9nrdiEJk+gx1ALCtTjdYuKSXMTDqq7n1W7dHPqWDdSi+BPL0ni2YMeHD7hKUSdbj3TZauY2A==",
 			"optional": true
 		},
 		"uid-number": {
@@ -41986,9 +42058,9 @@
 			}
 		},
 		"url-parse": {
-			"version": "1.5.8",
-			"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.8.tgz",
-			"integrity": "sha512-9JZ5zDrn9wJoOy/t+rH00HHejbU8dq9VsOYVu272TYDrCiyVAgHKUSpPh3ruZIpv8PMVR+NXLZvfRPJv8xAcQw==",
+			"version": "1.5.10",
+			"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+			"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
 			"requires": {
 				"querystringify": "^2.1.1",
 				"requires-port": "^1.0.0"
@@ -42339,9 +42411,9 @@
 			"integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ=="
 		},
 		"vm2": {
-			"version": "3.9.8",
-			"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.8.tgz",
-			"integrity": "sha512-/1PYg/BwdKzMPo8maOZ0heT7DLI0DAFTm7YQaz/Lim9oIaFZsJs3EdtalvXuBfZwczNwsYhju75NW4d6E+4q+w==",
+			"version": "3.9.9",
+			"resolved": "https://registry.npmjs.org/vm2/-/vm2-3.9.9.tgz",
+			"integrity": "sha512-xwTm7NLh/uOjARRBs8/95H0e8fT3Ukw5D/JJWhxMbhKzNh1Nu981jQKvkep9iKYNxzlVrdzD0mlBGkDKZWprlw==",
 			"requires": {
 				"acorn": "^8.7.0",
 				"acorn-walk": "^8.2.0"
@@ -42369,6 +42441,15 @@
 			"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
 			"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
 		},
+		"vue-agile": {
+			"version": "2.0.0",
+			"resolved": "https://registry.npmjs.org/vue-agile/-/vue-agile-2.0.0.tgz",
+			"integrity": "sha512-5xkSLJQNRdQ7qpEnXj5FgLg33XKRHaTZKGP5qkvteOc/uGJX89MYCjPSgdNqJ1GYFGfdGAp0jvhihW8OMuXS3g==",
+			"requires": {
+				"lodash.orderby": "^4.6.0",
+				"lodash.throttle": "^4.1.1"
+			}
+		},
 		"vue-class-component": {
 			"version": "7.2.6",
 			"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz",
@@ -42383,9 +42464,9 @@
 			}
 		},
 		"vue-docgen-api": {
-			"version": "4.44.15",
-			"resolved": "https://registry.npmjs.org/vue-docgen-api/-/vue-docgen-api-4.44.15.tgz",
-			"integrity": "sha512-JBFe4EAUSmRqRHaNNHqDo1U+w1HRaHh00C0bYKE65HdN9QS6pCJCUBwi1blow0beDzLTAJYCa90xwG61WYBo4A==",
+			"version": "4.44.17",
+			"resolved": "https://registry.npmjs.org/vue-docgen-api/-/vue-docgen-api-4.44.17.tgz",
+			"integrity": "sha512-bU1V9gvXDv5GPaOmXYcnHrc3EjwRxZ8IKFE+Hk7QWpnI5/MYriyt7rf/g/z0JS8u0vGdiYqqUVx6CB+Ghmkm5g==",
 			"requires": {
 				"@babel/parser": "^7.13.12",
 				"@babel/types": "^7.13.12",
@@ -42397,7 +42478,7 @@
 				"pug": "^3.0.2",
 				"recast": "0.20.5",
 				"ts-map": "^1.0.3",
-				"vue-inbrowser-compiler-utils": "^4.44.15"
+				"vue-inbrowser-compiler-utils": "^4.44.17"
 			},
 			"dependencies": {
 				"lru-cache": {
@@ -42473,9 +42554,9 @@
 			"integrity": "sha512-SX35iJHL5PJ4Gfh0Mo/q0shyHiI2V6Zkh51c+k8E9O1RKv5BQyYrCxRzpvPrsIOJEnLaeiovet3dsUB0e/kDzw=="
 		},
 		"vue-inbrowser-compiler-utils": {
-			"version": "4.44.15",
-			"resolved": "https://registry.npmjs.org/vue-inbrowser-compiler-utils/-/vue-inbrowser-compiler-utils-4.44.15.tgz",
-			"integrity": "sha512-dbuZbFNl7q3+MjLyFxD14LnrbYuhexVCbCU9AFJ2zd3zqHrueXSYGzYTLLTZ++fnCMC3J60xe409e/KEft+Cbw==",
+			"version": "4.44.17",
+			"resolved": "https://registry.npmjs.org/vue-inbrowser-compiler-utils/-/vue-inbrowser-compiler-utils-4.44.17.tgz",
+			"integrity": "sha512-dvxumVgIzR4FXjAWYWIOnpD+6bW0dLkoAv43UShER8gVIhLFo9UEmbF31wD6YWJj94lUpbVIuWl2qc6axYNEAQ==",
 			"requires": {
 				"camelcase": "^5.3.1"
 			}
@@ -43914,6 +43995,22 @@
 			"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",
 			"integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM="
 		},
+		"xss": {
+			"version": "1.0.10",
+			"resolved": "https://registry.npmjs.org/xss/-/xss-1.0.10.tgz",
+			"integrity": "sha512-qmoqrRksmzqSKvgqzN0055UFWY7OKx1/9JWeRswwEVX9fCG5jcYRxa/A2DHcmZX6VJvjzHRQ2STeeVcQkrmLSw==",
+			"requires": {
+				"commander": "^2.20.3",
+				"cssfilter": "0.0.10"
+			},
+			"dependencies": {
+				"commander": {
+					"version": "2.20.3",
+					"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+					"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+				}
+			}
+		},
 		"xtend": {
 			"version": "4.0.2",
 			"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts
index 9bd78931a3..85544254c2 100644
--- a/packages/cli/config/index.ts
+++ b/packages/cli/config/index.ts
@@ -690,6 +690,21 @@ const config = convict({
 		},
 	},
 
+	templates: {
+		enabled: {
+			doc: 'Whether templates feature is enabled to load workflow templates.',
+			format: Boolean,
+			default: true,
+			env: 'N8N_TEMPLATES_ENABLED',
+		},
+		host: {
+			doc: 'Endpoint host to retrieve workflow templates from endpoints.',
+			format: String,
+			default: 'https://api.n8n.io/',
+			env: 'N8N_TEMPLATES_HOST',
+		},
+	},
+
 	binaryDataManager: {
 		availableModes: {
 			format: String,
diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts
index f92b91427a..a2f9a2235a 100644
--- a/packages/cli/src/Interfaces.ts
+++ b/packages/cli/src/Interfaces.ts
@@ -412,6 +412,11 @@ export interface IN8nUISettings {
 	personalizationSurvey: IPersonalizationSurvey;
 	defaultLocale: string;
 	logLevel: 'info' | 'debug' | 'warn' | 'error' | 'verbose';
+	deploymentType: string;
+	templates: {
+		enabled: boolean;
+		host: string;
+	};
 }
 
 export interface IPersonalizationSurveyAnswers {
diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts
index 564a0e0dbd..70baa21fcc 100644
--- a/packages/cli/src/Server.ts
+++ b/packages/cli/src/Server.ts
@@ -286,6 +286,11 @@ class App {
 			},
 			defaultLocale: config.get('defaultLocale'),
 			logLevel: config.get('logs.level'),
+			deploymentType: config.get('deployment.type'),
+			templates: {
+				enabled: config.get('templates.enabled'),
+				host: config.get('templates.host'),
+			},
 		};
 	}
 
diff --git a/packages/design-system/.storybook/font-awesome-icons.js b/packages/design-system/.storybook/font-awesome-icons.js
index ca24a7f7a8..d839486fc0 100644
--- a/packages/design-system/.storybook/font-awesome-icons.js
+++ b/packages/design-system/.storybook/font-awesome-icons.js
@@ -17,6 +17,8 @@ import {
 	faCheck,
 	faChevronDown,
 	faChevronUp,
+	faChevronLeft,
+	faChevronRight,
 	faCode,
 	faCodeBranch,
 	faCog,
@@ -100,6 +102,8 @@ library.add(faCalendar);
 library.add(faCheck);
 library.add(faChevronDown);
 library.add(faChevronUp);
+library.add(faChevronLeft);
+library.add(faChevronRight);
 library.add(faCode);
 library.add(faCodeBranch);
 library.add(faCog);
diff --git a/packages/design-system/package.json b/packages/design-system/package.json
index 6a209215e6..bb412f9cde 100644
--- a/packages/design-system/package.json
+++ b/packages/design-system/package.json
@@ -30,14 +30,14 @@
     "@fortawesome/free-solid-svg-icons": "5.x",
     "@fortawesome/vue-fontawesome": "2.x",
     "core-js": "3.x",
-    "element-ui": "2.13.x"
+    "element-ui": "2.15.x"
   },
   "devDependencies": {
     "@fortawesome/fontawesome-svg-core": "^1.2.35",
     "@fortawesome/free-solid-svg-icons": "^5.15.3",
     "@fortawesome/vue-fontawesome": "^2.0.2",
     "core-js": "^3.6.5",
-    "element-ui": "~2.13.0",
+    "element-ui": "2.15.x",
     "storybook-addon-themes": "^6.1.0",
     "vue": "^2.6.11",
     "vue-class-component": "^7.2.3",
@@ -48,6 +48,7 @@
     "@storybook/addon-links": "^6.3.6",
     "@storybook/vue": "^6.3.6",
     "@types/jest": "^26.0.13",
+    "@types/markdown-it": "^12.2.3",
     "@typescript-eslint/eslint-plugin": "^4.29.0",
     "@typescript-eslint/parser": "^4.29.0",
     "@vue/cli-plugin-babel": "~4.5.0",
@@ -63,6 +64,10 @@
     "eslint-plugin-prettier": "^3.4.0",
     "eslint-plugin-vue": "^7.16.0",
     "gulp": "^4.0.0",
+    "markdown-it": "^12.3.2",
+    "markdown-it-emoji": "^2.0.0",
+    "markdown-it-link-attributes": "^4.0.0",
+    "markdown-it-task-lists": "^2.1.1",
     "prettier": "^2.3.2",
     "sass": "^1.26.5",
     "sass-loader": "^8.0.2",
@@ -74,6 +79,7 @@
     "gulp-clean-css": "^4.3.0",
     "gulp-dart-sass": "^1.0.2",
     "node-notifier": ">=8.0.1",
-    "trim": ">=0.0.3"
+    "trim": ">=0.0.3",
+    "xss": "^1.0.10"
   }
 }
diff --git a/packages/design-system/src/components/N8nLoading/Loading.stories.js b/packages/design-system/src/components/N8nLoading/Loading.stories.js
new file mode 100644
index 0000000000..2d08e3a409
--- /dev/null
+++ b/packages/design-system/src/components/N8nLoading/Loading.stories.js
@@ -0,0 +1,43 @@
+import N8nLoading from './Loading.vue';
+
+export default {
+	title: 'Atoms/Loading',
+	component: N8nLoading,
+	argTypes: {
+		animated: {
+			control: {
+				type: 'boolean',
+			},
+		},
+		loading: {
+			control: {
+				type: 'boolean',
+			},
+		},
+		rows: {
+			control: {
+				type: 'select',
+				options: [1, 2, 3, 4, 5],
+			},
+		},
+		variant: {
+			control: {
+				type: 'select',
+				options: ['button', 'h1', 'image', 'p'],
+			},
+		},
+	},
+};
+
+const Template = (args, { argTypes }) => ({
+	props: Object.keys(argTypes),
+	components: {
+		N8nLoading,
+	},
+	template: '<n8n-loading v-bind="$props"></n8n-loading>',
+});
+
+export const Loading = Template.bind({});
+Loading.args = {
+	variant: 'p',
+};
diff --git a/packages/design-system/src/components/N8nLoading/Loading.vue b/packages/design-system/src/components/N8nLoading/Loading.vue
new file mode 100644
index 0000000000..011a8bfab5
--- /dev/null
+++ b/packages/design-system/src/components/N8nLoading/Loading.vue
@@ -0,0 +1,86 @@
+<template>
+	<el-skeleton :loading="loading" :animated="animated">
+		<template slot="template">
+			<el-skeleton-item
+				v-if="variant === 'button'"
+				:variant="variant"
+			/>
+
+			<div v-if="variant === 'h1'">
+				<div
+					v-for="(item, index) in rows"
+					:key="index"
+					:class="{
+						[$style.h1Last]: item === rows && rows > 1 && shrinkLast,
+					}"
+				>
+					<el-skeleton-item
+						:variant="variant"
+					/>
+				</div>
+			</div>
+			<el-skeleton-item
+				v-if="variant === 'image'"
+				:variant="variant"
+			/>
+			<div v-if="variant === 'p'">
+				<div
+					v-for="(item, index) in rows"
+					:key="index"
+					:class="{
+						[$style.pLast]: item === rows && rows > 1 && shrinkLast,
+					}">
+						<el-skeleton-item
+							:variant="variant"
+						/>
+				</div>
+			</div>
+		</template>
+	</el-skeleton>
+</template>
+
+<script lang="ts">
+import ElSkeleton from 'element-ui/lib/skeleton';
+import ElSkeletonItem from 'element-ui/lib/skeleton-item';
+
+export default {
+	name: 'n8n-loading',
+	components: {
+		ElSkeleton,
+		ElSkeletonItem,
+	},
+	props: {
+		animated: {
+			type: Boolean,
+			default: true,
+		},
+		loading: {
+			type: Boolean,
+			default: true,
+		},
+		rows: {
+			type: Number,
+			default: 1,
+		},
+		shrinkLast: {
+			type: Boolean,
+			default: true,
+		},
+		variant: {
+			type: String,
+			default: 'p',
+			validator: (value: string): boolean => ['p', 'h1', 'button', 'image'].includes(value),
+		},
+	},
+};
+</script>
+
+<style lang="scss" module>
+.h1Last {
+  width: 40%;
+}
+
+.pLast {
+  width: 61%;
+}
+</style>
diff --git a/packages/design-system/src/components/N8nLoading/index.js b/packages/design-system/src/components/N8nLoading/index.js
new file mode 100644
index 0000000000..a7420bd670
--- /dev/null
+++ b/packages/design-system/src/components/N8nLoading/index.js
@@ -0,0 +1,3 @@
+import N8nLoading from './Loading.vue';
+
+export default N8nLoading;
diff --git a/packages/design-system/src/components/N8nMarkdown/Markdown.stories.js b/packages/design-system/src/components/N8nMarkdown/Markdown.stories.js
new file mode 100644
index 0000000000..bd77554449
--- /dev/null
+++ b/packages/design-system/src/components/N8nMarkdown/Markdown.stories.js
@@ -0,0 +1,48 @@
+import N8nMarkdown from './Markdown.vue';
+
+export default {
+	title: 'Atoms/Markdown',
+	component: N8nMarkdown,
+	argTypes: {
+		content: {
+			control: {
+				type: 'text',
+			},
+		},
+		loading: {
+			control: {
+				type: 'boolean',
+			},
+		},
+		loadingBlocks: {
+			control: {
+				type: 'select',
+				options: [1, 2, 3, 4, 5],
+			},
+		},
+		loadingRows: {
+			control: {
+				type: 'select',
+				options: [1, 2, 3, 4, 5],
+			},
+		},
+	},
+};
+
+const Template = (args, { argTypes }) => ({
+	props: Object.keys(argTypes),
+	components: {
+		N8nMarkdown,
+	},
+	template: '<n8n-markdown v-bind="$props"></n8n-markdown>',
+});
+
+export const Markdown = Template.bind({});
+Markdown.args = {
+	content: `I wanted a system to monitor website content changes and notify me. So I made it using n8n.\n\nEspecially my competitor blogs. I wanted to know how often they are posting new articles. (I used their sitemap.xml file) (The below workflow may vary)\n\nIn the Below example, I used HackerNews for example.\n\nExplanation:\n\n- First HTTP Request node crawls the webpage and grabs the website source code\n- Then wait for x minutes\n- Again, HTTP Node crawls the webpage\n- If Node compares both results are equal if anything is changed. It’ll go to the false branch and notify me in telegram.\n\n**Workflow:**\n\n![](fileId:1)\n\n**Sample Response:**\n\n![](https://community.n8n.io/uploads/default/original/2X/d/d21ba41d7ac9ff5cd8148fedb07d0f1ff53b2529.png)\n`,
+	loading: false,
+	images: [{
+		id: 1,
+		url: 'https://community.n8n.io/uploads/default/optimized/2X/b/b737a95de4dfe0825d50ca098171e9f33a459e74_2_690x288.png',
+	}],
+};
diff --git a/packages/design-system/src/components/N8nMarkdown/Markdown.vue b/packages/design-system/src/components/N8nMarkdown/Markdown.vue
new file mode 100644
index 0000000000..b94d93ac3c
--- /dev/null
+++ b/packages/design-system/src/components/N8nMarkdown/Markdown.vue
@@ -0,0 +1,220 @@
+<template>
+	<div>
+		<div v-if="!loading" ref="editor" :class="$style.markdown" v-html="htmlContent" />
+		<div v-else :class="$style.markdown">
+			<div v-for="(block, index) in loadingBlocks"
+				:key="index">
+				<n8n-loading
+					:loading="loading"
+					:rows="loadingRows"
+					animated
+					variant="p"
+				/>
+				<div :class="$style.spacer" />
+			</div>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import N8nLoading from '../N8nLoading';
+import Markdown from 'markdown-it';
+const markdownLink = require('markdown-it-link-attributes');
+const markdownEmoji = require('markdown-it-emoji');
+const markdownTasklists = require('markdown-it-task-lists');
+
+import xss from 'xss';
+import { escapeMarkdown } from '../../utils/markdown';
+
+const DEFAULT_OPTIONS_MARKDOWN = {
+	html: true,
+	linkify: true,
+	typographer: true,
+	breaks: true,
+};
+
+const DEFAULT_OPTIONS_LINK_ATTRIBUTES = {
+	attrs: {
+		target: '_blank',
+		rel: 'noopener',
+	},
+};
+
+const DEFAULT_OPTIONS_TASKLISTS = {
+	label: true,
+	labelAfter: true,
+};
+
+interface IImage {
+	id: string;
+	url: string;
+}
+
+export default {
+	components: {
+		N8nLoading,
+	},
+	name: 'n8n-markdown',
+	props: {
+		content: {
+			type: String,
+		},
+		images: {
+			type: Array,
+		},
+		loading: {
+			type: Boolean,
+		},
+		loadingBlocks: {
+			type: Number,
+			default: 2,
+		},
+		loadingRows: {
+			type: Number,
+			default: () => {
+				return 3;
+			},
+		},
+		options: {
+			type: Object,
+			default() {
+				return {
+					markdown: DEFAULT_OPTIONS_MARKDOWN,
+					linkAttributes: DEFAULT_OPTIONS_LINK_ATTRIBUTES,
+					tasklists: DEFAULT_OPTIONS_TASKLISTS,
+				};
+			},
+		},
+	},
+	computed: {
+		htmlContent(): string {
+			if (!this.content) {
+				 return '';
+			}
+
+			const imageUrls: { [key: string]: string } = {};
+			if (this.images) {
+				// @ts-ignore
+				this.images.forEach((image: IImage) => {
+					if (!image) {
+						// Happens if an image got deleted but the workflow
+						// still has a reference to it
+						return;
+					}
+					imageUrls[image.id] = image.url;
+				});
+			}
+
+			const fileIdRegex = new RegExp('fileId:([0-9]+)');
+			const html = this.md.render(escapeMarkdown(this.content));
+			const safeHtml = xss(html, {
+				onTagAttr: (tag, name, value, isWhiteAttr) => {
+					if (tag === 'img' && name === 'src') {
+						if (value.match(fileIdRegex)) {
+							const id = value.split('fileId:')[1];
+							return `src=${xss.friendlyAttrValue(imageUrls[id])}` || '';
+						}
+						if (!value.startsWith('https://')) {
+							return '';
+						}
+					}
+					// Return nothing, means keep the default handling measure
+				},
+				onTag: function (tag, html, options) {
+					if (tag === 'img' && html.includes(`alt="workflow-screenshot"`)) {
+						return '';
+					}
+					// return nothing, keep tag
+				},
+			});
+
+			return safeHtml;
+		},
+	},
+	data() {
+		return {
+			md: new Markdown(this.options.markdown)
+				.use(markdownLink, this.options.linkAttributes)
+				.use(markdownEmoji)
+				.use(markdownTasklists, this.options.tasklists),
+		};
+	},
+};
+</script>
+
+<style lang="scss" module>
+.markdown {
+	color: var(--color-text-base);
+
+	* {
+		font-size: var(--font-size-m);
+		line-height: var(--font-line-height-xloose);
+	}
+
+	h1, h2, h3, h4 {
+		margin-bottom: var(--spacing-s);
+		font-size: var(--font-size-m);
+		font-weight: var(--font-weight-bold);
+	}
+
+	h3, h4 {
+		font-weight: var(--font-weight-bold);
+	}
+
+	p,
+	span {
+		margin-bottom: var(--spacing-s);
+	}
+
+	ul, ol {
+		margin-bottom: var(--spacing-s);
+		padding-left: var(--spacing-m);
+
+		li {
+			margin-top: 0.25em;
+		}
+	}
+
+	pre {
+		margin-bottom: var(--spacing-s);
+		display: grid;
+	}
+
+	pre > code {
+		display: block;
+		padding: var(--spacing-s);
+		color: var(--color-text-dark);
+		background-color: var(--color-background-base);
+		overflow-x: auto;
+	}
+
+	li > code,
+	p > code {
+		padding: 0 var(--spacing-4xs);
+		color: var(--color-text-dark);
+		background-color: var(--color-background-base);
+	}
+
+	.label {
+		color: var(--color-text-base);
+	}
+
+	img {
+		width: 100%;
+		max-height: 90vh;
+		object-fit: cover;
+		border: var(--border-width-base) var(--color-foreground-base) var(--border-style-base);
+		border-radius: var(--border-radius-large);
+	}
+
+	blockquote {
+		padding-left: 10px;
+		font-style: italic;
+		border-left: var(--border-color-base) 2px solid;
+	}
+}
+
+.spacer {
+	margin: var(--spacing-2xl);
+}
+</style>
diff --git a/packages/design-system/src/components/N8nMarkdown/index.js b/packages/design-system/src/components/N8nMarkdown/index.js
new file mode 100644
index 0000000000..3751d0c839
--- /dev/null
+++ b/packages/design-system/src/components/N8nMarkdown/index.js
@@ -0,0 +1,3 @@
+import N8nMarkdown from './Markdown.vue';
+
+export default N8nMarkdown;
diff --git a/packages/design-system/src/components/N8nTag/Tag.stories.js b/packages/design-system/src/components/N8nTag/Tag.stories.js
new file mode 100644
index 0000000000..c204d5c2dd
--- /dev/null
+++ b/packages/design-system/src/components/N8nTag/Tag.stories.js
@@ -0,0 +1,27 @@
+import N8nTag from './Tag.vue';
+
+export default {
+	title: 'Atoms/Tag',
+	component: N8nTag,
+	argTypes: {
+		text: {
+			control: {
+				control: 'text',
+			},
+		},
+	},
+};
+
+const Template = (args, { argTypes }) => ({
+	props: Object.keys(argTypes),
+	components: {
+		N8nTag,
+	},
+	template:
+		'<n8n-tag v-bind="$props"></n8n-tag>',
+});
+
+export const Tag = Template.bind({});
+Tag.args = {
+	text: 'tag name',
+};
diff --git a/packages/design-system/src/components/N8nTag/Tag.vue b/packages/design-system/src/components/N8nTag/Tag.vue
new file mode 100644
index 0000000000..401e4f24b5
--- /dev/null
+++ b/packages/design-system/src/components/N8nTag/Tag.vue
@@ -0,0 +1,25 @@
+<template functional>
+	<span :class="$style.tag" v-text="props.text" @click="(e) => listeners.click && listeners.click(e)" />
+</template>
+
+<script lang="ts">
+export default {
+	name: 'n8n-tag',
+	props: {
+		text: {
+			type: String,
+		},
+	},
+};
+</script>
+
+<style lang="scss" module>
+.tag {
+	min-width: max-content;
+	padding: var(--spacing-4xs);
+	background-color: var(--color-foreground-base);
+	border-radius: var(--border-radius-base);
+	font-size: var(--font-size-2xs);
+	cursor: pointer;
+}
+</style>
diff --git a/packages/design-system/src/components/N8nTag/index.js b/packages/design-system/src/components/N8nTag/index.js
new file mode 100644
index 0000000000..5e8a0d45a6
--- /dev/null
+++ b/packages/design-system/src/components/N8nTag/index.js
@@ -0,0 +1,3 @@
+import Tag from './Tag.vue';
+
+export default Tag;
diff --git a/packages/design-system/src/components/N8nTags/Tags.stories.js b/packages/design-system/src/components/N8nTags/Tags.stories.js
new file mode 100644
index 0000000000..5b3370f349
--- /dev/null
+++ b/packages/design-system/src/components/N8nTags/Tags.stories.js
@@ -0,0 +1,35 @@
+import N8nTags from './Tags.vue';
+
+export default {
+	title: 'Atoms/Tags',
+	component: N8nTags,
+	argTypes: {
+	},
+};
+
+const Template = (args, { argTypes }) => ({
+	props: Object.keys(argTypes),
+	components: {
+		N8nTags,
+	},
+	template:
+    '<n8n-tags v-bind="$props"></n8n-tags>',
+});
+
+export const Tags = Template.bind({});
+Tags.args = {
+	tags: [
+		{
+			id: 1,
+			name: 'very long tag name',
+		},
+		{
+			id: 2,
+			name: 'tag1',
+		},
+		{
+			id: 3,
+			name: 'tag2 yo',
+		},
+	],
+};
diff --git a/packages/design-system/src/components/N8nTags/Tags.vue b/packages/design-system/src/components/N8nTags/Tags.vue
new file mode 100644
index 0000000000..cad6716ae3
--- /dev/null
+++ b/packages/design-system/src/components/N8nTags/Tags.vue
@@ -0,0 +1,32 @@
+<template functional>
+	<div :class="$style.tags">
+		<component :is="$options.components.N8nTag" v-for="tag in props.tags" :key="tag.id" :text="tag.name" @click="(e) => listeners.click && listeners.click(tag.id, e)"/>
+	</div>
+</template>
+
+<script lang="ts">
+import N8nTag from '../N8nTag';
+
+export default {
+	name: 'n8n-tags',
+	components: {
+		N8nTag,
+	},
+	props: {
+		tags: {
+			type: Array,
+		},
+	},
+};
+</script>
+
+<style lang="scss" module>
+.tags {
+	display: flex;
+	flex-wrap: wrap;
+
+	* {
+		margin: 0 var(--spacing-4xs) var(--spacing-4xs) 0;
+	}
+}
+</style>
diff --git a/packages/design-system/src/components/N8nTags/index.js b/packages/design-system/src/components/N8nTags/index.js
new file mode 100644
index 0000000000..ebf28f6470
--- /dev/null
+++ b/packages/design-system/src/components/N8nTags/index.js
@@ -0,0 +1,3 @@
+import Tags from './Tags.vue';
+
+export default Tags;
diff --git a/packages/design-system/src/components/index.js b/packages/design-system/src/components/index.js
index a82ddc6e3f..c62ee197e9 100644
--- a/packages/design-system/src/components/index.js
+++ b/packages/design-system/src/components/index.js
@@ -5,12 +5,16 @@ import N8nInput from './N8nInput';
 import N8nInfoTip from './N8nInfoTip';
 import N8nInputNumber from './N8nInputNumber';
 import N8nInputLabel from './N8nInputLabel';
+import N8nLoading from './N8nLoading';
 import N8nHeading from './N8nHeading';
+import N8nMarkdown from './N8nMarkdown';
 import N8nMenu from './N8nMenu';
 import N8nMenuItem from './N8nMenuItem';
 import N8nSelect from './N8nSelect';
 import N8nSpinner from './N8nSpinner';
 import N8nSquareButton from './N8nSquareButton';
+import N8nTags from './N8nTags';
+import N8nTag from './N8nTag';
 import N8nText from './N8nText';
 import N8nTooltip from './N8nTooltip';
 import N8nOption from './N8nOption';
@@ -23,12 +27,16 @@ export {
 	N8nInput,
 	N8nInputLabel,
 	N8nInputNumber,
+	N8nLoading,
+	N8nMarkdown,
 	N8nHeading,
 	N8nMenu,
 	N8nMenuItem,
 	N8nSelect,
 	N8nSpinner,
 	N8nSquareButton,
+	N8nTags,
+	N8nTag,
 	N8nText,
 	N8nTooltip,
 	N8nOption,
diff --git a/packages/design-system/src/shims-element-ui.d.ts b/packages/design-system/src/shims-element-ui.d.ts
index e59e86f062..645df50e69 100644
--- a/packages/design-system/src/shims-element-ui.d.ts
+++ b/packages/design-system/src/shims-element-ui.d.ts
@@ -6,4 +6,6 @@ declare module 'element-ui/lib/select';
 declare module 'element-ui/lib/option';
 declare module 'element-ui/lib/menu';
 declare module 'element-ui/lib/menu-item';
+declare module 'element-ui/lib/skeleton';
+declare module 'element-ui/lib/skeleton-item';
 
diff --git a/packages/design-system/src/utils/markdown.ts b/packages/design-system/src/utils/markdown.ts
new file mode 100644
index 0000000000..8d757580d3
--- /dev/null
+++ b/packages/design-system/src/utils/markdown.ts
@@ -0,0 +1,12 @@
+export const escapeMarkdown = (html: string | undefined): string => {
+	if (!html) {
+		return '';
+	}
+	const escaped = html.replace(/</g, "&lt;").replace(/>/g, "&gt;");
+	// unescape greater than quotes at start of line
+	const withQuotes = escaped.replace(/^((\s)*(&gt;)+)+\s*/gm, (matches) => {
+		return matches.replace(/&gt;/g, '>');
+	});
+
+	return withQuotes;
+};
diff --git a/packages/design-system/theme/src/index.scss b/packages/design-system/theme/src/index.scss
index 353317e1c3..953f306f1a 100644
--- a/packages/design-system/theme/src/index.scss
+++ b/packages/design-system/theme/src/index.scss
@@ -22,6 +22,7 @@
 // @use "./checkbox-group.scss";
 @use "./switch.scss";
 @use "./select.scss";
+@use "./skeleton.scss";
 @use "./button.scss";
 // @use "./button-group.scss";
 @use "./table.scss";
diff --git a/packages/design-system/theme/src/skeleton.scss b/packages/design-system/theme/src/skeleton.scss
new file mode 100644
index 0000000000..7a0dc72ec5
--- /dev/null
+++ b/packages/design-system/theme/src/skeleton.scss
@@ -0,0 +1,82 @@
+.el-skeleton {
+  width: 100%;
+}
+
+.el-skeleton__item {
+  width: 100%;
+  height: 16px;
+  border-radius: var(--border-radius-large);
+  background: var(--color-background-base);
+  display: inline-block;
+}
+
+.el-skeleton__button {
+  width: 162px;
+  height: 40px;
+  border-radius: 20px;
+}
+
+.el-skeleton__p {
+  width: 100%;
+  height: 16px;
+  margin-top: 16px;
+}
+
+.el-skeleton__h1 {
+  height: 20px;
+  margin-top: 14px;
+}
+
+.el-skeleton__image {
+  width: unset;
+  height: 500px !important;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: center;
+  -ms-flex-align: center;
+  align-items: center;
+  -webkit-box-pack: center;
+  -ms-flex-pack: center;
+  justify-content: center;
+  border-radius: 8px !important;
+}
+
+.el-skeleton__image svg {
+  width: 22%;
+  height: 22%;
+  fill: var(--color-info-tint-1);
+}
+
+.el-skeleton__first-line,
+.el-skeleton__paragraph {
+  background: var(--color-background-base);
+}
+
+.el-skeleton.is-animated .el-skeleton__item {
+  background: -webkit-gradient(linear, left top, right top, color-stop(25%, #f2f2f2), color-stop(37%, #e6e6e6), color-stop(63%, #f2f2f2));
+  background: linear-gradient(90deg, #f2f2f2 25%, #e6e6e6 37%, #f2f2f2 63%);
+  background-size: 400% 100%;
+  -webkit-animation: el-skeleton-loading 1.4s ease infinite;
+  animation: el-skeleton-loading 1.4s ease infinite;
+}
+
+@-webkit-keyframes el-skeleton-loading {
+  0% {
+    background-position: 100% 50%;
+  }
+
+  100% {
+    background-position: 0 50%;
+  }
+}
+
+@keyframes el-skeleton-loading {
+  0% {
+    background-position: 100% 50%;
+  }
+
+  100% {
+    background-position: 0 50%;
+  }
+}
diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json
index 05faa59e9d..0e221856a9 100644
--- a/packages/editor-ui/package.json
+++ b/packages/editor-ui/package.json
@@ -31,7 +31,8 @@
     "timeago.js": "^4.0.2",
     "v-click-outside": "^3.1.2",
     "vue-fragment": "^1.5.2",
-    "vue-i18n": "^8.26.7"
+    "vue-i18n": "^8.26.7",
+    "xss": "^1.0.10"
   },
   "devDependencies": {
     "@fortawesome/fontawesome-svg-core": "^1.2.35",
@@ -62,7 +63,7 @@
     "babel-eslint": "^10.0.1",
     "cross-env": "^7.0.2",
     "dateformat": "^3.0.3",
-    "element-ui": "~2.13.0",
+    "element-ui": "~2.15.7",
     "eslint": "^7.32.0",
     "eslint-plugin-import": "^2.23.4",
     "eslint-plugin-vue": "^7.16.0",
@@ -89,6 +90,7 @@
     "typescript": "~4.3.5",
     "uuid": "^8.3.2",
     "vue": "^2.6.11",
+    "vue-agile": "^2.0.0",
     "vue-cli-plugin-webpack-bundle-analyzer": "^2.0.0",
     "vue-json-pretty": "1.7.1",
     "vue-prism-editor": "^0.3.0",
diff --git a/packages/editor-ui/src/App.vue b/packages/editor-ui/src/App.vue
index c248d1f626..0487c89a1c 100644
--- a/packages/editor-ui/src/App.vue
+++ b/packages/editor-ui/src/App.vue
@@ -1,46 +1,125 @@
 <template>
-	<div id="app">
-		<div id="header">
-			<router-view name="header"></router-view>
+	<div>
+		<LoadingView v-if="loading" />
+		<div v-else id="app">
+			<div id="header">
+				<router-view name="header"></router-view>
+			</div>
+			<div id="sidebar">
+				<router-view name="sidebar"></router-view>
+			</div>
+			<div id="content">
+				<router-view />
+			</div>
+			<Modals />
+			<Telemetry />
 		</div>
-		<div id="sidebar">
-			<router-view name="sidebar"></router-view>
-		</div>
-		<div id="content">
-			<router-view />
-		</div>
-		<Telemetry />
 	</div>
 </template>
 
 <script lang="ts">
-import Vue from 'vue';
+import { mapGetters } from 'vuex';
 import Telemetry from './components/Telemetry.vue';
+import { HIRING_BANNER } from './constants';
+import Modals from '@/components/Modals.vue';
+import LoadingView from './views/LoadingView.vue';
+import mixins from 'vue-typed-mixins';
+import { showMessage } from './components/mixins/showMessage';
 
-export default Vue.extend({
+export default mixins(showMessage).extend({
 	name: 'App',
 	components: {
+		LoadingView,
+		Modals,
 		Telemetry,
 	},
-	mounted() {
-		this.$telemetry.page('Editor', this.$route.name);
+	computed: {
+		...mapGetters('settings', ['isInternalUser', 'isTemplatesEnabled', 'isTemplatesEndpointReachable']),
+		isRootPath(): boolean {
+			return this.$route.path === '/';
+		},
+	},
+	data() {
+		return {
+			loading: true,
+		};
+	},
+	methods: {
+		async initSettings(): Promise<void> {
+			try {
+				await this.$store.dispatch('settings/getSettings');
+			} catch (e) {
+				this.$showToast({
+					title: this.$locale.baseText('settings.errors.connectionError.title'),
+					message: this.$locale.baseText('settings.errors.connectionError.message'),
+					type: 'error',
+					duration: 0,
+				});
+
+				throw e;
+			}
+		},
+		async initTemplates(): Promise<void> {
+			try {
+				const templatesPromise = this.$store.dispatch('settings/testTemplatesEndpoint');
+				if (this.isRootPath) { // only delay loading to determine redirect
+					await templatesPromise;
+				}
+			} catch (e) {
+			}
+		},
+		async initialize(): Promise<void> {
+			await this.initSettings();
+			await this.initTemplates();
+
+			if (!this.isInternalUser && this.$route.name !== 'WorkflowDemo') {
+				console.log(HIRING_BANNER); // eslint-disable-line no-console
+			}
+		},
+		trackPage() {
+			this.$store.commit('ui/setCurrentView', this.$route.name);
+			if (this.$route && this.$route.meta && this.$route.meta.templatesEnabled) {
+				this.$store.commit('templates/setSessionId');
+			}
+			else {
+				this.$store.commit('templates/resetSessionId'); // reset telemetry session id when user leaves template pages
+			}
+
+			this.$telemetry.page('Editor', this.$route);
+		},
+	},
+	async mounted() {
+		await this.initialize();
+
+		if (this.isTemplatesEnabled && this.isTemplatesEndpointReachable && this.isRootPath) {
+			this.$router.replace({ name: 'TemplatesSearchView'});
+		} else if (this.isRootPath) {
+			this.$router.replace({ name: 'NodeViewNew'});
+		}
+		else if (!this.isTemplatesEnabled && this.$route.meta && this.$route.meta.templatesEnabled) {
+			this.$router.replace({ name: 'NodeViewNew'});
+		}
+		this.loading = false;
+
+		this.trackPage();
+		this.$externalHooks().run('app.mount');
 	},
 	watch: {
-		'$route'(route) {
-			this.$telemetry.page('Editor', route.name);
+		'$route'() {
+			this.trackPage();
 		},
 	},
 });
 </script>
 
 <style lang="scss">
-
 #app {
 	padding: 0;
 	margin: 0 auto;
 }
 
 #content {
+	background-color: var(--color-background-light);
 	position: relative;
 	top: 0;
 	left: 0;
diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts
index 08460e715d..d6b33d0091 100644
--- a/packages/editor-ui/src/Interface.ts
+++ b/packages/editor-ui/src/Interface.ts
@@ -253,7 +253,7 @@ export interface IWorkflowDataUpdate {
 }
 
 export interface IWorkflowTemplate {
-	id: string;
+	id: number;
 	name: string;
 	workflow: {
 		nodes: INodeUi[];
@@ -514,6 +514,64 @@ export interface IN8nPromptResponse {
 	updated: boolean;
 }
 
+export interface ITemplatesCollection {
+	id: number;
+	name: string;
+	nodes: ITemplatesNode[];
+	workflows: Array<{id: number}>;
+}
+
+interface ITemplatesImage {
+	id: number;
+	url: string;
+}
+
+interface ITemplatesCollectionExtended extends ITemplatesCollection {
+	description: string | null;
+	image: ITemplatesImage[];
+	categories: ITemplatesCategory[];
+	createdAt: string;
+}
+
+export interface ITemplatesCollectionFull extends ITemplatesCollectionExtended {
+	full: true;
+}
+
+export interface ITemplatesCollectionResponse extends ITemplatesCollectionExtended {
+	workflows: ITemplatesWorkflow[];
+}
+
+export interface ITemplatesWorkflow {
+	id: number;
+	createdAt: string;
+	name: string;
+	nodes: ITemplatesNode[];
+	totalViews: number;
+	user: {
+		username: string;
+	};
+}
+
+export interface ITemplatesWorkflowResponse extends ITemplatesWorkflow, IWorkflowTemplate {
+	description: string | null;
+	image: ITemplatesImage[];
+	categories: ITemplatesCategory[];
+}
+
+export interface ITemplatesWorkflowFull extends ITemplatesWorkflowResponse {
+	full: true;
+}
+
+export interface ITemplatesQuery {
+	categories: number[];
+	search: string;
+}
+
+export interface ITemplatesCategory {
+	id: number;
+	name: string;
+}
+
 export interface IN8nUISettings {
 	endpointWebhook: string;
 	endpointWebhookTest: string;
@@ -538,6 +596,11 @@ export interface IN8nUISettings {
 	telemetry: ITelemetrySettings;
 	defaultLocale: string;
 	logLevel: ILogLevel;
+	deploymentType: string;
+	templates: {
+		enabled: boolean;
+		host: string;
+	};
 }
 
 export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
@@ -644,7 +707,13 @@ export interface IVersionNode {
 		icon?: string;
 		fileBuffer?: string;
 	};
+	typeVersion?: number;
 }
+
+export interface ITemplatesNode extends IVersionNode {
+	categories?: ITemplatesCategory[];
+}
+
 export interface IRootState {
 	activeExecutions: IExecutionsCurrentSummaryExtended[];
 	activeWorkflows: string[];
@@ -717,6 +786,7 @@ export interface IUiState {
 		[key: string]: IModalState;
 	};
 	isPageLoading: boolean;
+	currentView: string;
 }
 
 export type ILogLevel = 'info' | 'debug' | 'warn' | 'error' | 'verbose';
@@ -724,6 +794,27 @@ export type ILogLevel = 'info' | 'debug' | 'warn' | 'error' | 'verbose';
 export interface ISettingsState {
 	settings: IN8nUISettings;
 	promptsData: IN8nPrompts;
+	templatesEndpointHealthy: boolean;
+}
+
+export interface ITemplateState {
+	categories: {[id: string]: ITemplatesCategory};
+	collections: {[id: string]: ITemplatesCollection};
+	workflows: {[id: string]: ITemplatesWorkflow};
+	workflowSearches: {
+		[search: string]: {
+			workflowIds: string[];
+			totalWorkflows: number;
+			loadingMore?: boolean;
+		}
+	};
+	collectionSearches: {
+		[search: string]: {
+			collectionIds: string[];
+		}
+	};
+	currentSessionId: string;
+	previousSessionId: string;
 }
 
 export interface IVersionsState {
diff --git a/packages/editor-ui/src/api/settings.ts b/packages/editor-ui/src/api/settings.ts
index 6609524149..25702a29b1 100644
--- a/packages/editor-ui/src/api/settings.ts
+++ b/packages/editor-ui/src/api/settings.ts
@@ -1,7 +1,7 @@
 import { IDataObject } from 'n8n-workflow';
 import { IRestApiContext, IN8nPrompts, IN8nValueSurveyData, IN8nUISettings, IPersonalizationSurveyAnswers } from '../Interface';
 import { makeRestApiRequest, get, post } from './helpers';
-import { TEMPLATES_BASE_URL } from '@/constants';
+import { N8N_IO_BASE_URL } from '@/constants';
 
 export async function getSettings(context: IRestApiContext): Promise<IN8nUISettings> {
 	return await makeRestApiRequest(context, 'GET', '/settings');
@@ -12,14 +12,14 @@ export async function submitPersonalizationSurvey(context: IRestApiContext, para
 }
 
 export async function getPromptsData(instanceId: string): Promise<IN8nPrompts> {
-	return await get(TEMPLATES_BASE_URL, '/prompts', {}, {'n8n-instance-id': instanceId});
+	return await get(N8N_IO_BASE_URL, '/prompts', {}, {'n8n-instance-id': instanceId});
 }
 
 export async function submitContactInfo(instanceId: string, email: string): Promise<void> {
-	return await post(TEMPLATES_BASE_URL, '/prompt', { email }, {'n8n-instance-id': instanceId});
+	return await post(N8N_IO_BASE_URL, '/prompt', { email }, {'n8n-instance-id': instanceId});
 }
 
 export async function submitValueSurvey(instanceId: string, params: IN8nValueSurveyData): Promise<IN8nPrompts> {
-	return await post(TEMPLATES_BASE_URL, '/value-survey', params, {'n8n-instance-id': instanceId});
+	return await post(N8N_IO_BASE_URL, '/value-survey', params, {'n8n-instance-id': instanceId});
 }
 
diff --git a/packages/editor-ui/src/api/templates.ts b/packages/editor-ui/src/api/templates.ts
new file mode 100644
index 0000000000..15efc8e8d7
--- /dev/null
+++ b/packages/editor-ui/src/api/templates.ts
@@ -0,0 +1,39 @@
+import { ITemplatesCategory, ITemplatesCollection, ITemplatesQuery, ITemplatesWorkflow, ITemplatesCollectionResponse, ITemplatesWorkflowResponse, IWorkflowTemplate } from '@/Interface';
+import { IDataObject } from 'n8n-workflow';
+import { get } from './helpers';
+
+function stringifyArray(arr: number[]) {
+	return arr.join(',');
+}
+
+export function testHealthEndpoint(apiEndpoint: string) {
+	return get(apiEndpoint, '/health');
+}
+
+export function getCategories(apiEndpoint: string, headers?: IDataObject): Promise<{categories: ITemplatesCategory[]}> {
+	return get(apiEndpoint, '/templates/categories', undefined, headers);
+}
+
+export async function getCollections(apiEndpoint: string, query: ITemplatesQuery, headers?: IDataObject): Promise<{collections: ITemplatesCollection[]}> {
+	return await get(apiEndpoint, '/templates/collections', {category: stringifyArray(query.categories || []), search: query.search}, headers);
+}
+
+export async function getWorkflows(
+	apiEndpoint: string,
+	query: {skip: number, limit: number, categories: number[], search: string},
+	headers?: IDataObject,
+): Promise<{totalWorkflows: number, workflows: ITemplatesWorkflow[]}> {
+	return get(apiEndpoint, '/templates/workflows', {skip: query.skip, rows: query.limit, category: stringifyArray(query.categories), search: query.search}, headers);
+}
+
+export async function getCollectionById(apiEndpoint: string, collectionId: string, headers?: IDataObject): Promise<{collection: ITemplatesCollectionResponse}> {
+	return await get(apiEndpoint, `/templates/collections/${collectionId}`, undefined, headers);
+}
+
+export async function getTemplateById(apiEndpoint: string, templateId: string, headers?: IDataObject): Promise<{workflow: ITemplatesWorkflowResponse}> {
+	return await get(apiEndpoint, `/templates/workflows/${templateId}`, undefined, headers);
+}
+
+export async function getWorkflowTemplate(apiEndpoint: string, templateId: string, headers?: IDataObject): Promise<IWorkflowTemplate> {
+	return await get(apiEndpoint, `/workflows/templates/${templateId}`, undefined, headers);
+}
diff --git a/packages/editor-ui/src/api/workflows.ts b/packages/editor-ui/src/api/workflows.ts
index 89c2569347..f8ee6ed79f 100644
--- a/packages/editor-ui/src/api/workflows.ts
+++ b/packages/editor-ui/src/api/workflows.ts
@@ -1,11 +1,7 @@
-import { IRestApiContext, IWorkflowTemplate } from '@/Interface';
-import { makeRestApiRequest, get } from './helpers';
-import { TEMPLATES_BASE_URL } from '@/constants';
+import { IRestApiContext } from '@/Interface';
+import { makeRestApiRequest } from './helpers';
 
 export async function getNewWorkflow(context: IRestApiContext, name?: string) {
 	return await makeRestApiRequest(context, 'GET', `/workflows/new`, name ? { name } : {});
 }
 
-export async function getWorkflowTemplate(templateId: string): Promise<IWorkflowTemplate> {
-	return await get(TEMPLATES_BASE_URL, `/workflows/templates/${templateId}`);
-}
diff --git a/packages/editor-ui/src/components/Card.vue b/packages/editor-ui/src/components/Card.vue
new file mode 100644
index 0000000000..670f92d2d9
--- /dev/null
+++ b/packages/editor-ui/src/components/Card.vue
@@ -0,0 +1,80 @@
+<template>
+	<div
+		:class="$style.card"
+		@click="(e) => $emit('click', e)"
+	>
+		<div :class="$style.container">
+			<span
+				v-if="!loading"
+				v-text="title"
+				:class="$style.title"
+			/>
+			<n8n-loading :loading="loading" :rows="3" variant="p" />
+			<div :class="$style.footer">
+				<slot name="footer"></slot>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import { genericHelpers } from '@/components/mixins/genericHelpers';
+import mixins from 'vue-typed-mixins';
+
+export default mixins(genericHelpers).extend({
+	name: 'Card',
+	props: {
+		loading: {
+			type: Boolean,
+		},
+		title: {
+			type: String,
+		},
+	},
+});
+</script>
+
+<style lang="scss" module>
+.card {
+	width: 240px !important;
+	height: 140px;
+	border-radius: var(--border-radius-large);
+	border: $--version-card-border;
+	margin-right: var(--spacing-2xs);
+	background-color: var(--color-background-xlight);
+	padding: var(--spacing-s);
+	cursor: pointer;
+
+	&:last-child {
+		margin-right: var(--spacing-5xs);
+	}
+
+	&:hover {
+		box-shadow: 0 2px 4px rgba(68,28,23,0.07);
+	}
+}
+
+.title {
+	display: -webkit-box;
+	-webkit-line-clamp: 4;
+	-webkit-box-orient: vertical;
+	font-size: var(--font-size-s);
+	line-height: var(--font-line-height-regular);
+	font-weight: var(--font-weight-bold);
+	overflow: hidden;
+	white-space: normal;
+}
+
+.container {
+	width: 100%;
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+	justify-content: space-between;
+}
+
+.footer {
+	display: flex;
+	justify-content: space-between;
+}
+</style>
diff --git a/packages/editor-ui/src/components/CollectionCard.vue b/packages/editor-ui/src/components/CollectionCard.vue
new file mode 100644
index 0000000000..8a74fc7ee9
--- /dev/null
+++ b/packages/editor-ui/src/components/CollectionCard.vue
@@ -0,0 +1,47 @@
+<template>
+	<Card
+		:loading="loading"
+		:title="collection.name"
+		@click="onClick"
+	>
+		<template v-slot:footer>
+			<n8n-text size="small" color="text-light">
+				{{ collection.workflows.length }}
+				{{ $locale.baseText('templates.workflows') }}
+			</n8n-text>
+			<NodeList :nodes="collection.nodes" :showMore="false" />
+		</template>
+	</Card>
+</template>
+
+<script lang="ts">
+import { genericHelpers } from '@/components/mixins/genericHelpers';
+import Card from '@/components/Card.vue';
+import mixins from 'vue-typed-mixins';
+import NodeList from '@/components/NodeList.vue';
+
+export default mixins(genericHelpers).extend({
+	name: 'CollectionCard',
+	props: {
+		loading: {
+			type: Boolean,
+		},
+		collection: {
+			type: Object,
+		},
+	},
+	components: {
+		Card,
+		NodeList,
+	},
+	methods: {
+		onClick(e: MouseEvent) {
+			this.$emit('click', e);
+		},
+	},
+});
+</script>
+
+<style lang="scss" module>
+
+</style>
diff --git a/packages/editor-ui/src/components/CollectionsCarousel.vue b/packages/editor-ui/src/components/CollectionsCarousel.vue
new file mode 100644
index 0000000000..6c536138a1
--- /dev/null
+++ b/packages/editor-ui/src/components/CollectionsCarousel.vue
@@ -0,0 +1,181 @@
+<template>
+	<div :class="$style.container" v-show="loading || collections.length">
+		<agile ref="slider" :dots="false" :navButtons="false" :infinite="false" :slides-to-show="4" @after-change="updateCarouselScroll">
+			<Card v-for="n in (loading ? 4: 0)" :key="`loading-${n}`" :loading="loading" />
+			<CollectionCard
+				v-for="collection in (loading? []: collections)"
+				:key="collection.id"
+				:collection="collection"
+				@click="(e) => onCardClick(e, collection.id)"
+			/>
+		</agile>
+		<button v-show="carouselScrollPosition > 0" :class="$style.leftButton" @click="scrollLeft">
+			<font-awesome-icon icon="chevron-left" />
+		</button>
+		<button v-show="!scrollEnd" :class="$style.rightButton" @click="scrollRight">
+			<font-awesome-icon icon="chevron-right" />
+		</button>
+	</div>
+</template>
+
+<script lang="ts">
+import Card from '@/components/Card.vue';
+import CollectionCard from '@/components/CollectionCard.vue';
+import VueAgile from 'vue-agile';
+
+import { genericHelpers } from '@/components/mixins/genericHelpers';
+import mixins from 'vue-typed-mixins';
+
+export default mixins(genericHelpers).extend({
+	name: 'CollectionsCarousel',
+	props: {
+		collections: {
+			type: Array,
+		},
+		loading: {
+			type: Boolean,
+		},
+	},
+	watch: {
+		collections() {
+			setTimeout(() => {
+				this.updateCarouselScroll();
+			}, 0);
+		},
+		loading() {
+			setTimeout(() => {
+				this.updateCarouselScroll();
+			}, 0);
+		},
+	},
+	components: {
+		Card,
+		CollectionCard,
+		VueAgile,
+	},
+	data() {
+		return {
+			carouselScrollPosition: 0,
+			cardWidth: 240,
+			scrollEnd: false,
+			listElement: null as null | Element,
+		};
+	},
+	methods: {
+		updateCarouselScroll() {
+			if (this.listElement) {
+				this.carouselScrollPosition = Number(this.listElement.scrollLeft.toFixed());
+
+				const width = this.listElement.clientWidth;
+				const scrollWidth = this.listElement.scrollWidth;
+				const scrollLeft = this.carouselScrollPosition;
+				this.scrollEnd = scrollWidth - width <= scrollLeft + 7;
+			}
+		},
+		onCardClick(event: MouseEvent, id: string) {
+			this.$emit('openCollection', {event, id});
+		},
+		scrollLeft() {
+			if (this.listElement) {
+				this.listElement.scrollBy({ left: -(this.cardWidth * 2), top: 0, behavior: 'smooth' });
+			}
+		},
+		scrollRight() {
+			if (this.listElement) {
+				this.listElement.scrollBy({ left: this.cardWidth * 2, top: 0, behavior: 'smooth' });
+			}
+		},
+	},
+	mounted() {
+		this.$nextTick(() => {
+			const slider = this.$refs.slider;
+			if (!slider) {
+				return;
+			}
+			// @ts-ignore
+			this.listElement = slider.$el.querySelector('.agile__list');
+			if (this.listElement) {
+				this.listElement.addEventListener('scroll', this.updateCarouselScroll);
+			}
+		});
+	},
+	beforeDestroy() {
+		if (this.$refs.slider) {
+			// @ts-ignore
+			this.$refs.slider.destroy();
+		}
+		window.removeEventListener('scroll', this.updateCarouselScroll);
+	},
+});
+</script>
+
+<style lang="scss" module>
+.container {
+	position: relative;
+}
+
+.button {
+	width: 28px;
+	height: 37px;
+	position: absolute;
+	top: 35%;
+	border-radius: var(--border-radius-large);
+	border: var(--border-base);
+	background-color: #fbfcfe;
+	cursor: pointer;
+
+	&:after {
+		content: '';
+		width: 40px;
+		height: 140px;
+		top: -55px;
+		position: absolute;
+	}
+	svg {
+		color: var(--color-foreground-xdark);
+	}
+}
+
+.leftButton {
+	composes: button;
+	left: -30px;
+
+	&:after {
+		left: 27px;
+		background: linear-gradient(270deg, rgba(255, 255, 255, 0.25) 0%, rgba(248, 249, 251, 1) 86%);
+	}
+}
+
+.rightButton {
+	composes: button;
+	right: -30px;
+	&:after {
+		right: 27px;
+		background: linear-gradient(270deg,rgba(248, 249, 251, 1) 25%, rgba(255, 255, 255, 0.25) 100%);
+	}
+}
+</style>
+
+<style lang="scss">
+.agile {
+	&__list {
+		width: 100%;
+		padding-bottom: var(--spacing-2xs);
+		overflow-x: scroll;
+		transition: all 1s ease-in-out;
+
+		&::-webkit-scrollbar {
+			height: 6px;
+		}
+
+		&::-webkit-scrollbar-thumb {
+			border-radius: 6px;
+			background-color: var(--color-foreground-dark);
+		}
+	}
+
+	&__track {
+		width: 50px;
+	}
+}
+</style>
diff --git a/packages/editor-ui/src/components/CredentialsList.vue b/packages/editor-ui/src/components/CredentialsList.vue
index d8154d5072..2fef894871 100644
--- a/packages/editor-ui/src/components/CredentialsList.vue
+++ b/packages/editor-ui/src/components/CredentialsList.vue
@@ -16,7 +16,7 @@
 				/>
 			</div>
 
-			<el-table :data="credentialsToDisplay" :default-sort = "{prop: 'name', order: 'ascending'}" stripe max-height="450" @row-click="editCredential">
+			<el-table :data="credentialsToDisplay" v-loading="loading" :default-sort = "{prop: 'name', order: 'ascending'}" stripe max-height="450" @row-click="editCredential">
 				<el-table-column property="name" :label="$locale.baseText('credentialsList.name')" class-name="clickable" sortable></el-table-column>
 				<el-table-column property="type" :label="$locale.baseText('credentialsList.type')" class-name="clickable" sortable></el-table-column>
 				<el-table-column property="createdAt" :label="$locale.baseText('credentialsList.created')" class-name="clickable" sortable></el-table-column>
@@ -64,11 +64,12 @@ export default mixins(
 	data() {
 		return {
 			CREDENTIAL_LIST_MODAL_KEY,
+			loading: true,
 		};
 	},
 	computed: {
 		...mapGetters('credentials', ['allCredentials']),
-		credentialsToDisplay() {
+		credentialsToDisplay(): ICredentialsResponse[] {
 			return this.allCredentials.reduce((accu: ICredentialsResponse[], cred: ICredentialsResponse) => {
 				const type = this.$store.getters['credentials/getCredentialTypeByName'](cred.type);
 
@@ -85,7 +86,17 @@ export default mixins(
 			}, []);
 		},
 	},
-	mounted() {
+	async mounted() {
+		try {
+			await Promise.all([
+				await this.$store.dispatch('credentials/fetchCredentialTypes'),
+				await this.$store.dispatch('credentials/fetchAllCredentials'),
+			]);
+		}	catch (e) {
+			this.$showError(e, this.$locale.baseText('credentialsList.errorLoadingCredentials'));
+		}
+		this.loading = false;
+
 		this.$externalHooks().run('credentialsList.mounted');
 		this.$telemetry.track('User opened Credentials panel', { workflow_id: this.$store.getters.workflowId });
 	},
diff --git a/packages/editor-ui/src/components/CredentialsSelectModal.vue b/packages/editor-ui/src/components/CredentialsSelectModal.vue
index 2f4af064af..3f019fbb73 100644
--- a/packages/editor-ui/src/components/CredentialsSelectModal.vue
+++ b/packages/editor-ui/src/components/CredentialsSelectModal.vue
@@ -4,7 +4,9 @@
 		:eventBus="modalBus"
 		width="50%"
 		:center="true"
+		:loading="loading"
 		maxWidth="460px"
+		minHeight="250px"
 	>
 		<template slot="header">
 			<h2 :class="$style.title">{{ $locale.baseText('credentialSelectModal.addNewCredential') }}</h2>
@@ -58,7 +60,13 @@ export default Vue.extend({
 	components: {
 		Modal,
 	},
-	mounted() {
+	async mounted() {
+		try {
+			await this.$store.dispatch('credentials/fetchCredentialTypes');
+		} catch (e) {
+		}
+		this.loading = false;
+
 		setTimeout(() => {
 			const element = this.$refs.select as HTMLSelectElement;
 			if (element) {
@@ -70,6 +78,7 @@ export default Vue.extend({
 		return {
 			modalBus: new Vue(),
 			selected: '',
+			loading: true,
 			CREDENTIAL_SELECT_MODAL_KEY,
 		};
 	},
diff --git a/packages/editor-ui/src/components/GoBackButton.vue b/packages/editor-ui/src/components/GoBackButton.vue
new file mode 100644
index 0000000000..082444dd4b
--- /dev/null
+++ b/packages/editor-ui/src/components/GoBackButton.vue
@@ -0,0 +1,55 @@
+<template>
+	<div :class="$style.wrapper" @click="navigateTo">
+		<font-awesome-icon :class="$style.icon" icon="arrow-left" />
+		<div :class="$style.text" v-text="$locale.baseText('template.buttons.goBackButton')" />
+	</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+
+export default Vue.extend({
+	name: 'TemplateList',
+	data() {
+		return {
+			routeHasHistory: false,
+		};
+	},
+	methods: {
+		navigateTo() {
+			if (this.routeHasHistory) this.$router.go(-1);
+			else this.$router.push({ name: 'TemplatesSearchView' });
+		},
+	},
+	mounted() {
+		window.history.state ? this.routeHasHistory = true : this.routeHasHistory = false;
+	},
+});
+</script>
+
+<style lang="scss" module>
+.wrapper {
+	display: flex;
+	align-items: center;
+	cursor: pointer;
+
+	&:hover {
+		.icon,
+		.text {
+			color: var(--color-primary);
+		}
+	}
+}
+
+.icon {
+	margin-right: var(--spacing-2xs);
+	color: var(--color-foreground-dark);
+	font-size: var(--font-size-m);
+}
+
+.text {
+	font-size: var(--font-size-s);
+	line-height: var(--font-line-height-loose);
+	color: var(--color-text-base);
+}
+</style>
diff --git a/packages/editor-ui/src/components/HoverableNodeIcon.vue b/packages/editor-ui/src/components/HoverableNodeIcon.vue
new file mode 100644
index 0000000000..582da3866f
--- /dev/null
+++ b/packages/editor-ui/src/components/HoverableNodeIcon.vue
@@ -0,0 +1,176 @@
+<template>
+	<div
+		:class="$style.wrapper"
+		:style="iconStyleData"
+		@click="(e) => $emit('click')"
+		@mouseover="showTooltip = true"
+		@mouseleave="showTooltip = false"
+	>
+		<div :class="$style.tooltip">
+			<n8n-tooltip placement="top" :manual="true" :value="showTooltip">
+				<div slot="content" v-text="nodeType.displayName"></div>
+				<span />
+			</n8n-tooltip>
+		</div>
+		<div v-if="nodeIconData !== null" :class="$style.icon" title="">
+			<div :class="$style.iconWrapper" :style="iconStyleData">
+				<div v-if="nodeIconData !== null" :class="$style.icon">
+					<img
+						v-if="nodeIconData.type === 'file'"
+						:src="nodeIconData.fileBuffer || nodeIconData.path"
+						:style="imageStyleData"
+					/>
+					<font-awesome-icon
+						v-else
+						:icon="nodeIconData.icon || nodeIconData.path"
+						:style="fontStyleData"
+					/>
+				</div>
+				<div v-else class="node-icon-placeholder">
+					{{ nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
+				</div>
+			</div>
+		</div>
+		<div v-else :class="$style.placeholder">
+			{{ nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+
+import { ITemplatesNode } from '@/Interface';
+import { INodeTypeDescription } from 'n8n-workflow';
+
+interface NodeIconData {
+	type: string;
+	path?: string;
+	fileExtension?: string;
+	fileBuffer?: string;
+}
+
+export default Vue.extend({
+	name: 'HoverableNodeIcon',
+	props: {
+		circle: {
+			type: Boolean,
+			default: false,
+		},
+		clickButton: {
+			type: Function,
+		},
+		disabled: {
+			type: Boolean,
+			default: false,
+		},
+		nodeType: {
+			type: Object,
+		},
+		size: {
+			type: Number,
+		},
+	},
+	computed: {
+		fontStyleData(): object {
+			return {
+				'max-width': this.size + 'px',
+			};
+		},
+		iconStyleData(): object {
+			const nodeType = this.nodeType as ITemplatesNode | null;
+			const color = nodeType ? nodeType.defaults && nodeType!.defaults.color : '';
+			if (!this.size) {
+				return { color };
+			}
+
+			return {
+				color,
+				width: this.size + 'px',
+				height: this.size + 'px',
+				'font-size': this.size + 'px',
+				'line-height': this.size + 'px',
+				'border-radius': this.circle ? '50%' : '2px',
+				...(this.disabled && {
+					color: '#ccc',
+					'-webkit-filter': 'contrast(40%) brightness(1.5) grayscale(100%)',
+					filter: 'contrast(40%) brightness(1.5) grayscale(100%)',
+				}),
+			};
+		},
+		imageStyleData(): object {
+			return {
+				width: '100%',
+				'max-width': '100%',
+				'max-height': '100%',
+			};
+		},
+		nodeIconData(): null | NodeIconData {
+			const nodeType = this.nodeType as INodeTypeDescription | ITemplatesNode | null;
+			if (nodeType === null) {
+				return null;
+			}
+
+			if ((nodeType as ITemplatesNode).iconData) {
+				return (nodeType as ITemplatesNode).iconData;
+			}
+
+			const restUrl = this.$store.getters.getRestUrl;
+
+			if (nodeType.icon) {
+				let type, path;
+				[type, path] = nodeType.icon.split(':');
+				const returnData: NodeIconData = {
+					type,
+					path,
+				};
+
+				if (type === 'file') {
+					returnData.path = restUrl + '/node-icon/' + nodeType.name;
+					returnData.fileExtension = path.split('.').slice(-1).join();
+				}
+
+				return returnData;
+			}
+			return null;
+		},
+	},
+	data() {
+		return {
+			showTooltip: false,
+		};
+	},
+});
+</script>
+
+<style lang="scss" module>
+.wrapper {
+	cursor: pointer;
+	z-index: 2000;
+}
+
+.icon {
+	height: 100%;
+	width: 100%;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+}
+
+.iconWrapper {
+	svg {
+		height: 100%;
+		width: 100%;
+	}
+}
+
+.placeholder {
+	text-align: center;
+}
+
+.tooltip {
+	left: 10px;
+	position: relative;
+	z-index: 9999;
+}
+</style>
diff --git a/packages/editor-ui/src/components/MainSidebar.vue b/packages/editor-ui/src/components/MainSidebar.vue
index a044b0b6e1..cdc2f6434b 100644
--- a/packages/editor-ui/src/components/MainSidebar.vue
+++ b/packages/editor-ui/src/components/MainSidebar.vue
@@ -30,49 +30,55 @@
 							<span slot="title" class="item-title">{{ $locale.baseText('mainSidebar.new') }}</span>
 						</template>
 					</n8n-menu-item>
+					<n8n-menu-item v-if="isTemplatesEnabled" index="template-new">
+						<template slot="title">
+							<font-awesome-icon icon="box-open"/>&nbsp;
+							<span slot="title" class="item-title">{{ $locale.baseText('mainSidebar.newTemplate') }}</span>
+						</template>
+					</n8n-menu-item>
 					<n8n-menu-item index="workflow-open">
 						<template slot="title">
 							<font-awesome-icon icon="folder-open"/>&nbsp;
 							<span slot="title" class="item-title">{{ $locale.baseText('mainSidebar.open') }}</span>
 						</template>
 					</n8n-menu-item>
-					<n8n-menu-item index="workflow-save">
+					<n8n-menu-item index="workflow-save" :disabled="!onWorkflowPage">
 						<template slot="title">
 							<font-awesome-icon icon="save"/>
 							<span slot="title" class="item-title">{{ $locale.baseText('mainSidebar.save') }}</span>
 						</template>
 					</n8n-menu-item>
-					<n8n-menu-item index="workflow-duplicate" :disabled="!currentWorkflow">
+					<n8n-menu-item index="workflow-duplicate" :disabled="!onWorkflowPage || !currentWorkflow">
 						<template slot="title">
 							<font-awesome-icon icon="copy"/>
 							<span slot="title" class="item-title">{{ $locale.baseText('mainSidebar.duplicate') }}</span>
 						</template>
 					</n8n-menu-item>
-					<n8n-menu-item index="workflow-delete" :disabled="!currentWorkflow">
+					<n8n-menu-item index="workflow-delete" :disabled="!onWorkflowPage || !currentWorkflow">
 						<template slot="title">
 							<font-awesome-icon icon="trash"/>
 							<span slot="title" class="item-title">{{ $locale.baseText('mainSidebar.delete') }}</span>
 						</template>
 					</n8n-menu-item>
-					<n8n-menu-item index="workflow-download">
+					<n8n-menu-item index="workflow-download" :disabled="!onWorkflowPage">
 						<template slot="title">
 							<font-awesome-icon icon="file-download"/>
 							<span slot="title" class="item-title">{{ $locale.baseText('mainSidebar.download') }}</span>
 						</template>
 					</n8n-menu-item>
-					<n8n-menu-item index="workflow-import-url">
+					<n8n-menu-item index="workflow-import-url" :disabled="!onWorkflowPage">
 						<template slot="title">
 							<font-awesome-icon icon="cloud"/>
 							<span slot="title" class="item-title">{{ $locale.baseText('mainSidebar.importFromUrl') }}</span>
 						</template>
 					</n8n-menu-item>
-					<n8n-menu-item index="workflow-import-file">
+					<n8n-menu-item index="workflow-import-file" :disabled="!onWorkflowPage">
 						<template slot="title">
 							<font-awesome-icon icon="hdd"/>
 							<span slot="title" class="item-title">{{ $locale.baseText('mainSidebar.importFromFile') }}</span>
 						</template>
 					</n8n-menu-item>
-					<n8n-menu-item index="workflow-settings" :disabled="!currentWorkflow">
+					<n8n-menu-item index="workflow-settings" :disabled="!onWorkflowPage || !currentWorkflow">
 						<template slot="title">
 							<font-awesome-icon icon="cog"/>
 							<span slot="title" class="item-title">{{ $locale.baseText('mainSidebar.settings') }}</span>
@@ -80,6 +86,11 @@
 					</n8n-menu-item>
 				</el-submenu>
 
+				<n8n-menu-item v-if="isTemplatesEnabled" index="templates">
+					<font-awesome-icon icon="box-open"/>&nbsp;
+					<span slot="title" class="item-title-root">{{ $locale.baseText('mainSidebar.templates') }}</span>
+				</n8n-menu-item>
+
 				<el-submenu index="credentials" :title="$locale.baseText('mainSidebar.credentials')" popperClass="sidebar-popper">
 					<template slot="title">
 						<font-awesome-icon icon="key"/>&nbsp;
@@ -165,7 +176,19 @@ import { saveAs } from 'file-saver';
 import mixins from 'vue-typed-mixins';
 import { mapGetters } from 'vuex';
 import MenuItemsIterator from './MainSidebarMenuItemsIterator.vue';
-import { CREDENTIAL_LIST_MODAL_KEY, CREDENTIAL_SELECT_MODAL_KEY, DUPLICATE_MODAL_KEY, TAGS_MANAGER_MODAL_KEY, VERSIONS_MODAL_KEY, WORKFLOW_SETTINGS_MODAL_KEY, WORKFLOW_OPEN_MODAL_KEY, EXECUTIONS_MODAL_KEY } from '@/constants';
+import {
+	CREDENTIAL_LIST_MODAL_KEY,
+	CREDENTIAL_SELECT_MODAL_KEY,
+	DUPLICATE_MODAL_KEY,
+	MODAL_CANCEL,
+	MODAL_CLOSE,
+	MODAL_CONFIRMED,
+	TAGS_MANAGER_MODAL_KEY,
+	VERSIONS_MODAL_KEY,
+	WORKFLOW_SETTINGS_MODAL_KEY,
+	WORKFLOW_OPEN_MODAL_KEY,
+	EXECUTIONS_MODAL_KEY,
+} from '@/constants';
 
 export default mixins(
 	genericHelpers,
@@ -200,6 +223,9 @@ export default mixins(
 				'hasVersionUpdates',
 				'nextVersions',
 			]),
+			...mapGetters('settings', [
+				'isTemplatesEnabled',
+			]),
 			helpMenuItems (): object[] {
 				return [
 					{
@@ -286,6 +312,9 @@ export default mixins(
 			sidebarMenuBottomItems(): IMenuItem[] {
 				return this.$store.getters.sidebarMenuItems.filter((item: IMenuItem) => item.position === 'bottom');
 			},
+			onWorkflowPage(): boolean {
+				return this.$route.meta && this.$route.meta.nodeView;
+			},
 		},
 		methods: {
 			trackHelpItemClick (itemType: string) {
@@ -449,14 +478,30 @@ export default mixins(
 				} else if (key === 'workflow-new') {
 					const result = this.$store.getters.getStateIsDirty;
 					if(result) {
-						const importConfirm = await this.confirmMessage(
+						const confirmModal = await this.confirmModal(
 							this.$locale.baseText('mainSidebar.confirmMessage.workflowNew.message'),
 							this.$locale.baseText('mainSidebar.confirmMessage.workflowNew.headline'),
 							'warning',
 							this.$locale.baseText('mainSidebar.confirmMessage.workflowNew.confirmButtonText'),
 							this.$locale.baseText('mainSidebar.confirmMessage.workflowNew.cancelButtonText'),
+							true,
 						);
-						if (importConfirm === true) {
+
+						if (confirmModal === MODAL_CONFIRMED) {
+							const saved = await this.saveCurrentWorkflow({}, false);
+							if (saved) this.$store.dispatch('settings/fetchPromptsData');
+
+							if (this.$router.currentRoute.name === 'NodeViewNew') {
+								this.$root.$emit('newWorkflow');
+							} else {
+								this.$router.push({ name: 'NodeViewNew' });
+							}
+
+							this.$showMessage({
+								title: this.$locale.baseText('mainSidebar.showMessage.handleSelect2.title'),
+								type: 'success',
+							});
+						} else if (confirmModal === MODAL_CANCEL) {
 							this.$store.commit('setStateDirty', false);
 							if (this.$router.currentRoute.name === 'NodeViewNew') {
 								this.$root.$emit('newWorkflow');
@@ -468,6 +513,8 @@ export default mixins(
 								title: this.$locale.baseText('mainSidebar.showMessage.handleSelect2.title'),
 								type: 'success',
 							});
+						} else if (confirmModal === MODAL_CLOSE) {
+							return;
 						}
 					} else {
 						if (this.$router.currentRoute.name !== 'NodeViewNew') {
@@ -480,6 +527,10 @@ export default mixins(
 						});
 					}
 					this.$titleReset();
+				} else if (key === 'templates' || key === 'template-new') {
+					if (this.$router.currentRoute.name !== 'TemplatesSearchView') {
+						this.$router.push({ name: 'TemplatesSearchView' });
+					}
 				} else if (key === 'credentials-open') {
 					this.$store.dispatch('ui/openModal', CREDENTIAL_LIST_MODAL_KEY);
 				} else if (key === 'credentials-new') {
@@ -554,7 +605,8 @@ export default mixins(
 			}
 			.item-title {
 				position: absolute;
-				left: 73px;
+				left: 56px;
+				font-size: var(--font-size-s);
 			}
 			.item-title-root {
 				position: absolute;
@@ -563,6 +615,12 @@ export default mixins(
 			}
 		}
 
+		.el-menu--inline {
+			.el-menu-item {
+				padding-left: 30px!important;
+			}
+		}
+
 	}
 
 	.el-menu-item {
diff --git a/packages/editor-ui/src/components/NodeCreator/NodeCreator.vue b/packages/editor-ui/src/components/NodeCreator/NodeCreator.vue
index 0fcc5eda09..44fa813cd8 100644
--- a/packages/editor-ui/src/components/NodeCreator/NodeCreator.vue
+++ b/packages/editor-ui/src/components/NodeCreator/NodeCreator.vue
@@ -96,8 +96,8 @@ export default Vue.extend({
 		},
 	},
 	watch: {
-		nodeTypes(newList, prevList) {
-			if (prevList.length === 0) {
+		nodeTypes(newList) {
+			if (newList.length !== this.allNodeTypes.length) {
 				this.allNodeTypes = newList;
 			}
 		},
diff --git a/packages/editor-ui/src/components/NodeList.vue b/packages/editor-ui/src/components/NodeList.vue
new file mode 100644
index 0000000000..bf9f04cea1
--- /dev/null
+++ b/packages/editor-ui/src/components/NodeList.vue
@@ -0,0 +1,111 @@
+<template>
+	<div :class="$style.list">
+		<div v-for="node in slicedNodes" :class="[$style.container, $style[size]]" :key="node.name">
+			<HoverableNodeIcon :nodeType="node" :size="size === 'md'? 24: 18" :title="node.name" />
+		</div>
+		<div :class="[$style.button, size === 'md' ? $style.buttonMd : $style.buttonSm]" v-if="filteredCoreNodes.length > limit + 1">
+			+{{ hiddenNodes }}
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import HoverableNodeIcon from '@/components/HoverableNodeIcon.vue';
+
+import { genericHelpers } from '@/components/mixins/genericHelpers';
+import { ITemplatesNode } from '@/Interface';
+
+import mixins from 'vue-typed-mixins';
+import { filterTemplateNodes } from './helpers';
+
+export default mixins(genericHelpers).extend({
+	name: 'NodeList',
+	props: {
+		nodes: {
+			type: Array,
+		},
+		limit: {
+			type: Number,
+			default: 4,
+		},
+		size: {
+			type: String,
+			default: 'sm',
+		},
+	},
+	components: {
+		HoverableNodeIcon,
+	},
+	computed: {
+		filteredCoreNodes() {
+			return filterTemplateNodes(this.nodes as ITemplatesNode[]);
+		},
+		hiddenNodes(): number {
+			return this.filteredCoreNodes.length - this.countNodesToBeSliced(this.filteredCoreNodes);
+		},
+		slicedNodes(): ITemplatesNode[] {
+			return this.filteredCoreNodes.slice(0, this.countNodesToBeSliced(this.filteredCoreNodes));
+		},
+	},
+	methods: {
+		countNodesToBeSliced(nodes: ITemplatesNode[]): number {
+			if (nodes.length > this.limit) {
+				return this.limit - 1;
+			} else {
+				return this.limit;
+			}
+		},
+	},
+});
+</script>
+
+<style lang="scss" module>
+.list {
+	max-width: 100px;
+	display: flex;
+	flex-direction: row;
+	justify-content: flex-end;
+	align-items: center;
+}
+
+.container {
+	position: relative;
+	display: block;
+}
+
+.sm {
+	margin-left: var(--spacing-2xs);
+}
+
+.md {
+	margin-left: var(--spacing-xs);
+}
+
+.button {
+	top: 0px;
+	position: relative;
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	background: var(--color-background-light);
+	border: 1px var(--color-foreground-base) solid;
+	border-radius: var(--border-radius-base);
+	font-size: 10px;
+	font-weight: var(--font-weight-bold);
+	color: var(--color-text-base);
+}
+
+.buttonSm {
+	margin-left: var(--spacing-2xs);
+	width: 20px;
+	min-width: 20px;
+	height: 20px;
+}
+
+.buttonMd {
+	margin-left: var(--spacing-xs);
+	width: 24px;
+	min-width: 24px;
+	height: 24px;
+}
+</style>
diff --git a/packages/editor-ui/src/components/ParameterInput.vue b/packages/editor-ui/src/components/ParameterInput.vue
index f3579e7cb4..6ebe6fc591 100644
--- a/packages/editor-ui/src/components/ParameterInput.vue
+++ b/packages/editor-ui/src/components/ParameterInput.vue
@@ -143,11 +143,12 @@
 			:value="displayValue"
 			:loading="remoteParameterOptionsLoading"
 			:disabled="isReadOnly || remoteParameterOptionsLoading"
+			:title="displayTitle"
+			:placeholder="$locale.baseText('parameterInput.select')"
 			@change="valueChanged"
 			@keydown.stop
 			@focus="setFocus"
 			@blur="onBlur"
-			:title="displayTitle"
 		>
 			<n8n-option v-for="option in parameterOptions" :value="option.value" :key="option.value" :label="getOptionsOptionDisplayName(option)">
 				<div class="list-option">
@@ -302,6 +303,9 @@ export default mixins(
 			},
 		},
 		computed: {
+			areExpressionsDisabled(): boolean {
+				return this.$store.getters['ui/areExpressionsDisabled'];
+			},
 			codeAutocomplete (): string | undefined {
 				return this.getArgument('codeAutocomplete') as string | undefined;
 			},
@@ -419,6 +423,10 @@ export default mixins(
 				return false;
 			},
 			expressionValueComputed (): NodeParameterValue | null {
+				if (this.areExpressionsDisabled) {
+					return this.value;
+				}
+
 				if (this.node === null) {
 					return null;
 				}
@@ -661,6 +669,10 @@ export default mixins(
 				this.valueChanged(value);
 			},
 			openExpressionEdit() {
+				if (this.areExpressionsDisabled) {
+					return;
+				}
+
 				if (this.isValueExpression) {
 					this.expressionEditDialogVisible = true;
 					this.trackExpressionEditOpen();
diff --git a/packages/editor-ui/src/components/Telemetry.vue b/packages/editor-ui/src/components/Telemetry.vue
index ac7978b513..159d5944d0 100644
--- a/packages/editor-ui/src/components/Telemetry.vue
+++ b/packages/editor-ui/src/components/Telemetry.vue
@@ -9,15 +9,43 @@ import { mapGetters } from 'vuex';
 
 export default Vue.extend({
 	name: 'Telemetry',
+	data() {
+		return {
+			initialised: false,
+		};
+	},
 	computed: {
 		...mapGetters('settings', ['telemetry']),
+		isTelemeteryEnabledOnRoute(): boolean {
+			return this.$route.meta && this.$route.meta.telemetry ? !this.$route.meta.telemetry.disabled: true;
+		},
+	},
+	mounted() {
+		this.init();
+	},
+	methods: {
+		init() {
+			if (this.initialised || !this.isTelemeteryEnabledOnRoute) {
+				return;
+			}
+			const opts = this.telemetry;
+			if (opts && opts.enabled) {
+				this.initialised = true;
+				const instanceId = this.$store.getters.instanceId;
+				const logLevel = this.$store.getters['settings/logLevel'];
+				this.$telemetry.init(opts, {instanceId, logLevel, store: this.$store});
+			}
+		},
 	},
 	watch: {
-		telemetry(opts) {
-			if (opts && opts.enabled) {
-				this.$telemetry.init(opts, this.$store.getters.instanceId, this.$store.getters['settings/logLevel']);
+		isTelemeteryEnabledOnRoute(enabled) {
+			if (enabled) {
+				this.init();
 			}
 		},
+		telemetry() {
+			this.init();
+		},
 	},
 });
 </script>
diff --git a/packages/editor-ui/src/components/TemplateCard.vue b/packages/editor-ui/src/components/TemplateCard.vue
new file mode 100644
index 0000000000..e4dcbf8f05
--- /dev/null
+++ b/packages/editor-ui/src/components/TemplateCard.vue
@@ -0,0 +1,176 @@
+<template>
+	<div
+		:class="[$style.card, lastItem && $style.last, firstItem && $style.first, !loading && $style.loaded]"
+		@click="onCardClick"
+	>
+		<div :class="$style.loading" v-if="loading">
+			<n8n-loading :rows="2" :shrinkLast="false" :loading="loading" />
+		</div>
+		<div v-else>
+			<n8n-heading :bold="true" size="small">{{ workflow.name }}</n8n-heading>
+			<div :class="$style.content">
+				<span v-if="workflow.totalViews">
+					<n8n-text size="small" color="text-light">
+						<font-awesome-icon icon="eye" />
+						{{ abbreviateNumber(workflow.totalViews) }}
+					</n8n-text>
+				</span>
+				<div v-if="workflow.totalViews" :class="$style.line" v-text="'|'" />
+				<n8n-text size="small" color="text-light">
+					<TimeAgo :date="workflow.createdAt" />
+				</n8n-text>
+				<div v-if="workflow.user" :class="$style.line" v-text="'|'" />
+				<n8n-text v-if="workflow.user" size="small" color="text-light">By {{ workflow.user.username }}</n8n-text>
+			</div>
+		</div>
+		<div :class="[$style.nodesContainer, useWorkflowButton && $style.hideOnHover]" v-if="!loading">
+			<NodeList v-if="workflow.nodes" :nodes="workflow.nodes" :limit="nodesToBeShown" size="md" />
+		</div>
+		<div :class="$style.buttonContainer" v-if="useWorkflowButton">
+			<n8n-button
+				v-if="useWorkflowButton"
+				type="outline"
+				label="Use workflow"
+				@click.stop="onUseWorkflowClick"
+			/>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import { genericHelpers } from '@/components/mixins/genericHelpers';
+import mixins from 'vue-typed-mixins';
+import { filterTemplateNodes, abbreviateNumber } from './helpers';
+import NodeList from './NodeList.vue';
+
+export default mixins(genericHelpers).extend({
+	name: 'TemplateCard',
+	props: {
+		lastItem: {
+			type: Boolean,
+			default: false,
+		},
+		firstItem: {
+			type: Boolean,
+			default: false,
+		},
+		workflow: {
+			type: Object,
+		},
+		useWorkflowButton: {
+			type: Boolean,
+		},
+		loading: {
+			type: Boolean,
+		},
+	},
+	components: {
+		NodeList,
+	},
+	data() {
+		return {
+			nodesToBeShown: 5,
+		};
+	},
+	methods: {
+		filterTemplateNodes,
+		abbreviateNumber,
+		countNodesToBeSliced(nodes: []): number {
+			if (nodes.length > this.nodesToBeShown) {
+				return this.nodesToBeShown - 1;
+			} else {
+				return this.nodesToBeShown;
+			}
+		},
+		onUseWorkflowClick(e: MouseEvent) {
+			this.$emit('useWorkflow', e);
+		},
+		onCardClick(e: MouseEvent) {
+			this.$emit('click', e);
+		},
+	},
+});
+</script>
+
+<style lang="scss" module>
+.nodes {
+	display: flex;
+	justify-content: center;
+	align-content: center;
+	flex-direction: row;
+}
+
+.icon {
+	margin-left: var(--spacing-xs);
+}
+
+.card {
+	position: relative;
+	border-left: var(--border-base);
+	border-right: var(--border-base);
+	border-bottom: var(--border-base);
+	background-color: var(--color-background-xlight);
+
+	display: flex;
+	padding: 0 var(--spacing-s) var(--spacing-s) var(--spacing-s);
+	background-color: var(--color-background-xlight);
+	cursor: pointer;
+
+	&:hover {
+		.hideOnHover {
+			visibility: hidden;
+		}
+
+		.buttonContainer {
+			display: block;
+		}
+	}
+}
+
+.buttonContainer {
+	display: none;
+	position: absolute;
+	right: 10px;
+	top: 30%;
+}
+
+.loaded {
+	padding-top: var(--spacing-s);
+}
+
+.first {
+	border-top: var(--border-base);
+	border-top-right-radius: var(--border-radius-large);
+	border-top-left-radius: var(--border-radius-large);
+}
+
+.last {
+	border-bottom-right-radius: var(--border-radius-large);
+	border-bottom-left-radius: var(--border-radius-large);
+}
+
+.content {
+	display: flex;
+	align-items: center;
+}
+
+.line {
+	padding: 0 6px;
+	color: var(--color-foreground-base);
+	font-size: var(--font-size-2xs);
+}
+
+.loading {
+	width: 100%;
+	background-color: var(--color-background-xlight);
+}
+
+.nodesContainer {
+	min-width: 175px;
+	display: flex;
+	justify-content: flex-end;
+	align-items: center;
+	flex-grow: 1;
+}
+
+</style>
diff --git a/packages/editor-ui/src/components/TemplateDetails.vue b/packages/editor-ui/src/components/TemplateDetails.vue
new file mode 100644
index 0000000000..7ba252f857
--- /dev/null
+++ b/packages/editor-ui/src/components/TemplateDetails.vue
@@ -0,0 +1,103 @@
+<template>
+	<div>
+		<n8n-loading :loading="loading" :rows="5" variant="p" />
+
+		<template-details-block v-if="!loading && template.nodes.length > 0" :title="blockTitle">
+			<div :class="$style.icons">
+				<div
+					v-for="node in filterTemplateNodes(template.nodes)"
+					:key="node.name"
+					:class="$style.icon"
+				>
+					<HoverableNodeIcon
+						:nodeType="node"
+						:title="node.name"
+						:size="24"
+						@click="redirectToSearchPage(node)"
+					/>
+				</div>
+			</div>
+		</template-details-block>
+
+		<template-details-block
+			v-if="!loading && template.categories.length > 0"
+			:title="$locale.baseText('template.details.categories')"
+		>
+			<n8n-tags :tags="template.categories" @click="redirectToCategory" />
+		</template-details-block>
+
+		<template-details-block v-if="!loading" :title="$locale.baseText('template.details.details')">
+			<div :class="$style.text">
+				<n8n-text size="small" color="text-base">
+					{{ $locale.baseText('template.details.created') }}
+					<TimeAgo :date="template.createdAt" />
+					<span>{{ $locale.baseText('template.details.by') }}</span>
+					<span v-if="template.user"> {{ template.user.username }}</span>
+					<span v-else> n8n team</span>
+				</n8n-text>
+			</div>
+			<div :class="$style.text">
+				<n8n-text v-if="template.totalViews !== 0" size="small" color="text-base">
+					{{ $locale.baseText('template.details.viewed') }}
+					{{ abbreviateNumber(template.totalViews) }}
+					{{ $locale.baseText('template.details.times') }}
+				</n8n-text>
+			</div>
+		</template-details-block>
+	</div>
+</template>
+<script lang="ts">
+import Vue from 'vue';
+
+import TemplateDetailsBlock from '@/components/TemplateDetailsBlock.vue';
+import HoverableNodeIcon from '@/components/HoverableNodeIcon.vue';
+
+import { abbreviateNumber, filterTemplateNodes } from '@/components/helpers';
+import { ITemplatesNode } from '@/Interface';
+
+export default Vue.extend({
+	name: 'TemplateDetails',
+	props: {
+		blockTitle: {
+			type: String,
+		},
+		loading: {
+			type: Boolean,
+		},
+		template: {
+			type: Object,
+		},
+	},
+	components: {
+		HoverableNodeIcon,
+		TemplateDetailsBlock,
+	},
+	methods: {
+		abbreviateNumber,
+		filterTemplateNodes,
+		redirectToCategory(id: string) {
+			this.$store.commit('templates/resetSessionId');
+			this.$router.push(`/templates?categories=${id}`);
+		},
+		redirectToSearchPage(node: ITemplatesNode) {
+			this.$store.commit('templates/resetSessionId');
+			this.$router.push(`/templates?search=${node.displayName}`);
+		},
+	},
+});
+</script>
+<style lang="scss" module>
+.icons {
+	display: flex;
+	flex-wrap: wrap;
+}
+
+.icon {
+	margin-right: var(--spacing-xs);
+	margin-bottom: var(--spacing-xs);
+}
+
+.text {
+	padding-bottom: var(--spacing-xs);
+}
+</style>
diff --git a/packages/editor-ui/src/components/TemplateDetailsBlock.vue b/packages/editor-ui/src/components/TemplateDetailsBlock.vue
new file mode 100644
index 0000000000..aa6bf787e2
--- /dev/null
+++ b/packages/editor-ui/src/components/TemplateDetailsBlock.vue
@@ -0,0 +1,38 @@
+<template>
+	<div :class="$style.block">
+		<div :class="$style.header">
+			<n8n-heading tag="h3" size="small" color="text-base">{{ title }}</n8n-heading>
+		</div>
+		<div :class="$style.content">
+			<slot></slot>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+
+export default Vue.extend({
+	name: 'TemplateDetailsBlock',
+	props: {
+		title: {
+			type: String,
+		},
+	},
+});
+</script>
+
+<style lang="scss" module>
+.block {
+	padding-bottom: var(--spacing-xl);
+}
+
+.header {
+	padding: 0 0 var(--spacing-4xs);
+	border-bottom: var(--border-base);
+}
+
+.content {
+	padding: var(--spacing-xs) 0 0;
+}
+</style>
diff --git a/packages/editor-ui/src/components/TemplateFilters.vue b/packages/editor-ui/src/components/TemplateFilters.vue
new file mode 100644
index 0000000000..41103bfde8
--- /dev/null
+++ b/packages/editor-ui/src/components/TemplateFilters.vue
@@ -0,0 +1,151 @@
+<template>
+	<div :class="$style.filters" class="template-filters">
+		<div :class="$style.title" v-text="$locale.baseText('templates.categoriesHeading')" />
+		<div v-if="loading" :class="$style.list">
+			<n8n-loading :loading="loading" :rows="expandLimit" />
+		</div>
+		<ul v-if="!loading" :class="$style.categories">
+			<li :class="$style.item">
+				<el-checkbox
+					:label="$locale.baseText('templates.allCategories')"
+					:value="allSelected"
+					@change="(value) => resetCategories(value)"
+				/>
+			</li>
+			<li
+				v-for="category in collapsed
+					? sortedCategories.slice(0, expandLimit)
+					: sortedCategories"
+				:key="category.id"
+				:class="$style.item"
+			>
+				<el-checkbox
+					:label="category.name"
+					:value="isSelected(category.id)"
+					@change="(value) => handleCheckboxChanged(value, category)"
+				/>
+			</li>
+		</ul>
+		<div
+			:class="$style.button"
+			v-if="sortedCategories.length > expandLimit && collapsed && !loading"
+			@click="collapseAction"
+		>
+			<n8n-text size="small" color="primary">
+				+ {{ `${sortedCategories.length - expandLimit} more` }}
+			</n8n-text>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import { genericHelpers } from '@/components/mixins/genericHelpers';
+import { ITemplatesCategory } from '@/Interface';
+import mixins from 'vue-typed-mixins';
+
+export default mixins(genericHelpers).extend({
+	name: 'TemplateFilters',
+	props: {
+		sortOnPopulate: {
+			type: Boolean,
+			default: false,
+		},
+		categories: {
+			type: Array,
+		},
+		expandLimit: {
+			type: Number,
+			default: 12,
+		},
+		loading: {
+			type: Boolean,
+		},
+		selected: {
+			type: Array,
+		},
+	},
+	watch: {
+		categories: {
+			handler(categories: ITemplatesCategory[]) {
+				if (!this.sortOnPopulate) {
+					this.sortedCategories = categories;
+				} else {
+					const selected = this.selected || [];
+					const selectedCategories = categories.filter(({ id }) => selected.includes(id));
+					const notSelectedCategories = categories.filter(({ id }) => !selected.includes(id));
+					this.sortedCategories = selectedCategories.concat(notSelectedCategories);
+				}
+			},
+			immediate: true,
+		},
+	},
+	data() {
+		return {
+			collapsed: true,
+			sortedCategories: [] as ITemplatesCategory[],
+		};
+	},
+	computed: {
+		allSelected(): boolean {
+			return this.selected.length === 0;
+		},
+	},
+	methods: {
+		collapseAction() {
+			this.collapsed = false;
+		},
+		handleCheckboxChanged(value: boolean, selectedCategory: ITemplatesCategory) {
+			this.$emit(value ? 'select' : 'clear', selectedCategory.id);
+		},
+		isSelected(categoryId: string) {
+			return this.selected.includes(categoryId);
+		},
+		resetCategories() {
+			this.$emit('clearAll');
+		},
+	},
+});
+</script>
+
+<style lang="scss" module>
+.title {
+	font-size: var(--font-size-2xs);
+	color: var(--color-text-base);
+}
+
+.categories {
+	padding-top: var(--spacing-xs);
+	list-style-type: none;
+}
+
+.item {
+	margin-top: var(--spacing-xs);
+
+	&:nth-child(1) {
+		margin-top: 0;
+	}
+}
+
+.button {
+	padding-top: var(--spacing-2xs);
+	cursor: pointer;
+}
+</style>
+
+<style lang="scss">
+.template-filters {
+	.el-checkbox {
+		display: flex;
+		white-space: unset;
+	}
+
+	.el-checkbox__label {
+		top: -2px;
+		position: relative;
+		font-size: var(--font-size-xs);
+		line-height: var(--font-line-height-loose);
+		color: var(--color-text-dark);
+		padding-left: var(--spacing-2xs);
+	}
+}
+</style>
diff --git a/packages/editor-ui/src/components/TemplateList.vue b/packages/editor-ui/src/components/TemplateList.vue
new file mode 100644
index 0000000000..7f9fcc685b
--- /dev/null
+++ b/packages/editor-ui/src/components/TemplateList.vue
@@ -0,0 +1,116 @@
+<template>
+	<div :class="$style.list" v-if="loading || workflows.length">
+		<div :class="$style.header">
+			<n8n-heading :bold="true" size="medium" color="text-light">
+				{{ $locale.baseText('templates.workflows') }}
+				<span v-if="!loading && totalWorkflows" v-text="`(${totalWorkflows})`" />
+			</n8n-heading>
+		</div>
+		<div :class="$style.container">
+			<TemplateCard
+				v-for="(workflow, index) in workflows"
+				:key="workflow.id"
+				:workflow="workflow"
+				:firstItem="index === 0"
+				:lastItem="index === workflows.length - 1 && !loading"
+				:useWorkflowButton="useWorkflowButton"
+				@click="(e) => onCardClick(e, workflow.id)"
+				@useWorkflow="(e) => onUseWorkflow(e, workflow.id)"
+			/>
+			<div v-if="infiniteScrollEnabled" ref="loader" />
+			<div v-if="loading">
+				<TemplateCard
+					v-for="n in 4"
+					:key="'index-' + n"
+					:loading="true"
+					:firstItem="workflows.length === 0 && n === 1"
+					:lastItem="n === 4"
+				/>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import { genericHelpers } from '@/components/mixins/genericHelpers';
+import mixins from 'vue-typed-mixins';
+import TemplateCard from './TemplateCard.vue';
+
+export default mixins(genericHelpers).extend({
+	name: 'TemplateList',
+	props: {
+		infiniteScrollEnabled: {
+			type: Boolean,
+			default: false,
+		},
+		loading: {
+			type: Boolean,
+		},
+		useWorkflowButton: {
+			type: Boolean,
+			default: false,
+		},
+		workflows: {
+			type: Array,
+		},
+		totalWorkflows: {
+			type: Number,
+		},
+	},
+	mounted() {
+		if (this.infiniteScrollEnabled) {
+			window.addEventListener('scroll', this.onScroll);
+		}
+	},
+	destroyed() {
+		window.removeEventListener('scroll', this.onScroll);
+	},
+	components: {
+		TemplateCard,
+	},
+	methods: {
+		onScroll() {
+			const el = this.$refs.loader;
+			if (!el || this.loading) {
+				return;
+			}
+
+			const rect = (el as Element).getBoundingClientRect();
+			const inView =
+				rect.top >= 0 &&
+				rect.left >= 0 &&
+				rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
+				rect.right <= (window.innerWidth || document.documentElement.clientWidth);
+
+			if (inView) {
+				this.$emit('loadMore');
+			}
+		},
+		onCardClick(event: MouseEvent, id: string) {
+			this.$emit('openTemplate', {event, id});
+		},
+		onUseWorkflow(event: MouseEvent, id: string) {
+			this.$emit('useWorkflow', {event, id});
+		},
+	},
+});
+</script>
+
+<style lang="scss" module>
+.header {
+	padding-bottom: var(--spacing-2xs);
+}
+
+.workflowButton {
+	&:hover {
+		.button {
+			display: block;
+		}
+
+		.nodes {
+			display: none;
+		}
+	}
+}
+
+</style>
diff --git a/packages/editor-ui/src/components/WorkflowOpen.vue b/packages/editor-ui/src/components/WorkflowOpen.vue
index 4a927519ec..84edc06973 100644
--- a/packages/editor-ui/src/components/WorkflowOpen.vue
+++ b/packages/editor-ui/src/components/WorkflowOpen.vue
@@ -66,7 +66,7 @@ import TagsContainer from '@/components/TagsContainer.vue';
 import TagsDropdown from '@/components/TagsDropdown.vue';
 import WorkflowActivator from '@/components/WorkflowActivator.vue';
 import { convertToDisplayDate } from './helpers';
-import { WORKFLOW_OPEN_MODAL_KEY } from '../constants';
+import { MODAL_CANCEL, MODAL_CLOSE, MODAL_CONFIRMED, WORKFLOW_OPEN_MODAL_KEY } from '../constants';
 
 export default mixins(
 	genericHelpers,
@@ -112,10 +112,14 @@ export default mixins(
 				});
 		},
 	},
-	mounted() {
+	async mounted() {
 		this.filterText = '';
 		this.filterTagIds = [];
-		this.openDialog();
+
+		this.isDataLoading = true;
+		await this.loadActiveWorkflows();
+		await this.loadWorkflows();
+		this.isDataLoading = false;
 
 		Vue.nextTick(() => {
 			// Make sure that users can directly type in the filter
@@ -159,23 +163,32 @@ export default mixins(
 
 				const result = this.$store.getters.getStateIsDirty;
 				if(result) {
-					const importConfirm = await this.confirmMessage(
+					const confirmModal = await this.confirmModal(
 						this.$locale.baseText('workflowOpen.confirmMessage.message'),
 						this.$locale.baseText('workflowOpen.confirmMessage.headline'),
 						'warning',
 						this.$locale.baseText('workflowOpen.confirmMessage.confirmButtonText'),
 						this.$locale.baseText('workflowOpen.confirmMessage.cancelButtonText'),
+						true,
 					);
-					if (importConfirm === false) {
-						return;
-					} else {
-						// This is used to avoid duplicating the message
+
+					if (confirmModal === MODAL_CONFIRMED) {
+						const saved = await this.saveCurrentWorkflow({}, false);
+						if (saved) this.$store.dispatch('settings/fetchPromptsData');
+
+						this.$router.push({
+							name: 'NodeViewExisting',
+							params: { name: data.id },
+						});
+					} else if (confirmModal === MODAL_CANCEL) {
 						this.$store.commit('setStateDirty', false);
 
 						this.$router.push({
 							name: 'NodeViewExisting',
 							params: { name: data.id },
 						});
+					} else if (confirmModal === MODAL_CLOSE) {
+						return;
 					}
 				} else {
 					this.$router.push({
@@ -186,29 +199,30 @@ export default mixins(
 				this.$store.commit('ui/closeAllModals');
 			}
 		},
-		openDialog () {
-			this.isDataLoading = true;
-			this.restApi().getWorkflows()
-				.then(
-					(data) => {
-						this.workflows = data;
-
-						this.workflows.forEach((workflowData: IWorkflowShortResponse) => {
-							workflowData.createdAt = convertToDisplayDate(workflowData.createdAt as number);
-							workflowData.updatedAt = convertToDisplayDate(workflowData.updatedAt as number);
-						});
-						this.isDataLoading = false;
-					},
-				)
-				.catch(
-					(error: Error) => {
-						this.$showError(
-							error,
-							this.$locale.baseText('workflowOpen.showError.title'),
-						);
-						this.isDataLoading = false;
-					},
+		async loadWorkflows () {
+			try {
+				this.workflows = await this.restApi().getWorkflows();
+				this.workflows.forEach((workflowData: IWorkflowShortResponse) => {
+					workflowData.createdAt = convertToDisplayDate(workflowData.createdAt as number);
+					workflowData.updatedAt = convertToDisplayDate(workflowData.updatedAt as number);
+				});
+			} catch (error) {
+				this.$showError(
+					error,
+					this.$locale.baseText('workflowOpen.showError.title'),
 				);
+			}
+		},
+		async loadActiveWorkflows () {
+			try {
+				const activeWorkflows = await this.restApi().getActiveWorkflows();
+				this.$store.commit('setActiveWorkflows', activeWorkflows);
+			} catch (error) {
+				this.$showError(
+					error,
+					this.$locale.baseText('workflowOpen.couldNotLoadActiveWorkflows'),
+				);
+			}
 		},
 		workflowActiveChanged (data: { id: string, active: boolean }) {
 			for (const workflow of this.workflows) {
diff --git a/packages/editor-ui/src/components/WorkflowPreview.vue b/packages/editor-ui/src/components/WorkflowPreview.vue
new file mode 100644
index 0000000000..c05ba94688
--- /dev/null
+++ b/packages/editor-ui/src/components/WorkflowPreview.vue
@@ -0,0 +1,144 @@
+<template>
+	<div :class="$style.container">
+		<n8n-loading :loading="!showPreview" :rows="1" variant="image" />
+		<iframe
+			:class="{
+				[$style.workflow]: !this.nodeViewDetailsOpened,
+				[$style.openNDV]: this.nodeViewDetailsOpened,
+				[$style.show]: this.showPreview,
+			}"
+			ref="preview_iframe"
+			src="/workflows/demo"
+			@mouseenter="onMouseEnter"
+			@mouseleave="onMouseLeave"
+		></iframe>
+	</div>
+</template>
+
+<script lang="ts">
+import mixins from 'vue-typed-mixins';
+import { showMessage } from '@/components/mixins/showMessage';
+
+export default mixins(showMessage).extend({
+	name: 'WorkflowPreview',
+	props: ['loading', 'workflow'],
+	data() {
+		return {
+			nodeViewDetailsOpened: false,
+			ready: false,
+			insideIframe: false,
+			scrollX: 0,
+			scrollY: 0,
+		};
+	},
+	computed: {
+		showPreview(): boolean {
+			return !this.loading && !!this.workflow && this.ready;
+		},
+	},
+	methods: {
+		onMouseEnter() {
+			this.insideIframe = true;
+			this.scrollX = window.scrollX;
+			this.scrollY = window.scrollY;
+		},
+		onMouseLeave() {
+			this.insideIframe = false;
+		},
+		loadWorkflow() {
+			try {
+				if (!this.workflow) {
+					throw new Error(this.$locale.baseText('workflowPreview.showError.missingWorkflow'));
+				}
+				if (!this.workflow.nodes || !Array.isArray(this.workflow.nodes)) {
+					throw new Error(this.$locale.baseText('workflowPreview.showError.arrayEmpty'));
+				}
+
+				const iframe = this.$refs.preview_iframe as HTMLIFrameElement;
+				if (iframe.contentWindow) {
+					iframe.contentWindow.postMessage(
+						JSON.stringify({
+							command: 'openWorkflow',
+							workflow: this.workflow,
+						}),
+						'*',
+					);
+				}
+			} catch (error) {
+				this.$showError(
+					error,
+					this.$locale.baseText('workflowPreview.showError.previewError.title'),
+					this.$locale.baseText('workflowPreview.showError.previewError.message'),
+				);
+			}
+		},
+		receiveMessage({ data }: MessageEvent) {
+			try {
+				const json = JSON.parse(data);
+				if (json.command === 'n8nReady') {
+					this.ready = true;
+				} else if (json.command === 'openNDV') {
+					this.nodeViewDetailsOpened = true;
+				} else if (json.command === 'closeNDV') {
+					this.nodeViewDetailsOpened = false;
+				} else if (json.command === 'error') {
+					this.$emit('close');
+				}
+			} catch (e) {
+			}
+		},
+		onDocumentScroll() {
+			if (this.insideIframe) {
+				window.scrollTo(this.scrollX, this.scrollY);
+			}
+		},
+	},
+	watch: {
+		showPreview(show) {
+			if (show) {
+				this.loadWorkflow();
+			}
+		},
+	},
+	mounted() {
+		window.addEventListener('message', this.receiveMessage);
+		document.addEventListener('scroll', this.onDocumentScroll);
+	},
+	beforeDestroy() {
+		window.removeEventListener('message', this.receiveMessage);
+		document.removeEventListener('scroll', this.onDocumentScroll);
+	},
+});
+</script>
+
+<style lang="scss" module>
+.container {
+	width: 100%;
+	height: 500px;
+}
+
+.workflow {
+	border: var(--border-base);
+	border-radius: var(--border-radius-large);
+
+	// firefox bug requires loading iframe as such
+	visibility: hidden;
+	height: 0;
+	width: 0;
+}
+
+.show {
+	visibility: visible;
+	height: 100%;
+	width: 100%;
+}
+
+.openNDV {
+	position: fixed;
+	top: 0;
+	left: 0;
+	height: 100%;
+	width: 100%;
+	z-index: 9999999;
+}
+</style>
diff --git a/packages/editor-ui/src/components/helpers.ts b/packages/editor-ui/src/components/helpers.ts
index a15a7396ae..d2c4b2d3fa 100644
--- a/packages/editor-ui/src/components/helpers.ts
+++ b/packages/editor-ui/src/components/helpers.ts
@@ -1,8 +1,21 @@
-import { ERROR_TRIGGER_NODE_TYPE } from '@/constants';
-import { INodeUi } from '@/Interface';
+import { CORE_NODES_CATEGORY, ERROR_TRIGGER_NODE_TYPE, TEMPLATES_NODES_FILTER } from '@/constants';
+import { INodeUi, ITemplatesNode } from '@/Interface';
 import dateformat from 'dateformat';
 
 const KEYWORDS_TO_FILTER = ['API', 'OAuth1', 'OAuth2'];
+const SI_SYMBOL = ['', 'k', 'M', 'G', 'T', 'P', 'E'];
+
+export function abbreviateNumber(num: number) {
+	const tier = (Math.log10(Math.abs(num)) / 3) | 0;
+
+	if (tier === 0) return num;
+
+	const suffix = SI_SYMBOL[tier];
+	const scale = Math.pow(10, tier * 3);
+	const scaled = num / scale;
+
+	return Number(scaled.toFixed(1)) + suffix;
+}
 
 export function convertToDisplayDate (epochTime: number) {
 	return dateformat(epochTime, 'yyyy-mm-dd HH:MM:ss');
@@ -31,3 +44,18 @@ export function getActivatableTriggerNodes(nodes: INodeUi[]) {
 		return !node.disabled && node.type !== ERROR_TRIGGER_NODE_TYPE;
 	});
 }
+
+export function filterTemplateNodes(nodes: ITemplatesNode[]) {
+	const notCoreNodes = nodes.filter((node: ITemplatesNode) => {
+		return !(node.categories || []).some(
+			(category) => category.name === CORE_NODES_CATEGORY,
+		);
+	});
+
+	const results = notCoreNodes.length > 0 ? notCoreNodes : nodes;
+	return results.filter((elem) => !TEMPLATES_NODES_FILTER.includes(elem.name));
+}
+
+export function setPageTitle(title: string) {
+	window.document.title = title;
+}
diff --git a/packages/editor-ui/src/components/mixins/copyPaste.ts b/packages/editor-ui/src/components/mixins/copyPaste.ts
index 98852c890d..4e368739f7 100644
--- a/packages/editor-ui/src/components/mixins/copyPaste.ts
+++ b/packages/editor-ui/src/components/mixins/copyPaste.ts
@@ -9,6 +9,9 @@ export const copyPaste = Vue.extend({
 	data () {
 		return {
 			copyPasteElementsGotCreated: false,
+			hiddenInput: null as null | Element,
+			onPaste: null as null | Function,
+			onBeforePaste: null as null | Function,
 		};
 	},
 	mounted () {
@@ -53,6 +56,7 @@ export const copyPaste = Vue.extend({
 		hiddenInput.setAttribute('type', 'text');
 		hiddenInput.setAttribute('id', 'hidden-input-copy-paste');
 		hiddenInput.setAttribute('class', 'hidden-copy-paste');
+		this.hiddenInput = hiddenInput;
 
 		document.body.append(hiddenInput);
 
@@ -64,12 +68,14 @@ export const copyPaste = Vue.extend({
 			ieClipboardDiv.setAttribute('contenteditable', 'true');
 			document.body.append(ieClipboardDiv);
 
-			document.addEventListener('beforepaste', () => {
+			this.onBeforePaste =  () => {
 				// @ts-ignore
 				if (hiddenInput.is(':focus')) {
 					this.focusIeClipboardDiv(ieClipboardDiv as HTMLDivElement);
 				}
-			}, true);
+			};
+			// @ts-ignore
+			document.addEventListener('beforepaste', this.onBeforePaste, true);
 		}
 
 		let userInput = '';
@@ -90,36 +96,38 @@ export const copyPaste = Vue.extend({
 			}
 		});
 
-		// Set clipboard event listeners on the document.
-		['paste'].forEach((event) => {
-			document.addEventListener(event, debounce((e) => {
-				// Check if the event got emitted from a message box or from something
-				// else which should ignore the copy/paste
-				// @ts-ignore
-				const path = e.path || (e.composedPath && e.composedPath());
-				for (let index = 0; index < path.length; index++) {
-					if (path[index].className && typeof path[index].className === 'string' && (
-						path[index].className.includes('el-message-box') || path[index].className.includes('ignore-key-press')
-					)) {
-						return;
-					}
+		this.onPaste = debounce((e) => {
+			const event = 'paste';
+			// Check if the event got emitted from a message box or from something
+			// else which should ignore the copy/paste
+			// @ts-ignore
+			const path = e.path || (e.composedPath && e.composedPath());
+			for (let index = 0; index < path.length; index++) {
+				if (path[index].className && typeof path[index].className === 'string' && (
+					path[index].className.includes('el-message-box') || path[index].className.includes('ignore-key-press')
+				)) {
+					return;
 				}
+			}
 
-				if (ieClipboardDiv !== null) {
-					this.ieClipboardEvent(event, ieClipboardDiv);
-				} else {
-					this.standardClipboardEvent(event, e as ClipboardEvent);
-					// @ts-ignore
-					if (!document.activeElement || (document.activeElement && ['textarea', 'text', 'email', 'password'].indexOf(document.activeElement.type) === -1)) {
-						// That it still allows to paste into text, email, password & textarea-fiels we
-						// check if we can identify the active element and if so only
-						// run it if something else is selected.
-						this.focusHiddenArea(hiddenInput);
-						e.preventDefault();
-					}
+			if (ieClipboardDiv !== null) {
+				this.ieClipboardEvent(event, ieClipboardDiv);
+			} else {
+				this.standardClipboardEvent(event, e as ClipboardEvent);
+				// @ts-ignore
+				if (!document.activeElement || (document.activeElement && ['textarea', 'text', 'email', 'password'].indexOf(document.activeElement.type) === -1)) {
+					// That it still allows to paste into text, email, password & textarea-fiels we
+					// check if we can identify the active element and if so only
+					// run it if something else is selected.
+					this.focusHiddenArea(hiddenInput);
+					e.preventDefault();
 				}
-			}, 1000, { leading: true }));
-		});
+			}
+		}, 1000, { leading: true });
+
+		// Set clipboard event listeners on the document.
+		// @ts-ignore
+		document.addEventListener('paste', this.onPaste);
 	},
 	methods: {
 		receivedCopyPasteData (plainTextData: string, event?: ClipboardEvent): void {
@@ -198,4 +206,17 @@ export const copyPaste = Vue.extend({
 		},
 
 	},
+	beforeDestroy() {
+		if (this.hiddenInput) {
+			this.hiddenInput.remove();
+		}
+		if (this.onPaste) {
+			// @ts-ignore
+			document.removeEventListener('paste', this.onPaste);
+		}
+		if (this.onBeforePaste) {
+			// @ts-ignore
+			document.removeEventListener('beforepaste', this.onBeforePaste);
+		}
+	},
 });
diff --git a/packages/editor-ui/src/components/mixins/genericHelpers.ts b/packages/editor-ui/src/components/mixins/genericHelpers.ts
index 15502f9ff9..9b015f0ac3 100644
--- a/packages/editor-ui/src/components/mixins/genericHelpers.ts
+++ b/packages/editor-ui/src/components/mixins/genericHelpers.ts
@@ -77,11 +77,12 @@ export const genericHelpers = mixins(showMessage).extend({
 		async callDebounced (...inputParameters: any[]): Promise<void> { // tslint:disable-line:no-any
 			const functionName = inputParameters.shift() as string;
 			const debounceTime = inputParameters.shift() as number;
+			const trailing = inputParameters.shift() as boolean;
 
 			// @ts-ignore
 			if (this.debouncedFunctions[functionName] === undefined) {
 				// @ts-ignore
-				this.debouncedFunctions[functionName] = debounce(this[functionName], debounceTime, { leading: true });
+				this.debouncedFunctions[functionName] = debounce(this[functionName], debounceTime, trailing ? { trailing: true } : { leading: true } );
 			}
 			// @ts-ignore
 			await this.debouncedFunctions[functionName].apply(this, inputParameters);
diff --git a/packages/editor-ui/src/components/mixins/showMessage.ts b/packages/editor-ui/src/components/mixins/showMessage.ts
index 9bb83b8f50..a7237e062d 100644
--- a/packages/editor-ui/src/components/mixins/showMessage.ts
+++ b/packages/editor-ui/src/components/mixins/showMessage.ts
@@ -150,6 +150,23 @@ export const showMessage = mixins(externalHooks).extend({
 			}
 		},
 
+		async confirmModal (message: string, headline: string, type: MessageType | null = 'warning', confirmButtonText?: string, cancelButtonText?: string, showClose = false): Promise<string> {
+			try {
+				const options: ElMessageBoxOptions  = {
+					confirmButtonText: confirmButtonText || this.$locale.baseText('showMessage.ok'),
+					cancelButtonText: cancelButtonText || this.$locale.baseText('showMessage.cancel'),
+					dangerouslyUseHTMLString: true,
+					showClose,
+					...(type && { type }),
+				};
+
+				await this.$confirm(message, headline, options);
+				return 'confirmed';
+			} catch (e) {
+				return e as string;
+			}
+		},
+
 		clearAllStickyNotifications() {
 			stickyNotificationQueue.map((notification: ElNotificationComponent) => {
 				if (notification) {
diff --git a/packages/editor-ui/src/components/mixins/workflowHelpers.ts b/packages/editor-ui/src/components/mixins/workflowHelpers.ts
index 2e6644a54e..885a4aa65b 100644
--- a/packages/editor-ui/src/components/mixins/workflowHelpers.ts
+++ b/packages/editor-ui/src/components/mixins/workflowHelpers.ts
@@ -451,10 +451,10 @@ export const workflowHelpers = mixins(
 				}
 			},
 
-			async saveCurrentWorkflow({name, tags}: {name?: string, tags?: string[]} = {}): Promise<boolean> {
+			async saveCurrentWorkflow({name, tags}: {name?: string, tags?: string[]} = {}, redirect = true): Promise<boolean> {
 				const currentWorkflow = this.$route.params.name;
 				if (!currentWorkflow) {
-					return this.saveAsNewWorkflow({name, tags});
+					return this.saveAsNewWorkflow({name, tags}, redirect);
 				}
 
 				// Workflow exists already so update it
@@ -501,7 +501,7 @@ export const workflowHelpers = mixins(
 				}
 			},
 
-			async saveAsNewWorkflow ({name, tags, resetWebhookUrls, openInNewWindow}: {name?: string, tags?: string[], resetWebhookUrls?: boolean, openInNewWindow?: boolean} = {}): Promise<boolean> {
+			async saveAsNewWorkflow ({name, tags, resetWebhookUrls, openInNewWindow}: {name?: string, tags?: string[], resetWebhookUrls?: boolean, openInNewWindow?: boolean} = {}, redirect = true): Promise<boolean> {
 				try {
 					this.$store.commit('addActiveAction', 'workflowSaving');
 
@@ -552,10 +552,21 @@ export const workflowHelpers = mixins(
 					const tagIds = createdTags.map((tag: ITag): string => tag.id);
 					this.$store.commit('setWorkflowTagIds', tagIds);
 
-					this.$router.push({
-						name: 'NodeViewExisting',
-						params: { name: workflowData.id as string, action: 'workflowSave' },
-					});
+					const templateId = this.$route.query.templateId;
+					if (templateId) {
+						this.$telemetry.track('User saved new workflow from template', {
+							template_id: templateId,
+							workflow_id: workflowData.id,
+							wf_template_repo_session_id: this.$store.getters['templates/previousSessionId'],
+						});
+					}
+
+					if (redirect) {
+						this.$router.push({
+							name: 'NodeViewExisting',
+							params: { name: workflowData.id as string, action: 'workflowSave' },
+						});
+					}
 
 					this.$store.commit('removeActiveAction', 'workflowSaving');
 					this.$store.commit('setStateDirty', false);
@@ -567,7 +578,7 @@ export const workflowHelpers = mixins(
 
 					this.$showMessage({
 						title: this.$locale.baseText('workflowHelpers.showMessage.title'),
-						message: this.$locale.baseText('workflowHelpers.showMessage.message') + `"${e.message}"`,
+						message: (e as Error).message,
 						type: 'error',
 					});
 
diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts
index a4001cef03..e7fd5d5e80 100644
--- a/packages/editor-ui/src/constants.ts
+++ b/packages/editor-ui/src/constants.ts
@@ -38,8 +38,7 @@ export const BREAKPOINT_LG = 1200;
 export const BREAKPOINT_XL = 1920;
 
 
-// templates
-export const TEMPLATES_BASE_URL = `https://api.n8n.io/`;
+export const N8N_IO_BASE_URL = `https://api.n8n.io/`;
 
 // node types
 export const CALENDLY_TRIGGER_NODE_TYPE = 'n8n-nodes-base.calendlyTrigger';
@@ -136,6 +135,36 @@ export const CODING_SKILL_KEY = 'codingSkill';
 export const OTHER_WORK_AREA_KEY = 'otherWorkArea';
 export const OTHER_COMPANY_INDUSTRY_KEY = 'otherCompanyIndustry';
 
+export const MODAL_CANCEL = 'cancel';
+export const MODAL_CLOSE = 'close';
+export const MODAL_CONFIRMED = 'confirmed';
+
 export const VALID_EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
 export const LOCAL_STORAGE_ACTIVATION_FLAG = 'N8N_HIDE_ACTIVATION_ALERT';
 
+export const HIRING_BANNER = `
+                                                                    //////
+                                                                 ///////////
+                                                               /////      ////
+                                               ///////////////////         ////
+                                             //////////////////////       ////
+     ///////               ///////          ////                /////////////
+  ////////////          ////////////       ////                    ///////
+ ////       ////       ////       ////    ////
+/////        /////////////         //////////
+ /////     ////       ////       ////     ////
+  ////////////          ////////////       ////           ////////
+    ///////                //////           ////        /////////////
+                                             /////////////        ////
+                                                //////////        ////
+                                                       ////      ////
+                                                        ///////////
+                                                          //////
+
+Love n8n? Help us build the future of automation! https://n8n.io/careers
+`;
+
+export const TEMPLATES_NODES_FILTER = [
+	'n8n-nodes-base.start',
+	'n8n-nodes-base.respondToWebhook',
+];
diff --git a/packages/editor-ui/src/modules/credentials.ts b/packages/editor-ui/src/modules/credentials.ts
index efe5e4db23..c890e8a61d 100644
--- a/packages/editor-ui/src/modules/credentials.ts
+++ b/packages/editor-ui/src/modules/credentials.ts
@@ -123,10 +123,16 @@ const module: Module<ICredentialsState, IRootState> = {
 	},
 	actions: {
 		fetchCredentialTypes: async (context: ActionContext<ICredentialsState, IRootState>) => {
+			if (context.getters.allCredentialTypes.length > 0) {
+				return;
+			}
 			const credentialTypes = await getCredentialTypes(context.rootGetters.getRestApiContext);
 			context.commit('setCredentialTypes', credentialTypes);
 		},
 		fetchAllCredentials: async (context: ActionContext<ICredentialsState, IRootState>) => {
+			if (context.getters.allCredentials.length > 0) {
+				return;
+			}
 			const credentials = await getAllCredentials(context.rootGetters.getRestApiContext);
 			context.commit('setCredentials', credentials);
 		},
diff --git a/packages/editor-ui/src/modules/settings.ts b/packages/editor-ui/src/modules/settings.ts
index 3332f2cc92..ff427f4bc6 100644
--- a/packages/editor-ui/src/modules/settings.ts
+++ b/packages/editor-ui/src/modules/settings.ts
@@ -13,12 +13,14 @@ import Vue from 'vue';
 import { getPersonalizedNodeTypes } from './helper';
 import { CONTACT_PROMPT_MODAL_KEY, PERSONALIZATION_MODAL_KEY, VALUE_SURVEY_MODAL_KEY } from '@/constants';
 import { ITelemetrySettings } from 'n8n-workflow';
+import { testHealthEndpoint } from '@/api/templates';
 
 const module: Module<ISettingsState, IRootState> = {
 	namespaced: true,
 	state: {
 		settings: {} as IN8nUISettings,
 		promptsData: {} as IN8nPrompts,
+		templatesEndpointHealthy: false,
 	},
 	getters: {
 		personalizedNodeTypes(state: ISettingsState): string[] {
@@ -41,6 +43,18 @@ const module: Module<ISettingsState, IRootState> = {
 		isTelemetryEnabled: (state) => {
 			return state.settings.telemetry && state.settings.telemetry.enabled;
 		},
+		isInternalUser: (state): boolean => {
+			return state.settings.deploymentType === 'n8n-internal';
+		},
+		isTemplatesEnabled: (state): boolean => {
+			return Boolean(state.settings.templates && state.settings.templates.enabled);
+		},
+		isTemplatesEndpointReachable: (state): boolean => {
+			return state.templatesEndpointHealthy;
+		},
+		templatesHost: (state): string  => {
+			return state.settings.templates.host;
+		},
 	},
 	mutations: {
 		setSettings(state: ISettingsState, settings: IN8nUISettings) {
@@ -55,6 +69,9 @@ const module: Module<ISettingsState, IRootState> = {
 		setPromptsData(state: ISettingsState, promptsData: IN8nPrompts) {
 			Vue.set(state, 'promptsData', promptsData);
 		},
+		setTemplatesEndpointHealthy(state: ISettingsState) {
+			state.templatesEndpointHealthy = true;
+		},
 	},
 	actions: {
 		async getSettings(context: ActionContext<ISettingsState, IRootState>) {
@@ -124,6 +141,11 @@ const module: Module<ISettingsState, IRootState> = {
 				return e;
 			}
 		},
+		async testTemplatesEndpoint(context: ActionContext<ISettingsState, IRootState>) {
+			const timeout = new Promise((_, reject) => setTimeout(() => reject(), 2000));
+			await Promise.race([testHealthEndpoint(context.getters.templatesHost), timeout]);
+			context.commit('setTemplatesEndpointHealthy', true);
+		},
 	},
 };
 
diff --git a/packages/editor-ui/src/modules/templates.ts b/packages/editor-ui/src/modules/templates.ts
new file mode 100644
index 0000000000..44c0c379f9
--- /dev/null
+++ b/packages/editor-ui/src/modules/templates.ts
@@ -0,0 +1,282 @@
+import { getCategories, getCollectionById, getCollections, getTemplateById, getWorkflows, getWorkflowTemplate } from '@/api/templates';
+import { ActionContext, Module } from 'vuex';
+import {
+	IRootState,
+	ITemplatesCollection,
+	ITemplatesWorkflow,
+	ITemplatesCategory,
+	ITemplateState,
+	ITemplatesQuery,
+	ITemplatesWorkflowFull,
+	ITemplatesCollectionFull,
+	IWorkflowTemplate,
+} from '../Interface';
+
+import Vue from 'vue';
+
+const TEMPLATES_PAGE_SIZE = 10;
+
+function getSearchKey(query: ITemplatesQuery): string {
+	return JSON.stringify([query.search || '', [...query.categories].sort()]);
+}
+
+const module: Module<ITemplateState, IRootState> = {
+	namespaced: true,
+	state: {
+		categories: {},
+		collections: {},
+		workflows: {},
+		collectionSearches: {},
+		workflowSearches: {},
+		currentSessionId: '',
+		previousSessionId: '',
+	},
+	getters: {
+		allCategories(state: ITemplateState) {
+			return Object.values(state.categories).sort((a: ITemplatesCategory, b: ITemplatesCategory) => a.name > b.name ? 1: -1);
+		},
+		getTemplateById(state: ITemplateState) {
+			return (id: string): null | ITemplatesWorkflow => state.workflows[id];
+		},
+		getCollectionById(state: ITemplateState) {
+			return (id: string): null | ITemplatesCollection => state.collections[id];
+		},
+		getCategoryById(state: ITemplateState) {
+			return (id: string): null | ITemplatesCategory => state.categories[id];
+		},
+		getSearchedCollections(state: ITemplateState) {
+			return (query: ITemplatesQuery) => {
+				const searchKey = getSearchKey(query);
+				const search = state.collectionSearches[searchKey];
+				if (!search) {
+					return null;
+				}
+
+				return search.collectionIds.map((collectionId: string) => state.collections[collectionId]);
+			};
+		},
+		getSearchedWorkflows(state: ITemplateState) {
+			return (query: ITemplatesQuery) => {
+				const searchKey = getSearchKey(query);
+				const search = state.workflowSearches[searchKey];
+				if (!search) {
+					return null;
+				}
+
+				return search.workflowIds.map((workflowId: string) => state.workflows[workflowId]);
+			};
+		},
+		getSearchedWorkflowsTotal(state: ITemplateState) {
+			return (query: ITemplatesQuery) => {
+				const searchKey = getSearchKey(query);
+				const search = state.workflowSearches[searchKey];
+
+				return search ? search.totalWorkflows : 0;
+			};
+		},
+		isSearchLoadingMore(state: ITemplateState) {
+			return (query: ITemplatesQuery) => {
+				const searchKey = getSearchKey(query);
+				const search = state.workflowSearches[searchKey];
+
+				return Boolean(search && search.loadingMore);
+			};
+		},
+		isSearchFinished(state: ITemplateState) {
+			return (query: ITemplatesQuery) => {
+				const searchKey = getSearchKey(query);
+				const search = state.workflowSearches[searchKey];
+
+				return Boolean(search && !search.loadingMore && search.totalWorkflows === search.workflowIds.length);
+			};
+		},
+		currentSessionId(state: ITemplateState) {
+			return state.currentSessionId;
+		},
+		previousSessionId(state: ITemplateState) {
+			return state.previousSessionId;
+		},
+	},
+	mutations: {
+		addCategories(state: ITemplateState, categories: ITemplatesCategory[]) {
+			categories.forEach((category: ITemplatesCategory) => {
+				Vue.set(state.categories, category.id, category);
+			});
+		},
+		addCollections(state: ITemplateState, collections: Array<ITemplatesCollection | ITemplatesCollectionFull>) {
+			collections.forEach((collection) => {
+				const workflows = (collection.workflows || []).map((workflow) => ({id: workflow.id}));
+				const cachedCollection = state.collections[collection.id] || {};
+				Vue.set(state.collections, collection.id, {
+					...cachedCollection,
+					...collection,
+					workflows,
+				});
+			});
+		},
+		addWorkflows(state: ITemplateState, workflows: Array<ITemplatesWorkflow | ITemplatesWorkflowFull>) {
+			workflows.forEach((workflow: ITemplatesWorkflow) => {
+				const cachedWorkflow = state.workflows[workflow.id] || {};
+				Vue.set(state.workflows, workflow.id, {
+					...cachedWorkflow,
+					...workflow,
+				});
+			});
+		},
+		addCollectionSearch(state: ITemplateState, data: {collections: ITemplatesCollection[], query: ITemplatesQuery}) {
+			const collectionIds = data.collections.map((collection) => collection.id);
+			const searchKey = getSearchKey(data.query);
+			Vue.set(state.collectionSearches, searchKey, {
+				collectionIds,
+			});
+		},
+		addWorkflowsSearch(state: ITemplateState, data: {totalWorkflows: number; workflows: ITemplatesWorkflow[], query: ITemplatesQuery}) {
+			const workflowIds = data.workflows.map((workflow) => workflow.id);
+			const searchKey = getSearchKey(data.query);
+			const cachedResults = state.workflowSearches[searchKey];
+			if (!cachedResults) {
+				Vue.set(state.workflowSearches, searchKey, {
+					workflowIds,
+					totalWorkflows: data.totalWorkflows,
+				});
+
+				return;
+			}
+
+			Vue.set(state.workflowSearches, searchKey, {
+				workflowIds: [...cachedResults.workflowIds, ...workflowIds],
+				totalWorkflows: data.totalWorkflows,
+			});
+		},
+		setWorkflowSearchLoading(state: ITemplateState, query: ITemplatesQuery) {
+			const searchKey = getSearchKey(query);
+			const cachedResults = state.workflowSearches[searchKey];
+			if (!cachedResults) {
+				return;
+			}
+
+			Vue.set(state.workflowSearches[searchKey], 'loadingMore', true);
+		},
+		setWorkflowSearchLoaded(state: ITemplateState, query: ITemplatesQuery) {
+			const searchKey = getSearchKey(query);
+			const cachedResults = state.workflowSearches[searchKey];
+			if (!cachedResults) {
+				return;
+			}
+
+			Vue.set(state.workflowSearches[searchKey], 'loadingMore', false);
+		},
+		resetSessionId(state: ITemplateState) {
+			state.previousSessionId = state.currentSessionId;
+			state.currentSessionId = '';
+		},
+		setSessionId(state: ITemplateState) {
+			if (!state.currentSessionId) {
+				state.currentSessionId = `templates-${Date.now()}`;
+			}
+		},
+	},
+	actions: {
+		async getTemplateById(context: ActionContext<ITemplateState, IRootState>, templateId: string): Promise<ITemplatesWorkflowFull> {
+			const apiEndpoint: string = context.rootGetters['settings/templatesHost'];
+			const versionCli: string = context.rootGetters['versionCli'];
+			const response = await getTemplateById(apiEndpoint, templateId, { 'n8n-version': versionCli });
+			const template: ITemplatesWorkflowFull = {
+				...response.workflow,
+				full: true,
+			};
+
+			context.commit('addWorkflows', [template]);
+			return template;
+		},
+		async getCollectionById(context: ActionContext<ITemplateState, IRootState>, collectionId: string): Promise<ITemplatesCollection> {
+			const apiEndpoint: string = context.rootGetters['settings/templatesHost'];
+			const versionCli: string = context.rootGetters['versionCli'];
+			const response = await getCollectionById(apiEndpoint, collectionId, { 'n8n-version': versionCli });
+			const collection: ITemplatesCollectionFull = {
+				...response.collection,
+				full: true,
+			};
+
+			context.commit('addCollections', [collection]);
+			context.commit('addWorkflows', response.collection.workflows);
+
+			return context.getters.getCollectionById(collectionId);
+		},
+		async getCategories(context: ActionContext<ITemplateState, IRootState>): Promise<ITemplatesCategory[]> {
+			const cachedCategories: ITemplatesCategory[] = context.getters.allCategories;
+			if (cachedCategories.length) {
+				return cachedCategories;
+			}
+			const apiEndpoint: string = context.rootGetters['settings/templatesHost'];
+			const versionCli: string = context.rootGetters['versionCli'];
+			const response = await getCategories(apiEndpoint, { 'n8n-version': versionCli });
+			const categories = response.categories;
+
+			context.commit('addCategories', categories);
+
+			return categories;
+		},
+		async getCollections(context: ActionContext<ITemplateState, IRootState>, query: ITemplatesQuery): Promise<ITemplatesCollection[]> {
+			const cachedResults: ITemplatesCollection[] | null = context.getters.getSearchedCollections(query);
+			if (cachedResults) {
+				return cachedResults;
+			}
+
+			const apiEndpoint: string = context.rootGetters['settings/templatesHost'];
+			const versionCli: string = context.rootGetters['versionCli'];
+			const response = await getCollections(apiEndpoint, query, { 'n8n-version': versionCli });
+			const collections = response.collections;
+
+			context.commit('addCollections', collections);
+			context.commit('addCollectionSearch', {query, collections});
+			collections.forEach((collection: ITemplatesCollection) => context.commit('addWorkflows', collection.workflows));
+
+			return collections;
+		},
+		async getWorkflows(context: ActionContext<ITemplateState, IRootState>, query: ITemplatesQuery): Promise<ITemplatesWorkflow[]> {
+			const cachedResults: ITemplatesWorkflow[] = context.getters.getSearchedWorkflows(query);
+			if (cachedResults) {
+				return cachedResults;
+			}
+
+			const apiEndpoint: string = context.rootGetters['settings/templatesHost'];
+			const versionCli: string = context.rootGetters['versionCli'];
+
+			const payload = await getWorkflows(apiEndpoint, {...query, skip: 0, limit: TEMPLATES_PAGE_SIZE}, { 'n8n-version': versionCli });
+
+			context.commit('addWorkflows', payload.workflows);
+			context.commit('addWorkflowsSearch', {...payload, query});
+
+			return context.getters.getSearchedWorkflows(query);
+		},
+		async getMoreWorkflows(context: ActionContext<ITemplateState, IRootState>, query: ITemplatesQuery): Promise<ITemplatesWorkflow[]> {
+			if (context.getters.isSearchLoadingMore(query) && !context.getters.isSearchFinished(query)) {
+				return [];
+			}
+			const cachedResults: ITemplatesWorkflow[] = context.getters.getSearchedWorkflows(query) || [];
+			const apiEndpoint: string = context.rootGetters['settings/templatesHost'];
+
+			context.commit('setWorkflowSearchLoading', query);
+			try {
+				const payload = await getWorkflows(apiEndpoint, {...query, skip: cachedResults.length, limit: TEMPLATES_PAGE_SIZE});
+
+				context.commit('setWorkflowSearchLoaded', query);
+				context.commit('addWorkflows', payload.workflows);
+				context.commit('addWorkflowsSearch', {...payload, query});
+
+				return context.getters.getSearchedWorkflows(query);
+			} catch (e) {
+				context.commit('setWorkflowSearchLoaded', query);
+				throw e;
+			}
+		},
+		getWorkflowTemplate: async (context: ActionContext<ITemplateState, IRootState>, templateId: string): Promise<IWorkflowTemplate> => {
+			const apiEndpoint: string = context.rootGetters['settings/templatesHost'];
+			const versionCli: string = context.rootGetters['versionCli'];
+			return await getWorkflowTemplate(apiEndpoint, templateId, { 'n8n-version': versionCli });
+		},
+	},
+};
+
+export default module;
diff --git a/packages/editor-ui/src/modules/ui.ts b/packages/editor-ui/src/modules/ui.ts
index 331828bbe1..bd9cb9b44d 100644
--- a/packages/editor-ui/src/modules/ui.ts
+++ b/packages/editor-ui/src/modules/ui.ts
@@ -55,8 +55,12 @@ const module: Module<IUiState, IRootState> = {
 		modalStack: [],
 		sidebarMenuCollapsed: true,
 		isPageLoading: true,
+		currentView: '',
 	},
 	getters: {
+		areExpressionsDisabled(state: IUiState) {
+			return state.currentView === 'WorkflowDemo';
+		},
 		isVersionsOpen: (state: IUiState) => {
 			return state.modals[VERSIONS_MODAL_KEY].open;
 		},
@@ -104,6 +108,9 @@ const module: Module<IUiState, IRootState> = {
 		toggleSidebarMenuCollapse: (state: IUiState) => {
 			state.sidebarMenuCollapsed = !state.sidebarMenuCollapsed;
 		},
+		setCurrentView: (state: IUiState, currentView: string) => {
+			state.currentView = currentView;
+		},
 	},
 	actions: {
 		openModal: async (context: ActionContext<IUiState, IRootState>, modalKey: string) => {
diff --git a/packages/editor-ui/src/modules/workflows.ts b/packages/editor-ui/src/modules/workflows.ts
index 3654c13de5..8a44f11243 100644
--- a/packages/editor-ui/src/modules/workflows.ts
+++ b/packages/editor-ui/src/modules/workflows.ts
@@ -1,10 +1,9 @@
-import { getNewWorkflow, getWorkflowTemplate } from '@/api/workflows';
+import { getNewWorkflow } from '@/api/workflows';
 import { DUPLICATE_POSTFFIX, MAX_WORKFLOW_NAME_LENGTH, DEFAULT_NEW_WORKFLOW_NAME } from '@/constants';
 import { ActionContext, Module } from 'vuex';
 import {
 	IRootState,
 	IWorkflowsState,
-	IWorkflowTemplate,
 } from '../Interface';
 
 const module: Module<IWorkflowsState, IRootState> = {
@@ -39,14 +38,11 @@ const module: Module<IWorkflowsState, IRootState> = {
 				newName = newWorkflow.name;
 			}
 			catch (e) {
-			}			
+			}
 
 			return newName;
 		},
-		getWorkflowTemplate: async (context: ActionContext<IWorkflowsState, IRootState>, templateId: string): Promise<IWorkflowTemplate> => {
-			return await getWorkflowTemplate(templateId);
-		},
 	},
 };
 
-export default module;
\ No newline at end of file
+export default module;
diff --git a/packages/editor-ui/src/n8n-theme.scss b/packages/editor-ui/src/n8n-theme.scss
index 7d865c78b9..acd8ab1c75 100644
--- a/packages/editor-ui/src/n8n-theme.scss
+++ b/packages/editor-ui/src/n8n-theme.scss
@@ -2,11 +2,6 @@
 
 @import "~n8n-design-system/theme/dist/index.css";
 
-
-body {
-	background-color: var(--color-canvas-background);
-}
-
 .clickable {
 	cursor: pointer;
 }
diff --git a/packages/editor-ui/src/plugins/components.ts b/packages/editor-ui/src/plugins/components.ts
index 24856be949..7c84fa12a6 100644
--- a/packages/editor-ui/src/plugins/components.ts
+++ b/packages/editor-ui/src/plugins/components.ts
@@ -37,6 +37,7 @@ import MessageBox from 'element-ui/lib/message-box';
 import Message from 'element-ui/lib/message';
 import Notification from 'element-ui/lib/notification';
 import CollapseTransition from 'element-ui/lib/transitions/collapse-transition';
+import VueAgile from 'vue-agile';
 
 // @ts-ignore
 import lang from 'element-ui/lib/locale/lang/en';
@@ -50,12 +51,16 @@ import {
 	N8nInput,
 	N8nInputLabel,
 	N8nInputNumber,
+	N8nLoading,
 	N8nHeading,
+	N8nMarkdown,
 	N8nMenu,
 	N8nMenuItem,
 	N8nSelect,
 	N8nSpinner,
 	N8nSquareButton,
+	N8nTags,
+	N8nTag,
 	N8nText,
 	N8nTooltip,
 	N8nOption,
@@ -71,12 +76,16 @@ Vue.use(N8nInfoTip);
 Vue.use(N8nInput);
 Vue.use(N8nInputLabel);
 Vue.use(N8nInputNumber);
+Vue.component('n8n-loading', N8nLoading);
 Vue.use(N8nHeading);
+Vue.component('n8n-markdown', N8nMarkdown);
 Vue.use(N8nMenu);
 Vue.use(N8nMenuItem);
 Vue.use(N8nSelect);
 Vue.use(N8nSpinner);
 Vue.component('n8n-square-button', N8nSquareButton);
+Vue.use(N8nTags);
+Vue.use(N8nTag);
 Vue.component('n8n-text', N8nText);
 Vue.use(N8nTooltip);
 Vue.use(N8nOption);
@@ -111,6 +120,7 @@ Vue.use(Badge);
 Vue.use(Card);
 Vue.use(ColorPicker);
 Vue.use(Container);
+Vue.use(VueAgile);
 
 Vue.component(CollapseTransition.name, CollapseTransition);
 
@@ -141,7 +151,8 @@ Vue.prototype.$confirm = async (message: string, configOrTitle: string | ElMessa
 		roundButton: true,
 		cancelButtonClass: 'btn--cancel',
 		confirmButtonClass: 'btn--confirm',
-		showClose: false,
+		distinguishCancelAndClose: true,
+		showClose: config.showClose || false,
 		closeOnClickModal: false,
 	};
 
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index 005c955d74..806270c2ac 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -131,7 +131,8 @@
 		},
 		"type": "Type",
 		"updated": "Updated",
-		"yourSavedCredentials": "Your saved credentials"
+		"yourSavedCredentials": "Your saved credentials",
+		"errorLoadingCredentials": "Error loading credentials"
 	},
 	"dataDisplay": {
 		"needHelp": "Need help?",
@@ -289,10 +290,10 @@
 				"message": "Are you sure that you want to delete '{workflowName}'?"
 			},
 			"workflowNew": {
-				"cancelButtonText": "",
-				"confirmButtonText": "Yes, switch workflows and forget changes",
-				"headline": "Switch workflows without saving?",
-				"message": "When you switch workflows without saving, your current changes will be lost."
+				"cancelButtonText": "Leave without saving",
+				"confirmButtonText": "Save",
+				"headline": "Save changes before leaving?",
+				"message": "If you don't save, you will lose your changes."
 			}
 		},
 		"credentials": "Credentials",
@@ -309,6 +310,7 @@
 		"importFromFile": "Import from File",
 		"importFromUrl": "Import from URL",
 		"new": "New",
+		"newTemplate": "New from template",
 		"open": "Open",
 		"prompt": {
 			"cancel": "@:reusableBaseText.cancel",
@@ -342,6 +344,7 @@
 				"title": "Execution stopped"
 			}
 		},
+		"templates": "Templates",
 		"workflows": "Workflows"
 	},
 	"multipleParameter": {
@@ -488,16 +491,16 @@
 		"addNode": "Add node",
 		"confirmMessage": {
 			"beforeRouteLeave": {
-				"cancelButtonText": "",
-				"confirmButtonText": "Yes, switch workflows and forget changes",
-				"headline": "Switch workflows without saving?",
-				"message": "When you switch workflows without saving, your current changes will be lost."
+				"cancelButtonText": "Leave without saving",
+				"confirmButtonText": "Save",
+				"headline": "Save changes before leaving?",
+				"message": "If you don't save, you will lose your changes."
 			},
 			"initView": {
-				"cancelButtonText": "",
-				"confirmButtonText": "Yes, switch workflows and forget changes",
-				"headline": "Switch workflows without saving?",
-				"message": "When you switch workflows without saving, your current changes will be lost."
+				"cancelButtonText": "Leave without saving",
+				"confirmButtonText": "Save",
+				"headline": "Save changes before leaving?",
+				"message": "If you don't save, you will lose your changes."
 			},
 			"receivedCopyPasteData": {
 				"cancelButtonText": "",
@@ -620,7 +623,8 @@
 		"refreshList": "Refresh List",
 		"removeExpression": "Remove Expression",
 		"resetValue": "Reset Value",
-		"selectDateAndTime": "Select date and time"
+		"selectDateAndTime": "Select date and time",
+		"select": "Select"
 	},
 	"parameterInputExpanded": {
 		"openDocs": "Open docs",
@@ -729,6 +733,14 @@
 		"saved": "Saved",
 		"saving": "Saving"
 	},
+	"settings": {
+		"errors": {
+			"connectionError": {
+				"title": "Error connecting to n8n",
+				"message": "Could not connect to server. <a onclick='window.location.reload(false);'>Refresh</a> to try again"
+			}
+		}
+	},
 	"showMessage": {
 		"cancel": "@:reusableBaseText.cancel",
 		"ok": "OK",
@@ -795,6 +807,38 @@
 		},
 		"notBeingUsed": "Not being used"
 	},
+	"template": {
+		"buttons": {
+			"goBackButton": "Go back",
+			"useThisWorkflowButton": "Use this workflow"
+		},
+		"details": {
+			"appsInTheWorkflow": "Apps in this workflow",
+			"appsInTheCollection": "This collection features",
+			"by": "by",
+			"categories": "Categories",
+			"created": "Created",
+			"details": "Details",
+			"times": "times",
+			"viewed": "Viewed"
+		}
+	},
+	"templates": {
+		"allCategories": "All Categories",
+		"categoriesHeading": "Categories",
+		"collection": "Collection",
+		"collections": "Collections",
+		"collectionsNotFound": "Collection could not be found",
+		"endResult": "Share your own useful workflows through your <a href='https://n8n.io/dashboard' target='_blank'>n8n.io account</a>",
+		"heading": "Workflow templates",
+		"newButton": "New blank workflow",
+		"noSearchResults": "Nothing found. Try adjusting your search to see more.",
+		"searchPlaceholder": "Search workflows",
+		"workflow": "Workflow",
+		"workflows": "Workflows",
+		"workflowsNotFound": "Workflow could not be found",
+		"connectionWarning": "⚠️ There was a problem fetching workflow templates. Check your internet connection."
+	},
 	"textEdit": {
 		"edit": "Edit"
 	},
@@ -898,10 +942,10 @@
 	"workflowOpen": {
 		"active": "Active",
 		"confirmMessage": {
-			"cancelButtonText": "",
-			"confirmButtonText": "Yes, switch workflows and forget changes",
-			"headline": "Switch workflows without saving?",
-			"message": "If you do this, your current changes will be lost."
+			"cancelButtonText": "Leave without saving",
+			"confirmButtonText": "Save",
+			"headline": "Save changes before leaving?",
+			"message": "If you don't save, you will lose your changes."
 		},
 		"created": "Created",
 		"name": "@:reusableBaseText.name",
@@ -915,7 +959,8 @@
 			"message": "This is the current workflow",
 			"title": "Workflow already open"
 		},
-		"updated": "Updated"
+		"updated": "Updated",
+		"couldNotLoadActiveWorkflows": "Could not load active workflows"
 	},
 	"workflowRun": {
 		"noActiveConnectionToTheServer": "Lost connection to the server",
@@ -1009,5 +1054,15 @@
 		"yourWorkflowWillNowRegularlyCheck": "Your workflow will now regularly check {serviceName} for events and trigger executions for them.",
 		"yourWorkflowWillNowListenForEvents": "Your workflow will now listen for events from {serviceName} and trigger executions.",
 		"gotIt": "Got it"
+	},
+	"workflowPreview": {
+		"showError": {
+			"previewError": {
+				"message": "Unable to preview workflow",
+				"title": "Preview error"
+			},
+			"missingWorkflow": "Missing workflow",
+			"arrayEmpty": "Must have an array of nodes"
+		}
 	}
 }
diff --git a/packages/editor-ui/src/plugins/icons.ts b/packages/editor-ui/src/plugins/icons.ts
index 1a6d086ed6..864f5cc940 100644
--- a/packages/editor-ui/src/plugins/icons.ts
+++ b/packages/editor-ui/src/plugins/icons.ts
@@ -10,12 +10,15 @@ import {
 	faArrowRight,
 	faAt,
 	faBook,
+	faBoxOpen,
 	faBug,
 	faCalendar,
 	faCheck,
 	faCheckCircle,
 	faChevronDown,
 	faChevronUp,
+	faChevronLeft,
+	faChevronRight,
 	faCode,
 	faCodeBranch,
 	faCog,
@@ -99,10 +102,13 @@ addIcon(faArrowLeft);
 addIcon(faArrowRight);
 addIcon(faAt);
 addIcon(faBook);
+addIcon(faBoxOpen);
 addIcon(faBug);
 addIcon(faCalendar);
 addIcon(faCheck);
 addIcon(faCheckCircle);
+addIcon(faChevronLeft);
+addIcon(faChevronRight);
 addIcon(faChevronDown);
 addIcon(faChevronUp);
 addIcon(faCode);
diff --git a/packages/editor-ui/src/plugins/telemetry/index.ts b/packages/editor-ui/src/plugins/telemetry/index.ts
index 146d4d1f48..a44e8769d3 100644
--- a/packages/editor-ui/src/plugins/telemetry/index.ts
+++ b/packages/editor-ui/src/plugins/telemetry/index.ts
@@ -3,7 +3,9 @@ import {
 	ITelemetrySettings,
 	IDataObject,
 } from 'n8n-workflow';
-import { ILogLevel, INodeCreateElement } from "@/Interface";
+import { ILogLevel, INodeCreateElement, IRootState } from "@/Interface";
+import { Route } from "vue-router";
+import { Store } from "vuex";
 
 declare module 'vue/types/vue' {
 	interface Vue {
@@ -35,7 +37,9 @@ interface IUserNodesPanelSession {
 
 class Telemetry {
 
-	private pageEventQueue: Array<{category?: string, name?: string | null}>;
+	private pageEventQueue: Array<{category: string, route: Route}>;
+	private previousPath: string;
+	private store: Store<IRootState> | null;
 
 	private get telemetry() {
 		// @ts-ignore
@@ -53,17 +57,20 @@ class Telemetry {
 
 	constructor() {
 		this.pageEventQueue = [];
+		this.previousPath = '';
+		this.store = null;
 	}
 
-	init(options: ITelemetrySettings, instanceId: string, logLevel?: ILogLevel) {
+	init(options: ITelemetrySettings, props: {instanceId: string, logLevel?: ILogLevel, store: Store<IRootState>}) {
 		if (options.enabled && !this.telemetry) {
 			if(!options.config) {
 				return;
 			}
 
-			const logging = logLevel === 'debug' ? { logLevel: 'DEBUG'} : {};
+			this.store = props.store;
+			const logging = props.logLevel === 'debug' ? { logLevel: 'DEBUG'} : {};
 			this.loadTelemetryLibrary(options.config.key, options.config.url, { integrations: { All: false }, loadIntegration: false, ...logging});
-			this.telemetry.identify(instanceId);
+			this.telemetry.identify(props.instanceId);
 			this.flushPageEvents();
 		}
 	}
@@ -74,14 +81,24 @@ class Telemetry {
 		}
 	}
 
-	page(category?: string, name?: string | null) {
+	page(category: string, route: Route) {
 		if (this.telemetry)	{
-			this.telemetry.page(category, name);
+			if (route.path === this.previousPath) { // avoid duplicate requests query is changed for example on search page
+				return;
+			}
+			this.previousPath = route.path;
+
+			const pageName = route.name;
+			let properties: {[key: string]: string} = {};
+			if (this.store && route.meta && route.meta.telemetry && typeof route.meta.telemetry.getProperties === 'function') {
+				properties = route.meta.telemetry.getProperties(route, this.store);
+			}
+			this.telemetry.page(category, pageName, properties);
 		}
 		else {
 			this.pageEventQueue.push({
 				category,
-				name,
+				route,
 			});
 		}
 	}
@@ -89,10 +106,8 @@ class Telemetry {
 	flushPageEvents() {
 		const queue = this.pageEventQueue;
 		this.pageEventQueue = [];
-		queue.forEach(({category, name}) => {
-			if (this.telemetry) {
-				this.telemetry.page(category, name);
-			}
+		queue.forEach(({category, route}) => {
+			this.page(category, route);
 		});
 	}
 
diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts
index bfd54b0dba..30fe2bc48d 100644
--- a/packages/editor-ui/src/router.ts
+++ b/packages/editor-ui/src/router.ts
@@ -1,8 +1,14 @@
 import Vue from 'vue';
-import Router from 'vue-router';
+import Router, { Route } from 'vue-router';
+
+import TemplatesCollectionView from '@/views/TemplatesCollectionView.vue';
 import MainHeader from '@/components/MainHeader/MainHeader.vue';
 import MainSidebar from '@/components/MainSidebar.vue';
 import NodeView from '@/views/NodeView.vue';
+import TemplatesWorkflowView from '@/views/TemplatesWorkflowView.vue';
+import TemplatesSearchView from '@/views/TemplatesSearchView.vue';
+import { Store } from 'vuex';
+import { IRootState } from './Interface';
 
 Vue.use(Router);
 
@@ -11,6 +17,25 @@ export default new Router({
 	// @ts-ignore
 	base: window.BASE_PATH === '/%BASE_PATH%/' ? '/' : window.BASE_PATH,
 	routes: [
+		{
+			path: '/collections/:id',
+			name: 'TemplatesCollectionView',
+			components: {
+				default: TemplatesCollectionView,
+				sidebar: MainSidebar,
+			},
+			meta: {
+				templatesEnabled: true,
+				telemetry: {
+					getProperties(route: Route, store: Store<IRootState>) {
+						return {
+							collection_id: route.params.id,
+							wf_template_repo_session_id: store.getters['templates/currentSessionId'],
+						};
+					},
+				},
+			},
+		},
 		{
 			path: '/execution/:id',
 			name: 'ExecutionById',
@@ -19,6 +44,46 @@ export default new Router({
 				header: MainHeader,
 				sidebar: MainSidebar,
 			},
+			meta: {
+				nodeView: true,
+			},
+		},
+		{
+			path: '/templates/:id',
+			name: 'TemplatesWorkflowView',
+			components: {
+				default: TemplatesWorkflowView,
+				sidebar: MainSidebar,
+			},
+			meta: {
+				templatesEnabled: true,
+				telemetry: {
+					getProperties(route: Route, store: Store<IRootState>) {
+						return {
+							template_id: route.params.id,
+							wf_template_repo_session_id: store.getters['templates/currentSessionId'],
+						};
+					},
+				},
+			},
+		},
+		{
+			path: '/templates/',
+			name: 'TemplatesSearchView',
+			components: {
+				default: TemplatesSearchView,
+				sidebar: MainSidebar,
+			},
+			meta: {
+				templatesEnabled: true,
+				telemetry: {
+					getProperties(route: Route, store: Store<IRootState>) {
+						return {
+							wf_template_repo_session_id: store.getters['templates/currentSessionId'],
+						};
+					},
+				},
+			},
 		},
 		{
 			path: '/workflow',
@@ -28,6 +93,9 @@ export default new Router({
 				header: MainHeader,
 				sidebar: MainSidebar,
 			},
+			meta: {
+				nodeView: true,
+			},
 		},
 		{
 			path: '/workflow/:name',
@@ -37,10 +105,9 @@ export default new Router({
 				header: MainHeader,
 				sidebar: MainSidebar,
 			},
-		},
-		{
-			path: '/',
-			redirect: '/workflow',
+			meta: {
+				nodeView: true,
+			},
 		},
 		{
 			path: '/workflows/templates/:id',
@@ -50,6 +117,9 @@ export default new Router({
 				header: MainHeader,
 				sidebar: MainSidebar,
 			},
+			meta: {
+				templatesEnabled: true,
+			},
 		},
 		{
 			path: '/workflows/demo',
@@ -57,6 +127,12 @@ export default new Router({
 			components: {
 				default: NodeView,
 			},
+			meta: {
+				nodeView: true,
+				telemetry: {
+					disabled: true,
+				},
+			},
 		},
 	],
 });
diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts
index 8cd1d6c643..e97ab47731 100644
--- a/packages/editor-ui/src/store.ts
+++ b/packages/editor-ui/src/store.ts
@@ -37,6 +37,7 @@ import settings from './modules/settings';
 import ui from './modules/ui';
 import workflows from './modules/workflows';
 import versions from './modules/versions';
+import templates from './modules/templates';
 
 Vue.use(Vuex);
 
@@ -94,6 +95,7 @@ const modules = {
 	credentials,
 	tags,
 	settings,
+	templates,
 	workflows,
 	versions,
 	ui,
diff --git a/packages/editor-ui/src/views/LoadingView.vue b/packages/editor-ui/src/views/LoadingView.vue
new file mode 100644
index 0000000000..55a051b3d3
--- /dev/null
+++ b/packages/editor-ui/src/views/LoadingView.vue
@@ -0,0 +1,29 @@
+<template>
+  <div :class="$style.wrapper">
+		<div :class="$style.spinner">
+			<n8n-spinner />
+		</div>
+	</div>
+</template>
+
+<style lang="scss" module>
+.wrapper {
+	width: 100%;
+	height: 100%;
+	position: absolute;
+	background-color: var(--color-background-light);
+}
+
+.spinner {
+	margin-bottom: var(--spacing-l);
+	position: absolute;
+	left: 50%;
+	top: 30%;
+
+	* {
+		color: var(--color-primary);
+		min-height: 40px;
+		min-width: 40px;
+	}
+}
+</style>
diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue
index 14810077ca..15be05eac3 100644
--- a/packages/editor-ui/src/views/NodeView.vue
+++ b/packages/editor-ui/src/views/NodeView.vue
@@ -104,7 +104,6 @@
 				@click.stop="clearExecutionData()"
 			/>
 		</div>
-		<Modals />
 	</div>
 </template>
 
@@ -115,7 +114,7 @@ import {
 } from 'jsplumb';
 import { MessageBoxInputData } from 'element-ui/types/message-box';
 import { jsPlumb, OnConnectionBindInfo } from 'jsplumb';
-import { NODE_NAME_PREFIX, NODE_OUTPUT_DEFAULT_KEY, PLACEHOLDER_EMPTY_WORKFLOW_ID, START_NODE_TYPE, WEBHOOK_NODE_TYPE, WORKFLOW_OPEN_MODAL_KEY } from '@/constants';
+import { MODAL_CANCEL, MODAL_CLOSE, MODAL_CONFIRMED, NODE_NAME_PREFIX, NODE_OUTPUT_DEFAULT_KEY, PLACEHOLDER_EMPTY_WORKFLOW_ID, START_NODE_TYPE, WEBHOOK_NODE_TYPE, WORKFLOW_OPEN_MODAL_KEY } from '@/constants';
 import { copyPaste } from '@/components/mixins/copyPaste';
 import { externalHooks } from '@/components/mixins/externalHooks';
 import { genericHelpers } from '@/components/mixins/genericHelpers';
@@ -130,7 +129,6 @@ import { workflowHelpers } from '@/components/mixins/workflowHelpers';
 import { workflowRun } from '@/components/mixins/workflowRun';
 
 import DataDisplay from '@/components/DataDisplay.vue';
-import Modals from '@/components/Modals.vue';
 import Node from '@/components/Node.vue';
 import NodeCreator from '@/components/NodeCreator/NodeCreator.vue';
 import NodeSettings from '@/components/NodeSettings.vue';
@@ -197,7 +195,6 @@ export default mixins(
 		name: 'NodeView',
 		components: {
 			DataDisplay,
-			Modals,
 			Node,
 			NodeCreator,
 			NodeSettings,
@@ -242,20 +239,27 @@ export default mixins(
 		async beforeRouteLeave(to, from, next) {
 			const result = this.$store.getters.getStateIsDirty;
 			if(result) {
-				const importConfirm = await this.confirmMessage(
+				const confirmModal = await this.confirmModal(
 					this.$locale.baseText('nodeView.confirmMessage.beforeRouteLeave.message'),
 					this.$locale.baseText('nodeView.confirmMessage.beforeRouteLeave.headline'),
 					'warning',
 					this.$locale.baseText('nodeView.confirmMessage.beforeRouteLeave.confirmButtonText'),
 					this.$locale.baseText('nodeView.confirmMessage.beforeRouteLeave.cancelButtonText'),
+					true,
 				);
-				if (importConfirm === false) {
-					next(false);
-				} else {
-					// Prevent other popups from displaying
+
+				if (confirmModal === MODAL_CONFIRMED) {
+					const saved = await this.saveCurrentWorkflow({}, false);
+					if (saved) this.$store.dispatch('settings/fetchPromptsData');
 					this.$store.commit('setStateDirty', false);
 					next();
+				} else if (confirmModal === MODAL_CANCEL) {
+					this.$store.commit('setStateDirty', false);
+					next();
+				} else if (confirmModal === MODAL_CLOSE) {
+					next(false);
 				}
+
 			} else {
 				next();
 			}
@@ -347,6 +351,7 @@ export default mixins(
 			};
 		},
 		beforeDestroy () {
+			this.resetWorkspace();
 			// Make sure the event listeners get removed again else we
 			// could add up with them registred multiple times
 			document.removeEventListener('keydown', this.keyDown);
@@ -530,7 +535,7 @@ export default mixins(
 				let data: IWorkflowTemplate | undefined;
 				try {
 					this.$externalHooks().run('template.requested', { templateId });
-					data = await this.$store.dispatch('workflows/getWorkflowTemplate', templateId);
+					data = await this.$store.dispatch('templates/getWorkflowTemplate', templateId);
 
 					if (!data) {
 						throw new Error(
@@ -540,22 +545,16 @@ export default mixins(
 							),
 						);
 					}
-
-					data.workflow.nodes.forEach((node) => {
-						if (!this.$store.getters.nodeType(node.type, node.typeVersion)) {
-							throw new Error(`The ${this.$locale.shortNodeType(node.type)} node is not supported`);
-						}
-					});
 				} catch (error) {
 					this.$showError(error, this.$locale.baseText('nodeView.couldntImportWorkflow'));
-					this.$router.push({ name: 'NodeViewNew' });
+					this.$router.replace({ name: 'NodeViewNew' });
 					return;
 				}
 
 				data.workflow.nodes = CanvasHelpers.getFixedNodesList(data.workflow.nodes);
 
 				this.blankRedirect = true;
-				this.$router.push({ name: 'NodeViewNew', query: { templateId } });
+				this.$router.replace({ name: 'NodeViewNew', query: { templateId } });
 
 				await this.addNodes(data.workflow.nodes, data.workflow.connections);
 				await this.$store.dispatch('workflows/setNewWorkflowName', data.name);
@@ -955,8 +954,11 @@ export default mixins(
 			},
 
 			cutSelectedNodes () {
-				this.copySelectedNodes(true);
-				this.deleteSelectedNodes();
+				const deleteCopiedNodes = !this.isReadOnly;
+				this.copySelectedNodes(deleteCopiedNodes);
+				if (deleteCopiedNodes) {
+					this.deleteSelectedNodes();
+				}
 			},
 
 			copySelectedNodes (isCut: boolean) {
@@ -1003,6 +1005,9 @@ export default mixins(
 			setZoomLevel (zoomLevel: number) {
 				this.nodeViewScale = zoomLevel; // important for background
 				const element = this.instance.getContainer() as HTMLElement;
+				if (!element) {
+					return;
+				}
 
 				// https://docs.jsplumbtoolkit.com/community/current/articles/zooming.html
 				const prependProperties = ['webkit', 'moz', 'ms', 'o'];
@@ -1789,14 +1794,19 @@ export default mixins(
 
 					const result = this.$store.getters.getStateIsDirty;
 					if(result) {
-						const importConfirm = await this.confirmMessage(
+						const confirmModal = await this.confirmModal(
 							this.$locale.baseText('nodeView.confirmMessage.initView.message'),
 							this.$locale.baseText('nodeView.confirmMessage.initView.headline'),
 							'warning',
 							this.$locale.baseText('nodeView.confirmMessage.initView.confirmButtonText'),
 							this.$locale.baseText('nodeView.confirmMessage.initView.cancelButtonText'),
+							true,
 						);
-						if (importConfirm === false) {
+
+						if (confirmModal === MODAL_CONFIRMED) {
+							const saved = await this.saveCurrentWorkflow();
+							if (saved) this.$store.dispatch('settings/fetchPromptsData');
+						} else if (confirmModal === MODAL_CLOSE) {
 							return Promise.resolve();
 						}
 					}
@@ -2259,11 +2269,13 @@ export default mixins(
 			deleteEveryEndpoint () {
 				// Check as it does not exist on first load
 				if (this.instance) {
-					const nodes = this.$store.getters.allNodes as INodeUi[];
-					// @ts-ignore
-					nodes.forEach((node: INodeUi) => this.instance.destroyDraggable(`${NODE_NAME_PREFIX}${this.$store.getters.getNodeIndex(node.name)}`));
+					try {
+						const nodes = this.$store.getters.allNodes as INodeUi[];
+						// @ts-ignore
+						nodes.forEach((node: INodeUi) => this.instance.destroyDraggable(`${NODE_NAME_PREFIX}${this.$store.getters.getNodeIndex(node.name)}`));
 
-					this.instance.deleteEveryEndpoint();
+						this.instance.deleteEveryEndpoint();
+					} catch (e) {}
 				}
 			},
 			matchCredentials(node: INodeUi) {
@@ -2377,7 +2389,11 @@ export default mixins(
 					for (const sourceNode of Object.keys(connections)) {
 						for (const type of Object.keys(connections[sourceNode])) {
 							for (let sourceIndex = 0; sourceIndex < connections[sourceNode][type].length; sourceIndex++) {
-								connections[sourceNode][type][sourceIndex].forEach((
+								const outwardConnections = connections[sourceNode][type][sourceIndex];
+								if (!outwardConnections) {
+									continue;
+								}
+								outwardConnections.forEach((
 									targetData,
 								) => {
 									connectionData = [
@@ -2629,9 +2645,6 @@ export default mixins(
 				const activeWorkflows = await this.restApi().getActiveWorkflows();
 				this.$store.commit('setActiveWorkflows', activeWorkflows);
 			},
-			async loadSettings (): Promise<void> {
-				await this.$store.dispatch('settings/getSettings');
-			},
 			async loadNodeTypes (): Promise<void> {
 				const nodeTypes = await this.restApi().getNodeTypes();
 				this.$store.commit('setNodeTypes', nodeTypes);
@@ -2676,11 +2689,7 @@ export default mixins(
 					this.stopLoading();
 				}
 			},
-		},
-
-
-		async mounted () {
-			window.addEventListener('message', async (message) => {
+			async onPostMessageReceived(message: MessageEvent) {
 				try {
 					const json = JSON.parse(message.data);
 					if (json && json.command === 'openWorkflow') {
@@ -2699,20 +2708,24 @@ export default mixins(
 					}
 				} catch (e) {
 				}
-			});
-
-			this.$root.$on('importWorkflowData', async (data: IDataObject) => {
+			},
+			async onImportWorkflowDataEvent(data: IDataObject) {
 				await this.importWorkflowData(data.data as IWorkflowDataUpdate);
-			});
-
-			this.$root.$on('newWorkflow', this.newWorkflow);
-
-			this.$root.$on('importWorkflowUrl', async (data: IDataObject) => {
+			},
+			async onImportWorkflowUrlEvent(data: IDataObject) {
 				const workflowData = await this.getWorkflowDataFromUrl(data.url as string);
 				if (workflowData !== undefined) {
 					await this.importWorkflowData(workflowData);
 				}
-			});
+			},
+		},
+
+		async mounted () {
+			this.$titleReset();
+			window.addEventListener('message', this.onPostMessageReceived);
+			this.$root.$on('importWorkflowData', this.onImportWorkflowDataEvent);
+			this.$root.$on('newWorkflow', this.newWorkflow);
+			this.$root.$on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
 
 			this.startLoading();
 
@@ -2721,7 +2734,6 @@ export default mixins(
 				this.loadCredentials(),
 				this.loadCredentialTypes(),
 				this.loadNodeTypes(),
-				this.loadSettings(),
 			];
 
 			try {
@@ -2770,6 +2782,11 @@ export default mixins(
 
 		destroyed () {
 			this.resetWorkspace();
+			this.$store.commit('setStateDirty', false);
+			window.removeEventListener('message', this.onPostMessageReceived);
+			this.$root.$off('newWorkflow', this.newWorkflow);
+			this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent);
+			this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
 		},
 	});
 </script>
@@ -2841,6 +2858,7 @@ export default mixins(
 }
 
 .node-view-background {
+	background-color: var(--color-canvas-background);
 	position: absolute;
 	width: 10000px;
 	height: 10000px;
diff --git a/packages/editor-ui/src/views/TemplatesCollectionView.vue b/packages/editor-ui/src/views/TemplatesCollectionView.vue
new file mode 100644
index 0000000000..e1c71f6616
--- /dev/null
+++ b/packages/editor-ui/src/views/TemplatesCollectionView.vue
@@ -0,0 +1,193 @@
+<template>
+	<TemplatesView :goBackEnabled="true">
+		<template v-slot:header>
+			<div v-if="!notFoundError" :class="$style.wrapper">
+				<div :class="$style.title">
+					<n8n-heading v-if="collection && collection.name" tag="h1" size="2xlarge">
+						{{ collection.name }}
+					</n8n-heading>
+					<n8n-text v-if="collection && collection.name" color="text-base" size="small">
+						{{ $locale.baseText('templates.collection') }}
+					</n8n-text>
+					<n8n-loading :loading="!collection || !collection.name" :rows="2" variant="h1" />
+				</div>
+			</div>
+			<div :class="$style.notFound" v-else>
+				<n8n-text color="text-base">{{ $locale.baseText('templates.collectionsNotFound') }}</n8n-text>
+			</div>
+		</template>
+		<template v-if="!notFoundError" v-slot:content>
+			<div :class="$style.wrapper">
+				<div :class="$style.mainContent">
+					<div :class="$style.markdown" v-if="loading || (collection && collection.description)">
+						<n8n-markdown
+							:content="collection && collection.description"
+							:images="collection && collection.image"
+							:loading="loading"
+						/>
+					</div>
+					<TemplateList
+						:infinite-scroll-enabled="false"
+						:loading="loading"
+						:use-workflow-button="true"
+						:workflows="loading ? [] : collectionWorkflows"
+						@useWorkflow="onUseWorkflow"
+						@openTemplate="onOpenTemplate"
+					/>
+				</div>
+				<div :class="$style.details">
+					<TemplateDetails
+						:block-title="$locale.baseText('template.details.appsInTheCollection')"
+						:loading="loading"
+						:template="collection"
+					/>
+				</div>
+			</div>
+		</template>
+	</TemplatesView>
+</template>
+
+<script lang="ts">
+import TemplateDetails from '@/components/TemplateDetails.vue';
+import TemplateList from '@/components/TemplateList.vue';
+import TemplatesView from './TemplatesView.vue';
+
+import { workflowHelpers } from '@/components/mixins/workflowHelpers';
+import {
+	ITemplatesCollection,
+	ITemplatesCollectionFull,
+	ITemplatesWorkflow,
+	ITemplatesWorkflowFull,
+} from '@/Interface';
+
+import mixins from 'vue-typed-mixins';
+import { setPageTitle } from '@/components/helpers';
+
+export default mixins(workflowHelpers).extend({
+	name: 'TemplatesCollectionView',
+	components: {
+		TemplateDetails,
+		TemplateList,
+		TemplatesView,
+	},
+	computed: {
+		collection(): null | ITemplatesCollection | ITemplatesCollectionFull {
+			return this.$store.getters['templates/getCollectionById'](this.collectionId);
+		},
+		collectionId(): string {
+			return this.$route.params.id;
+		},
+		collectionWorkflows(): Array<ITemplatesWorkflow | ITemplatesWorkflowFull> | null {
+			if (!this.collection) {
+				return null;
+			}
+			return this.collection.workflows.map(({ id }) => {
+				return this.$store.getters['templates/getTemplateById'](id) as ITemplatesWorkflow;
+			});
+		},
+	},
+	data() {
+		return {
+			loading: true,
+			notFoundError: false,
+		};
+	},
+	methods: {
+		scrollToTop() {
+			setTimeout(() => {
+				window.scrollTo({
+					top: 0,
+					behavior: 'smooth',
+				});
+			}, 50);
+		},
+		onOpenTemplate({event, id}: {event: MouseEvent, id: string}) {
+			this.navigateTo(event, 'TemplatesWorkflowView', id);
+		},
+		onUseWorkflow({event, id}: {event: MouseEvent, id: string}) {
+			this.$telemetry.track('User inserted workflow template', {
+				template_id: id,
+				wf_template_repo_session_id: this.$store.getters['templates/currentSessionId'],
+				source: 'collection',
+			});
+
+			this.navigateTo(event, 'WorkflowTemplate', id);
+		},
+		navigateTo(e: MouseEvent, page: string, id: string) {
+			if (e.metaKey || e.ctrlKey) {
+				const route = this.$router.resolve({ name: page, params: { id } });
+				window.open(route.href, '_blank');
+				return;
+			} else {
+				this.$router.push({ name: page, params: { id } });
+			}
+		},
+	},
+	watch: {
+		collection(collection: ITemplatesCollection) {
+			if (collection) {
+				setPageTitle(`n8n - Template collection: ${collection.name}`);
+			}
+			else {
+				setPageTitle(`n8n - Templates`);
+			}
+		},
+	},
+	async mounted() {
+		this.scrollToTop();
+
+		if (this.collection && (this.collection as ITemplatesCollectionFull).full) {
+			this.loading = false;
+			return;
+		}
+
+		try {
+			await this.$store.dispatch('templates/getCollectionById', this.collectionId);
+		} catch (e) {
+			this.notFoundError = true;
+		}
+		this.loading = false;
+	},
+});
+</script>
+
+<style lang="scss" module>
+.wrapper {
+	display: flex;
+	justify-content: space-between;
+
+	@media (max-width: $--breakpoint-xs) {
+		display: block;
+	}
+}
+
+.notFound {
+	padding-top: var(--spacing-xl);
+}
+
+.title {
+	width: 100%;
+}
+
+.button {
+	display: block;
+}
+
+.mainContent {
+	padding-right: var(--spacing-2xl);
+	margin-bottom: var(--spacing-l);
+	width: 100%;
+
+	@media (max-width: $--breakpoint-xs) {
+		padding-right: 0;
+	}
+}
+
+.markdown {
+	margin-bottom: var(--spacing-l);
+}
+
+.details {
+	width: 180px;
+}
+</style>
diff --git a/packages/editor-ui/src/views/TemplatesSearchView.vue b/packages/editor-ui/src/views/TemplatesSearchView.vue
new file mode 100644
index 0000000000..57fb46da6e
--- /dev/null
+++ b/packages/editor-ui/src/views/TemplatesSearchView.vue
@@ -0,0 +1,396 @@
+<template>
+	<TemplatesView>
+		<template v-slot:header>
+			<div :class="$style.wrapper">
+				<div :class="$style.title">
+					<n8n-heading tag="h1" size="2xlarge">
+						{{ $locale.baseText('templates.heading') }}
+					</n8n-heading>
+				</div>
+				<div :class="$style.button">
+					<n8n-button
+						size="medium"
+						:label="$locale.baseText('templates.newButton')"
+						@click="openNewWorkflow"
+					/>
+				</div>
+			</div>
+		</template>
+		<template v-slot:content>
+			<div :class="$style.contentWrapper">
+				<div :class="$style.filters">
+					<TemplateFilters
+						:categories="allCategories"
+						:sortOnPopulate="areCategoriesPrepopulated"
+						:loading="loadingCategories"
+						:selected="categories"
+						@clear="onCategoryUnselected"
+						@clearAll="onCategoriesCleared"
+						@select="onCategorySelected"
+					/>
+				</div>
+				<div :class="$style.search">
+					<n8n-input
+						:value="search"
+						:placeholder="$locale.baseText('templates.searchPlaceholder')"
+						@input="onSearchInput"
+						@blur="trackSearch"
+						clearable
+					>
+						<font-awesome-icon icon="search" slot="prefix" />
+					</n8n-input>
+					<div :class="$style.carouselContainer" v-show="collections.length || loadingCollections">
+						<div :class="$style.header">
+							<n8n-heading :bold="true" size="medium" color="text-light">
+								{{ $locale.baseText('templates.collections') }}
+								<span v-if="!loadingCollections" v-text="`(${collections.length})`" />
+							</n8n-heading>
+						</div>
+
+						<CollectionsCarousel
+							:collections="collections"
+							:loading="loadingCollections"
+							@openCollection="onOpenCollection"
+						/>
+					</div>
+					<TemplateList
+						:infinite-scroll-enabled="true"
+						:loading="loadingWorkflows"
+						:total-workflows="totalWorkflows"
+						:workflows="workflows"
+						@loadMore="onLoadMore"
+						@openTemplate="onOpenTemplate"
+					/>
+					<div v-if="endOfSearchMessage" :class="$style.endText">
+						<n8n-text size="medium" color="text-base">
+							<span v-html="endOfSearchMessage" />
+						</n8n-text>
+					</div>
+				</div>
+			</div>
+		</template>
+	</TemplatesView>
+</template>
+
+<script lang="ts">
+import CollectionsCarousel from '@/components/CollectionsCarousel.vue';
+import TemplateFilters from '@/components/TemplateFilters.vue';
+import TemplateList from '@/components/TemplateList.vue';
+import TemplatesView from './TemplatesView.vue';
+
+import { genericHelpers } from '@/components/mixins/genericHelpers';
+import { ITemplatesCollection, ITemplatesWorkflow, ITemplatesQuery, ITemplatesCategory } from '@/Interface';
+import mixins from 'vue-typed-mixins';
+import { mapGetters } from 'vuex';
+import { IDataObject } from 'n8n-workflow';
+import { setPageTitle } from '@/components/helpers';
+
+interface ISearchEvent {
+	search_string: string;
+	workflow_results_count: number;
+	collection_results_count: number;
+	categories_applied: ITemplatesCategory[];
+	wf_template_repo_session_id: number;
+}
+
+export default mixins(genericHelpers).extend({
+	name: 'TemplatesSearchView',
+	components: {
+		CollectionsCarousel,
+		TemplateFilters,
+		TemplateList,
+		TemplatesView,
+	},
+	computed: {
+		...mapGetters('templates', ['allCategories', 'getSearchedWorkflowsTotal', 'getSearchedWorkflows', 'getSearchedCollections']),
+		...mapGetters('settings', ['isTemplatesEndpointReachable']),
+		collections(): ITemplatesCollection[] {
+			return this.getSearchedCollections(this.query) || [];
+		},
+		endOfSearchMessage(): string | null {
+			if (this.loadingWorkflows) {
+				return null;
+			}
+			if (this.workflows.length && this.workflows.length >= this.totalWorkflows) {
+				return this.$locale.baseText('templates.endResult');
+			}
+			if (!this.loadingCollections && this.workflows.length === 0 && this.collections.length === 0) {
+				if (!this.isTemplatesEndpointReachable && this.errorLoadingWorkflows) {
+					return this.$locale.baseText('templates.connectionWarning');
+				}
+				return this.$locale.baseText('templates.noSearchResults');
+			}
+
+			return null;
+		},
+		query(): ITemplatesQuery {
+			return {
+				categories: this.categories,
+				search: this.search,
+			};
+		},
+		nothingFound(): boolean {
+			return (
+				!this.loadingWorkflows &&
+				!this.loadingCollections &&
+				this.workflows.length === 0 &&
+				this.collections.length === 0
+			);
+		},
+		totalWorkflows(): number {
+			return this.getSearchedWorkflowsTotal(this.query);
+		},
+		workflows(): ITemplatesWorkflow[] {
+			return this.getSearchedWorkflows(this.query) || [];
+		},
+	},
+	data() {
+		return {
+			areCategoriesPrepopulated: false,
+			categories: [] as number[],
+			loading: true,
+			loadingCategories: true,
+			loadingCollections: true,
+			loadingWorkflows: true,
+			search: '',
+			searchEventToTrack: null as null | ISearchEvent,
+			errorLoadingWorkflows: false,
+		};
+	},
+	methods: {
+		onOpenCollection({event, id}: {event: MouseEvent, id: string}) {
+			this.navigateTo(event, 'TemplatesCollectionView', id);
+		},
+		onOpenTemplate({event, id}: {event: MouseEvent, id: string}) {
+			this.navigateTo(event, 'TemplatesWorkflowView', id);
+		},
+		navigateTo(e: MouseEvent, page: string, id: string) {
+			if (e.metaKey || e.ctrlKey) {
+				const route = this.$router.resolve({ name: page, params: { id } });
+				window.open(route.href, '_blank');
+				return;
+			} else {
+				this.$router.push({ name: page, params: { id } });
+			}
+		},
+		updateSearch() {
+			this.updateQueryParam(this.search, this.categories.join(','));
+			this.loadWorkflowsAndCollections(false);
+		},
+		updateSearchTracking(search: string, categories: number[]) {
+			if (!search) {
+				return;
+			}
+			if (this.searchEventToTrack && this.searchEventToTrack.search_string.length > search.length) {
+				return;
+			}
+
+			this.searchEventToTrack = {
+				search_string: search,
+				workflow_results_count: this.getSearchedWorkflowsTotal({search, categories}),
+				collection_results_count: this.getSearchedCollections({search, categories}).length,
+				categories_applied: categories.map((categoryId) =>
+					this.$store.getters['templates/getCategoryById'](categoryId),
+				) as ITemplatesCategory[],
+				wf_template_repo_session_id: this.$store.getters['templates/currentSessionId'],
+			};
+		},
+		trackSearch() {
+			if (this.searchEventToTrack) {
+				this.$telemetry.track('User searched workflow templates', this.searchEventToTrack as unknown as IDataObject);
+				this.searchEventToTrack = null;
+			}
+		},
+		openNewWorkflow() {
+			this.$router.push({ name: 'NodeViewNew' });
+		},
+		onSearchInput(search: string) {
+			this.loadingWorkflows = true;
+			this.loadingCollections = true;
+			this.search = search;
+			this.callDebounced('updateSearch', 500, true);
+
+			if (search.length === 0) {
+				this.trackSearch();
+			}
+		},
+		onCategorySelected(selected: number) {
+			this.categories = this.categories.concat(selected);
+			this.updateSearch();
+			this.trackCategories();
+		},
+		onCategoryUnselected(selected: number) {
+			this.categories = this.categories.filter((id) => id !== selected);
+			this.updateSearch();
+			this.trackCategories();
+		},
+		onCategoriesCleared() {
+			this.categories = [];
+			this.updateSearch();
+		},
+		trackCategories() {
+			if (this.categories.length) {
+				this.$telemetry.track('User changed template filters', {
+					search_string: this.search,
+					categories_applied: this.categories.map((categoryId: number) =>
+						this.$store.getters['templates/getCategoryById'](categoryId),
+					),
+					wf_template_repo_session_id: this.$store.getters['templates/currentSessionId'],
+				});
+			}
+		},
+		updateQueryParam(search: string, category: string) {
+			const query = Object.assign({}, this.$route.query);
+
+			if (category.length) {
+				query.categories = category;
+			} else {
+				delete query.categories;
+			}
+
+			if (search.length) {
+				query.search = search;
+			} else {
+				delete query.search;
+			}
+
+			this.$router.replace({ query });
+		},
+		async onLoadMore() {
+			if (this.workflows.length >= this.totalWorkflows) {
+				return;
+			}
+			try {
+				this.loadingWorkflows = true;
+				await this.$store.dispatch('templates/getMoreWorkflows', {
+					categories: this.categories,
+					search: this.search,
+				});
+			} catch (e) {
+				this.$showMessage({
+					title: 'Error',
+					message: 'Could not load more workflows',
+					type: 'error',
+				});
+			} finally {
+				this.loadingWorkflows = false;
+			}
+		},
+		async loadCategories() {
+			try {
+				await this.$store.dispatch('templates/getCategories');
+			} catch (e) {
+			}
+
+			this.loadingCategories = false;
+		},
+		async loadCollections() {
+			try {
+				this.loadingCollections = true;
+				await this.$store.dispatch('templates/getCollections', {
+					categories: this.categories,
+					search: this.search,
+				});
+			} catch (e) {
+			}
+
+			this.loadingCollections = false;
+		},
+		async loadWorkflows() {
+			try {
+				this.loadingWorkflows = true;
+				await this.$store.dispatch('templates/getWorkflows', {
+					search: this.search,
+					categories: this.categories,
+				});
+				this.errorLoadingWorkflows = false;
+			} catch (e) {
+				this.errorLoadingWorkflows = true;
+			}
+
+			this.loadingWorkflows = false;
+		},
+		async loadWorkflowsAndCollections(initialLoad: boolean) {
+			const search = this.search;
+			const categories = [...this.categories];
+			await Promise.all([this.loadWorkflows(), this.loadCollections()]);
+			if (!initialLoad) {
+				this.updateSearchTracking(search, categories);
+			}
+		},
+		scrollToTop() {
+			setTimeout(() => {
+				window.scrollTo({
+					top: 0,
+					behavior: 'smooth',
+				});
+			}, 100);
+		},
+	},
+	watch: {
+		workflows(newWorkflows) {
+			if (newWorkflows.length === 0) {
+				this.scrollToTop();
+			}
+		},
+	},
+	beforeRouteLeave(to, from, next) {
+		this.trackSearch();
+		next();
+	},
+	async mounted() {
+		setPageTitle('n8n - Templates');
+		this.loadCategories();
+		this.loadWorkflowsAndCollections(true);
+	},
+	async created() {
+		if (this.$route.query.search && typeof this.$route.query.search === 'string') {
+			this.search = this.$route.query.search;
+		}
+
+		if (typeof this.$route.query.categories === 'string' && this.$route.query.categories.length) {
+			this.categories = this.$route.query.categories.split(',').map((categoryId) => parseInt(categoryId, 10));
+			this.areCategoriesPrepopulated = true;
+		}
+	},
+});
+</script>
+
+<style lang="scss" module>
+.wrapper {
+	display: flex;
+	justify-content: space-between;
+}
+
+.contentWrapper {
+	display: flex;
+	justify-content: space-between;
+
+	@media (max-width: $--breakpoint-xs) {
+		flex-direction: column;
+	}
+}
+
+.filters {
+	width: 200px;
+	margin-bottom: var(--spacing-xl);
+}
+
+.search {
+	width: 100%;
+	padding-left: var(--spacing-2xl);
+
+	> * {
+		margin-bottom: var(--spacing-l);
+	}
+
+	@media (max-width: $--breakpoint-xs) {
+		padding-left: 0;
+	}
+}
+
+.header {
+	margin-bottom: var(--spacing-2xs);
+}
+
+</style>
diff --git a/packages/editor-ui/src/views/TemplatesView.vue b/packages/editor-ui/src/views/TemplatesView.vue
new file mode 100644
index 0000000000..9a3a830065
--- /dev/null
+++ b/packages/editor-ui/src/views/TemplatesView.vue
@@ -0,0 +1,81 @@
+<template>
+	<div :class="$style.template">
+		<div :class="isMenuCollapsed ? $style.menu : $style.expandedMenu"></div>
+		<div :class="$style.container">
+			<div :class="$style.header">
+				<div :class="$style.goBack" v-if="goBackEnabled">
+					<GoBackButton />
+				</div>
+				<slot name="header"></slot>
+			</div>
+			<div>
+				<slot name="content"></slot>
+			</div>
+		</div>
+	</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import GoBackButton from '@/components/GoBackButton.vue';
+
+export default Vue.extend({
+	name: 'TemplatesView',
+	components: {
+		GoBackButton,
+	},
+	props: {
+		goBackEnabled: {
+			type: Boolean,
+			default: false,
+		},
+	},
+	computed: {
+		isMenuCollapsed(): boolean {
+			return this.$store.getters['ui/sidebarMenuCollapsed'];
+		},
+	},
+});
+</script>
+
+<style lang="scss" module>
+.mockMenu {
+	height: 100%;
+	min-height: 100vh;
+}
+
+.menu {
+	composes: mockMenu;
+	min-width: $--sidebar-width;
+}
+
+.expandedMenu {
+	composes: mockMenu;
+	min-width: $--sidebar-expanded-width;
+}
+
+.template {
+	display: flex;
+}
+
+.container {
+	width: 100%;
+	max-width: 1024px;
+	padding: var(--spacing-3xl) var(--spacing-3xl) var(--spacing-4xl) var(--spacing-3xl);
+	margin: 0 auto;
+
+	@media (max-width: $--breakpoint-md) {
+		width: 900px;
+	}
+}
+
+.header {
+	display: flex;
+	flex-direction: column;
+	margin-bottom: var(--spacing-2xl);
+}
+
+.goBack {
+	margin-bottom: var(--spacing-2xs);
+}
+</style>
diff --git a/packages/editor-ui/src/views/TemplatesWorkflowView.vue b/packages/editor-ui/src/views/TemplatesWorkflowView.vue
new file mode 100644
index 0000000000..765de0f8a0
--- /dev/null
+++ b/packages/editor-ui/src/views/TemplatesWorkflowView.vue
@@ -0,0 +1,194 @@
+<template>
+	<TemplatesView :goBackEnabled="true">
+		<template v-slot:header>
+			<div v-if="!notFoundError" :class="$style.wrapper">
+				<div :class="$style.title">
+					<n8n-heading v-if="template && template.name" tag="h1" size="2xlarge">{{
+						template.name
+					}}</n8n-heading>
+					<n8n-text v-if="template && template.name" color="text-base" size="small">
+						{{ $locale.baseText('templates.workflow') }}
+					</n8n-text>
+					<n8n-loading :loading="!template || !template.name" :rows="2" variant="h1" />
+				</div>
+				<div :class="$style.button">
+					<n8n-button
+						v-if="template"
+						:label="$locale.baseText('template.buttons.useThisWorkflowButton')"
+						size="large"
+						@click="navigateTo(template.id, 'WorkflowTemplate', $event)"
+					/>
+					<n8n-loading :loading="!template" :rows="1" variant="button" />
+				</div>
+			</div>
+			<div :class="$style.notFound" v-else>
+				<n8n-text color="text-base">{{ $locale.baseText('templates.workflowsNotFound') }}</n8n-text>
+			</div>
+		</template>
+		<template v-if="!notFoundError" v-slot:content>
+			<div :class="$style.image">
+				<WorkflowPreview
+					v-if="showPreview"
+					:loading="loading"
+					:workflow="template && template.workflow"
+					@close="onHidePreview"
+				/>
+			</div>
+			<div :class="$style.content">
+				<div :class="$style.markdown">
+					<n8n-markdown
+						:content="template && template.description"
+						:images="template && template.image"
+						:loading="loading"
+					/>
+				</div>
+				<div :class="$style.details">
+					<TemplateDetails
+						:block-title="$locale.baseText('template.details.appsInTheWorkflow')"
+						:loading="loading"
+						:template="template"
+					/>
+				</div>
+			</div>
+		</template>
+	</TemplatesView>
+</template>
+
+<script lang="ts">
+import TemplateDetails from '@/components/TemplateDetails.vue';
+import TemplatesView from './TemplatesView.vue';
+import WorkflowPreview from '@/components/WorkflowPreview.vue';
+
+import { ITemplatesWorkflow, ITemplatesWorkflowFull } from '@/Interface';
+import { workflowHelpers } from '@/components/mixins/workflowHelpers';
+import mixins from 'vue-typed-mixins';
+import { setPageTitle } from '@/components/helpers';
+
+export default mixins(workflowHelpers).extend({
+	name: 'TemplatesWorkflowView',
+	components: {
+		TemplateDetails,
+		TemplatesView,
+		WorkflowPreview,
+	},
+	computed: {
+		template(): ITemplatesWorkflow | ITemplatesWorkflowFull {
+			return this.$store.getters['templates/getTemplateById'](this.templateId);
+		},
+		templateId() {
+			return this.$route.params.id;
+		},
+	},
+	data() {
+		return {
+			loading: true,
+			showPreview: true,
+			notFoundError: false,
+		};
+	},
+	methods: {
+		navigateTo(id: string, page: string, e: PointerEvent) {
+			if (page === 'WorkflowTemplate') {
+				this.$telemetry.track('User inserted workflow template', {
+					source: 'workflow',
+					template_id: id,
+					wf_template_repo_session_id: this.$store.getters['templates/currentSessionId'],
+				});
+			}
+
+			if (e.metaKey || e.ctrlKey) {
+				const route = this.$router.resolve({ name: page, params: { id } });
+				window.open(route.href, '_blank');
+				return;
+			} else {
+				this.$router.push({ name: page, params: { id } });
+			}
+		},
+		onHidePreview() {
+			this.showPreview = false;
+		},
+		scrollToTop() {
+			window.scrollTo({
+				top: 0,
+			});
+		},
+	},
+	watch: {
+		template(template: ITemplatesWorkflowFull) {
+			if (template) {
+				setPageTitle(`n8n - Template template: ${template.name}`);
+			}
+			else {
+				setPageTitle(`n8n - Templates`);
+			}
+		},
+	},
+	async mounted() {
+		this.scrollToTop();
+
+		if (this.template && (this.template as ITemplatesWorkflowFull).full) {
+			this.loading = false;
+			return;
+		}
+
+		try {
+			await this.$store.dispatch('templates/getTemplateById', this.templateId);
+		} catch (e) {
+			this.notFoundError = true;
+		}
+
+		this.loading = false;
+	},
+});
+</script>
+
+<style lang="scss" module>
+.wrapper {
+	display: flex;
+	justify-content: space-between;
+}
+
+.notFound {
+	padding-top: var(--spacing-xl);
+}
+
+.title {
+	width: 75%;
+}
+
+.button {
+	display: block;
+}
+
+.image {
+	width: 100%;
+
+	img {
+		width: 100%;
+	}
+}
+
+.content {
+	padding: var(--spacing-2xl) 0;
+	display: flex;
+	justify-content: space-between;
+
+	@media (max-width: $--breakpoint-xs) {
+		display: block;
+	}
+}
+
+.markdown {
+	width: calc(100% - 180px);
+	padding-right: var(--spacing-2xl);
+	margin-bottom: var(--spacing-l);
+
+	@media (max-width: $--breakpoint-xs) {
+		width: 100%;
+	}
+}
+
+.details {
+	width: 180px;
+}
+</style>
diff --git a/packages/editor-ui/src/vue-agile.d.ts b/packages/editor-ui/src/vue-agile.d.ts
new file mode 100644
index 0000000000..23b389c3eb
--- /dev/null
+++ b/packages/editor-ui/src/vue-agile.d.ts
@@ -0,0 +1 @@
+declare module 'vue-agile';
diff --git a/packages/editor-ui/vue.config.js b/packages/editor-ui/vue.config.js
index 35338dd497..b001f73168 100644
--- a/packages/editor-ui/vue.config.js
+++ b/packages/editor-ui/vue.config.js
@@ -1,4 +1,5 @@
 const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
+const webpack = require('webpack');
 
 module.exports = {
 	chainWebpack: config => {
@@ -26,6 +27,7 @@ module.exports = {
 		},
 		plugins: [
 			new MonacoWebpackPlugin({ languages: ['javascript', 'json', 'typescript'] }),
+			new webpack.NormalModuleReplacementPlugin(/element-ui[\/\\]lib[\/\\]locale[\/\\]lang[\/\\]zh-CN/, 'element-ui/lib/locale/lang/en'),
 		],
 	},
 	css: {