listeners.click && listeners.click(e)" />
+
+
+
+
+
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 0000000000000..5e8a0d45a6249
--- /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 0000000000000..4de11c649e686
--- /dev/null
+++ b/packages/design-system/src/components/N8nTags/Tags.stories.js
@@ -0,0 +1,24 @@
+import N8nTags from './Tags.vue';
+
+export default {
+ title: 'Atoms/Tags',
+ component: N8nTags,
+ argTypes: {
+ tags: {
+ control: {
+ type: 'array',
+ },
+ },
+ },
+};
+
+const Template = (args, { argTypes }) => ({
+ props: Object.keys(argTypes),
+ components: {
+ N8nTags,
+ },
+ template:
+ '
',
+});
+
+export const Tags = Template.bind({});
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 0000000000000..b4b9154e05667
--- /dev/null
+++ b/packages/design-system/src/components/N8nTags/Tags.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
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 0000000000000..ebf28f6470f1c
--- /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 a82ddc6e3f84b..8989827ea98ac 100644
--- a/packages/design-system/src/components/index.js
+++ b/packages/design-system/src/components/index.js
@@ -1,16 +1,21 @@
import N8nButton from './N8nButton';
import N8nIcon from './N8nIcon';
import N8nIconButton from './N8nIconButton';
+import N8nImage from './N8nImage';
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';
@@ -19,16 +24,21 @@ export {
N8nButton,
N8nIcon,
N8nIconButton,
+ N8nImage,
N8nInfoTip,
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 e59e86f062c80..645df50e69941 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 0000000000000..8d757580d3d5b
--- /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, ">");
+ // unescape greater than quotes at start of line
+ const withQuotes = escaped.replace(/^((\s)*(>)+)+\s*/gm, (matches) => {
+ return matches.replace(/>/g, '>');
+ });
+
+ return withQuotes;
+};
diff --git a/packages/design-system/theme/src/index.scss b/packages/design-system/theme/src/index.scss
index 353317e1c3d2e..953f306f1a2d3 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 0000000000000..f85d78e11d8dd
--- /dev/null
+++ b/packages/design-system/theme/src/skeleton.scss
@@ -0,0 +1,96 @@
+.el-skeleton {
+ width: 100%;
+}
+
+.el-skeleton__item {
+ width: 100%;
+ height: 16px;
+ border-radius: var(--border-radius-large);
+ background: #f2f2f2;
+ display: inline-block;
+}
+
+.el-skeleton__button {
+ width: 162px;
+ height: 40px;
+ border-radius: 20px;
+}
+
+.el-skeleton__p {
+ width: 100%;
+}
+
+.el-skeleton__h1 {
+ height: 20px;
+ margin-top: 14px;
+}
+
+.el-skeleton__h1--last {
+ width: 40%;
+ margin-top: 14px;
+}
+
+.el-skeleton__image {
+ width: unset;
+ height: 607px !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: #dcdde0;
+}
+
+.el-skeleton__first-line,
+.el-skeleton__paragraph {
+ height: 16px;
+ margin-top: 16px;
+ background: #f2f2f2;
+}
+
+.el-skeleton__paragraph.last {
+ width: 61%;
+}
+
+.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 6d0710be8097b..9888bbee15196 100644
--- a/packages/editor-ui/package.json
+++ b/packages/editor-ui/package.json
@@ -30,7 +30,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",
@@ -61,7 +62,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",
diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts
index a0ab00a93b774..8e5f9f00ca7d9 100644
--- a/packages/editor-ui/src/Interface.ts
+++ b/packages/editor-ui/src/Interface.ts
@@ -511,6 +511,29 @@ export interface IN8nPromptResponse {
updated: boolean;
}
+export interface IN8nTemplateResponse {
+ data: IN8nTemplateWorkflow;
+}
+
+export interface IN8nTemplateWorkflow {
+ workflow: IN8nTemplate;
+}
+
+export interface IN8nTemplate {
+ id: string;
+ categories: ITemplateCategories[];
+ createdAt: string;
+ description: string;
+ image: ITemplateImage[];
+ mainImage: ITemplateMainImage[];
+ name: string;
+ nodes: ITemplateNode[];
+ totalViews: number;
+ user: {
+ username: string;
+ };
+}
+
export interface IN8nUISettings {
endpointWebhook: string;
endpointWebhookTest: string;
@@ -545,6 +568,38 @@ export interface IWorkflowSettings extends IWorkflowSettingsWorkflow {
executionTimeout?: number;
}
+export interface ITemplateCategories {
+ id: string;
+ name: string;
+}
+
+export interface ITemplateImage {
+ id: string;
+ url: string;
+}
+
+export interface ITemplateMainImage {
+ url: string;
+ metadata: ITemplateMetadata;
+}
+
+export interface ITemplateMetadata {
+ width: string;
+}
+export interface ITemplateNode {
+ displayName: string;
+ defaults: {
+ color: string;
+ };
+ icon: string;
+ iconData?: {
+ fileBuffer?: string;
+ type?: string;
+ };
+ name: string;
+ typeVersion: number;
+}
+
export interface ITimeoutHMS {
hours: number;
minutes: number;
@@ -721,6 +776,10 @@ export interface ISettingsState {
promptsData: IN8nPrompts;
}
+export interface ITemplateState {
+ templates: IN8nTemplate[];
+}
+
export interface IVersionsState {
versionNotificationSettings: IVersionNotificationSettings;
nextVersions: IVersion[];
diff --git a/packages/editor-ui/src/api/templates-mock.ts b/packages/editor-ui/src/api/templates-mock.ts
new file mode 100644
index 0000000000000..e0c401c930ced
--- /dev/null
+++ b/packages/editor-ui/src/api/templates-mock.ts
@@ -0,0 +1,90 @@
+import { IN8nTemplateResponse } from '@/Interface';
+
+const response = {
+ data: {
+ workflow: {
+ id: '23',
+ name: 'New/updated employee in BambooHR -> Create/Update user in Okta and Slack to group',
+ description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris hendrerit iaculis ante quis pretium. Fusce eget lectus nec quam ornare finibus. Proin ut imperdiet velit. Donec at ligula mattis, sagittis elit sit amet, volutpat quam. Nunc vel congue quam. Vivamus feugiat libero tellus, quis iaculis justo mattis sit amet. Nam vitae nunc vel urna interdum pretium ut quis odio. Maecenas porta scelerisque dolor eleifend suscipit. Ut venenatis orci eget neque vestibulum, id elementum nisi congue. Nulla facilisi. Suspendisse vitae venenatis urna. Fusce viverra sapien nec diam tincidunt, id scelerisque turpis viverra. Morbi finibus consequat nisi maximus malesuada. Integer vel ultricies odio. Donec at ligula mattis, sagittis elit sit amet Vivamus feugiat libero tellus, quis iaculis justo mattis sit amet. Nam vitae nunc vel urna interdum pretium ut quis odio. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris hendrerit iaculis ante quis pretium. Fusce eget lectus nec quam ornare finibus. I made this workflow after discovering that n8n does not have a Todoist trigger. \n\nSteps:\n\n- Configure Todoist webhook by visiting their portal [https://developer.todoist.com/appconsole.html](https://developer.todoist.com/appconsole.html) and checking off the triggers which you want to use. I only used item:completed in my case, but yours could be different.\n- Use the test webhook URL from n8n to test your trigger.\n- Once testing is successful, register the production webhook URL from n8n into the todoist developer portal\n- Adjust the date & time to your liking and appropriate timezone\n- In my case I am sending the data to a Google Sheet but you can change that to whatever app you want to send it to.\n- Profit!\n\nFeel free to reach out to me on [https://twitter.com/sami_abid](https://twitter.com/sami_abid) if you have any questions\n\n\n\n\n**HTTP Request node:** This node fetches an image from Unsplash. Replace this node with any other node to fetch the image file.\n\n**HTTP Request1 node:** This node uploads the Twitter Profile Banner. The Twitter API requires OAuth 1.0 authentication. Follow the Twitter documentation to learn how to configure the authentication.\n\n**Cron node** schedules the workflow to run every minute.\n1. Copy workflow to your n8n\n2. Set telegram credentials and\n3. Copy workflow to your n8n\n2. Set telegram credentials and\n *Heading* \n ***Heading*** \n **h1Headingh1** \n ##Heading 2 \n ###Heading3 \n > Blockquote \n ~~Strikethrough~~ \n # 🛸 Technologies Used \n \n ## 🛸 Technologies Used \n ### 🛸 Technologies Used \n #### 🛸 Technologies Used \n ```bash npm install happiness ``` \n - [ ] Mercury \n - [x] Mercury',
+ image: [
+ {
+ id: '564',
+ url: 'https://f000.backblazeb2.com/file/n8n-website-images/ffa19945d89a4feca922859899d435cf.png',
+ },
+ ],
+ mainImage: [
+ {
+ url: 'https://f000.backblazeb2.com/file/n8n-website-images/ffa19945d89a4feca922859899d435cf.png',
+ metadata: {
+ width: '1000',
+ },
+ },
+ {
+ url: 'https://f000.backblazeb2.com/file/n8n-website-images/ffa19945d89a4feca922859899d435cf.png',
+ metadata: {
+ width: '600',
+ },
+ },
+ ],
+ nodes: [
+ {
+ defaults: {
+ color: '#000000',
+ },
+ displayName: 'Airtable',
+ icon: 'file:airtable.svg',
+ iconData: {
+ fileBuffer: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMDAgMTcwIj48cGF0aCBkPSJNODkgNC44TDE2LjIgMzQuOWMtNC4xIDEuNy00IDcuNC4xIDkuMWw3My4yIDI5YzYuNCAyLjYgMTMuNiAyLjYgMjAgMGw3My4yLTI5YzQuMS0xLjYgNC4xLTcuNC4xLTkuMWwtNzMtMzAuMUMxMDMuMiAyIDk1LjcgMiA4OSA0LjgiIGZpbGw9IiNmY2I0MDAiLz48cGF0aCBkPSJNMTA1LjkgODguOXY3Mi41YzAgMy40IDMuNSA1LjggNi43IDQuNWw4MS42LTMxLjdjMS45LS43IDMuMS0yLjUgMy4xLTQuNVY1Ny4yYzAtMy40LTMuNS01LjgtNi43LTQuNUwxMDkgODQuM2MtMS45LjgtMy4xIDIuNi0zLjEgNC42IiBmaWxsPSIjMThiZmZmIi8+PHBhdGggZD0iTTg2LjkgOTIuNmwtMjQuMiAxMS43LTIuNSAxLjJMOS4xIDEzMGMtMy4yIDEuNi03LjQtLjgtNy40LTQuNFY1Ny41YzAtMS4zLjctMi40IDEuNi0zLjMuNC0uNC44LS43IDEuMi0uOSAxLjItLjcgMy0uOSA0LjQtLjNsNzcuNSAzMC43YzQgMS41IDQuMyA3LjEuNSA4LjkiIGZpbGw9IiNmODJiNjAiLz48cGF0aCBkPSJNODYuOSA5Mi42bC0yNC4yIDExLjctNTkuNC01MGMuNC0uNC44LS43IDEuMi0uOSAxLjItLjcgMy0uOSA0LjQtLjNsNzcuNSAzMC43YzQgMS40IDQuMyA3IC41IDguOCIgZmlsbD0iI2JhMWU0NSIvPjwvc3ZnPg==',
+ type: 'file',
+ },
+ name: 'n8n-nodes-base.airtable',
+ typeVersion: 1,
+ },
+ {
+ defaults: {
+ color: '#305b94',
+ },
+ displayName: 'AWS Rekognition',
+ icon: `file:rekognition.svg`,
+ iconData: {
+ fileBuffer: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNzQuMzc1IDg1IiBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48dXNlIHhsaW5rOmhyZWY9IiNhIiB4PSIyLjE4OCIgeT0iMi41Ii8+PHN5bWJvbCBpZD0iYSIgb3ZlcmZsb3c9InZpc2libGUiPjxnIHN0cm9rZT0ibm9uZSI+PHBhdGggZD0iTTIuODg2IDUyLjhMMTYuOCA1MS4yNjhWMjguNzMyTDIuODg2IDI3LjJ2MjUuNnoiIGZpbGw9IiM1Mjk0Y2YiLz48ZyBmaWxsPSIjMTk0ODZmIj48cGF0aCBkPSJNNjcuMjA3IDI4Ljg5OGwtNi40NjIgMi40My0zNi4yMzctNS4zNDZMMzQuOTkgMGwzMi4yMTcgMjguODk4eiIvPjxwYXRoIGQ9Ik0zLjUwNCAyOC45NjZMMzUgMTIuMjM0IDQ1LjU0MyAyNiAxNi44MSAzMC4yMjRsLTEzLjMwNS0xLjI2eiIvPjwvZz48ZyBmaWxsPSIjMjA1Yjk5Ij48cGF0aCBkPSJNMzUgMjRMMCAzMC42MjRWMTYuNTU2TDM1IDBsMTcuMDE2IDE4LjQ3OEwzNSAyNHoiLz48cGF0aCBkPSJNNy4wMDggMTYuNDc4TDAgMTkuMzk1djQ0LjA1bDcuMDA4IDMuMzA3VjE2LjQ3OHoiLz48L2c+PHBhdGggZD0iTTcwIDE2LjU2NkwzNSAwdjI0bDM1IDYuNjI0di0xNC4wNnoiIGZpbGw9IiM1Mjk0Y2YiLz48ZyBmaWxsPSIjOTliY2UzIj48cGF0aCBkPSJNMS4xNTQgNTEuMjZMMzQuOTkgODBsMTAuNTU0LTI2LTI4LjczNC00LjIyNEwxLjE1NCA1MS4yNnoiLz48cGF0aCBkPSJNNjcuNjQgNTEuMTQybC02LjQ5My0yLjUyNy0zNi42NCA1LjQwNSAxMC40OCAyNS4yMiAzMi42NS0yOC4wOTd6Ii8+PC9nPjxwYXRoIGQ9Ik02Ny4yMDcgNTEuMTAzbC0xMy45NjUtMS4zMjd2LTE5LjU1TDY3LjIwNyAyOC45djIyLjIwNXpNMzUgNTZsMTUuMTMtMTZMMzUgMjQgMTYuMzU2IDQwIDM1IDU2eiIgZmlsbD0iIzIwNWI5OSIvPjxwYXRoIGQ9Ik01My42MjQgNDBMMzUgNTZWMjRsMTguNjM0IDE2eiIgZmlsbD0iIzUyOTRjZiIvPjxwYXRoIGQ9Ik0wIDQ5LjM3NkwzNSA1NmwxOS4yMSA3Ljg3M0wzNSA4MCAwIDYzLjQ0NFY0OS4zNzZ6IiBmaWxsPSIjMjA1Yjk5Ii8+PGcgZmlsbD0iIzUyOTRjZiI+PHBhdGggZD0iTTcwIDYzLjQzNUwzNSA4MFY1NmwzNS02LjYyNHYxNC4wNnoiLz48cGF0aCBkPSJNNjIuOTcgNjYuNzVMNzAgNjMuNDM0VjE2LjU2NmwtNy4wMy0zLjMyN1Y2Ni43NXoiLz48L2c+PC9nPjwvc3ltYm9sPjwvc3ZnPg==',
+ type: 'file',
+ },
+ name: 'n8n-nodes-base.awsRekognition',
+ typeVersion: 1,
+ },
+ ],
+ totalViews: 120000,
+ categories: [
+ {
+ id: '1',
+ name: 'Security Ops',
+ },
+ {
+ id: '2',
+ name: 'Building Blocks',
+ },
+ {
+ id: '3',
+ name: 'Building Blocks',
+ },
+ {
+ id: '4',
+ name: 'Building Blocks',
+ },
+ {
+ id: '5',
+ name: 'Building Blocks',
+ },
+ ],
+ user: {
+ username: 'someusername',
+ },
+ createdAt: '2021-12-15T11:56:35.412Z',
+ },
+ },
+};
+
+export async function getTemplateById(templateId: string): Promise
{
+ return response;
+}
diff --git a/packages/editor-ui/src/api/templates.ts b/packages/editor-ui/src/api/templates.ts
new file mode 100644
index 0000000000000..7706d3c320911
--- /dev/null
+++ b/packages/editor-ui/src/api/templates.ts
@@ -0,0 +1,37 @@
+import { IN8nTemplateResponse } from '@/Interface';
+import { post } from './helpers';
+import { TEMPLATES_BASE_URL } from '@/constants';
+
+export async function getTemplateById(templateId: string): Promise {
+ const query = `query {
+ workflow(id:"${templateId}"){
+ id
+ name
+ description
+ image{
+ id
+ url
+ }
+ mainImage
+ nodes{
+ defaults
+ name
+ displayName
+ icon
+ iconData
+ typeVersion: version
+ }
+ totalViews: views
+ categories{
+ id
+ name
+ }
+ user{
+ username
+ }
+ created_at
+ }
+ }`;
+
+ return await post(TEMPLATES_BASE_URL, `/graphql`, { query });
+}
diff --git a/packages/editor-ui/src/components/GoBackButton.vue b/packages/editor-ui/src/components/GoBackButton.vue
new file mode 100644
index 0000000000000..4f769efcd06f2
--- /dev/null
+++ b/packages/editor-ui/src/components/GoBackButton.vue
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
diff --git a/packages/editor-ui/src/components/TemplateDetails/NodeIcon/NodeIcon.vue b/packages/editor-ui/src/components/TemplateDetails/NodeIcon/NodeIcon.vue
new file mode 100644
index 0000000000000..18449f3bb04dc
--- /dev/null
+++ b/packages/editor-ui/src/components/TemplateDetails/NodeIcon/NodeIcon.vue
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+ {{nodeType !== null ? nodeType.displayName.charAt(0) : '?' }}
+
+
+
+
+
+
+
diff --git a/packages/editor-ui/src/components/TemplateDetails/TemplateBlock/TemplateBlock.vue b/packages/editor-ui/src/components/TemplateDetails/TemplateBlock/TemplateBlock.vue
new file mode 100644
index 0000000000000..dc24378bf2a38
--- /dev/null
+++ b/packages/editor-ui/src/components/TemplateDetails/TemplateBlock/TemplateBlock.vue
@@ -0,0 +1,36 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/editor-ui/src/components/TemplateDetails/TemplateDetails.vue b/packages/editor-ui/src/components/TemplateDetails/TemplateDetails.vue
new file mode 100644
index 0000000000000..6a05ff47e4b94
--- /dev/null
+++ b/packages/editor-ui/src/components/TemplateDetails/TemplateDetails.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $locale.baseText('template.details.created') }}
+
+ {{ $locale.baseText('template.details.by') }}
+ {{ template.user.username }}
+
+
+
+
+ {{ $locale.baseText('template.details.viewed') }}
+ {{ abbreviateNumber(template.totalViews) }}
+ {{ $locale.baseText('template.details.times') }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/editor-ui/src/components/helpers.ts b/packages/editor-ui/src/components/helpers.ts
index 506b3de6185a5..7b1d49709d375 100644
--- a/packages/editor-ui/src/components/helpers.ts
+++ b/packages/editor-ui/src/components/helpers.ts
@@ -1,6 +1,19 @@
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');
diff --git a/packages/editor-ui/src/constants.ts b/packages/editor-ui/src/constants.ts
index 9036c1cabcaad..aa85d538915f7 100644
--- a/packages/editor-ui/src/constants.ts
+++ b/packages/editor-ui/src/constants.ts
@@ -37,7 +37,7 @@ export const BREAKPOINT_XL = 1920;
// templates
-export const TEMPLATES_BASE_URL = `https://api.n8n.io/`;
+export const TEMPLATES_BASE_URL = `https://api-staging.n8n.io/`;
// node types
export const CALENDLY_TRIGGER_NODE_TYPE = 'n8n-nodes-base.calendlyTrigger';
diff --git a/packages/editor-ui/src/modules/templates.ts b/packages/editor-ui/src/modules/templates.ts
new file mode 100644
index 0000000000000..5f317930d07a6
--- /dev/null
+++ b/packages/editor-ui/src/modules/templates.ts
@@ -0,0 +1,42 @@
+import { getTemplateById } from '@/api/templates';
+import { ActionContext, Module } from 'vuex';
+import {
+ IRootState,
+ IN8nTemplateResponse,
+ IN8nTemplateWorkflow,
+ IN8nTemplate,
+ ITemplateState,
+} from '../Interface';
+
+const module: Module = {
+ namespaced: true,
+ state: {
+ templates: [],
+ },
+ getters: {
+ getTemplates(state: ITemplateState) {
+ return state.templates;
+ },
+ },
+ mutations: {
+ setTemplate(state: ITemplateState, template: IN8nTemplate) {
+ state.templates.push(template);
+ },
+ },
+ actions: {
+ async getTemplateById(context: ActionContext, templateId: string) {
+ try {
+ const response: IN8nTemplateResponse = await getTemplateById(templateId);
+ const data: IN8nTemplateWorkflow = response.data;
+ const template: IN8nTemplate = data.workflow;
+
+ context.commit('setTemplate', template);
+ return template;
+ } catch(e) {
+ return;
+ }
+ },
+ },
+};
+
+export default module;
diff --git a/packages/editor-ui/src/plugins/components.ts b/packages/editor-ui/src/plugins/components.ts
index 24856be949381..ea0c87adb280a 100644
--- a/packages/editor-ui/src/plugins/components.ts
+++ b/packages/editor-ui/src/plugins/components.ts
@@ -46,16 +46,21 @@ import locale from 'element-ui/lib/locale';
import {
N8nIconButton,
N8nButton,
+ N8nImage,
N8nInfoTip,
N8nInput,
N8nInputLabel,
N8nInputNumber,
+ N8nLoading,
N8nHeading,
+ N8nMarkdown,
N8nMenu,
N8nMenuItem,
N8nSelect,
N8nSpinner,
N8nSquareButton,
+ N8nTags,
+ N8nTag,
N8nText,
N8nTooltip,
N8nOption,
@@ -67,16 +72,21 @@ Vue.use(Fragment.Plugin);
// n8n design system
Vue.use(N8nButton);
Vue.use(N8nIconButton);
+Vue.use(N8nImage);
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);
diff --git a/packages/editor-ui/src/plugins/i18n/locales/en.json b/packages/editor-ui/src/plugins/i18n/locales/en.json
index dd979ce8e9339..35173a32c94ae 100644
--- a/packages/editor-ui/src/plugins/i18n/locales/en.json
+++ b/packages/editor-ui/src/plugins/i18n/locales/en.json
@@ -828,6 +828,21 @@
},
"notBeingUsed": "Not being used"
},
+ "template": {
+ "buttons": {
+ "goBackButton": "Go back",
+ "useThisWorkflowButton": "Use this workflow"
+ },
+ "details": {
+ "appsInTheWorkflow": "Apps in this workflow",
+ "by": "by",
+ "categories": "Categories",
+ "created": "Created",
+ "details": "Details",
+ "times": "times",
+ "viewed": "Viewed"
+ }
+ },
"textEdit": {
"edit": "Edit"
},
@@ -1039,4 +1054,4 @@
"timeoutWorkflow": "Timeout Workflow",
"timezone": "Timezone"
}
-}
\ No newline at end of file
+}
diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts
index a1f5e30e50dfe..b7cf7ba111312 100644
--- a/packages/editor-ui/src/router.ts
+++ b/packages/editor-ui/src/router.ts
@@ -3,6 +3,7 @@ import Router from 'vue-router';
import MainHeader from '@/components/MainHeader/MainHeader.vue';
import MainSidebar from '@/components/MainSidebar.vue';
import NodeView from '@/views/NodeView.vue';
+import TemplateView from '@/views/TemplateView.vue';
Vue.use(Router);
@@ -20,6 +21,14 @@ export default new Router({
sidebar: MainSidebar,
},
},
+ {
+ path: '/template/:id',
+ name: 'TemplateView',
+ components: {
+ default: TemplateView,
+ sidebar: MainSidebar,
+ },
+ },
{
path: '/workflow',
name: 'NodeViewNew',
diff --git a/packages/editor-ui/src/store.ts b/packages/editor-ui/src/store.ts
index 987ffa87699f8..a4eadc887f117 100644
--- a/packages/editor-ui/src/store.ts
+++ b/packages/editor-ui/src/store.ts
@@ -38,6 +38,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);
@@ -96,6 +97,7 @@ const modules = {
credentials,
tags,
settings,
+ templates,
workflows,
versions,
ui,
diff --git a/packages/editor-ui/src/views/TemplateView.vue b/packages/editor-ui/src/views/TemplateView.vue
new file mode 100644
index 0000000000000..629eca2e6819b
--- /dev/null
+++ b/packages/editor-ui/src/views/TemplateView.vue
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+
+ {{ template.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+