@raketa-cloud/admin 1.6.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/package.json +4 -4
  2. package/src/HonoAdapter.js +80 -39
  3. package/src/handlers/_shared/getArticlesUpdateDestination.js +7 -0
  4. package/src/handlers/_shared/getEventsUpdateDestination.js +7 -0
  5. package/src/handlers/_shared/getPagesUpdateDestination.js +8 -0
  6. package/src/handlers/article_revisions/CodeHandler.js +2 -1
  7. package/src/handlers/article_revisions/GeneralHandler.js +2 -1
  8. package/src/handlers/article_revisions/MetaHandler.js +2 -1
  9. package/src/handlers/article_revisions/TagHandler.js +2 -1
  10. package/src/handlers/article_revisions/VisualHandler.js +2 -1
  11. package/src/handlers/event_revisions/CodeHandler.js +2 -1
  12. package/src/handlers/event_revisions/GeneralHandler.js +2 -1
  13. package/src/handlers/event_revisions/MetaHandler.js +2 -1
  14. package/src/handlers/event_revisions/TagHandler.js +2 -1
  15. package/src/handlers/event_revisions/VisualHandler.js +2 -1
  16. package/src/handlers/page_revisions/CodeHandler.js +3 -1
  17. package/src/handlers/page_revisions/GeneralHandler.js +3 -1
  18. package/src/handlers/page_revisions/MetaHandler.js +3 -1
  19. package/src/handlers/page_revisions/TagHandler.js +3 -1
  20. package/src/handlers/page_revisions/VisualHandler.js +3 -1
  21. package/src/index.js +4 -0
  22. package/src/tests/HonoAdapter.test.js +89 -20
  23. package/src/tests/handlers/page_revisions/CodesHandler.test/update.test.js +50 -0
  24. package/src/tests/handlers/page_revisions/GeneralHandler.test/update.test.js +53 -0
  25. package/src/tests/handlers/page_revisions/MetaHandler.test/update.test.js +52 -0
  26. package/src/tests/handlers/page_revisions/TagHandler.test/update.test.js +43 -0
  27. package/src/tests/handlers/page_revisions/VisualHandler.test.test/update.test.js +44 -0
  28. package/src/views/article_revisions/codes/_form.ejs +3 -2
  29. package/src/views/article_revisions/general/_form.ejs +3 -2
  30. package/src/views/article_revisions/meta/_form.ejs +3 -2
  31. package/src/views/article_revisions/tags/_form.ejs +3 -2
  32. package/src/views/article_revisions/visuals/_form.ejs +3 -2
  33. package/src/views/event_revisions/codes/_form.ejs +3 -2
  34. package/src/views/event_revisions/general/_form.ejs +3 -2
  35. package/src/views/event_revisions/meta/_form.ejs +3 -2
  36. package/src/views/event_revisions/tags/_form.ejs +3 -2
  37. package/src/views/event_revisions/visuals/_form.ejs +3 -2
  38. package/src/views/page_revisions/codes/_form.ejs +3 -2
  39. package/src/views/page_revisions/general/_form.ejs +3 -2
  40. package/src/views/page_revisions/meta/_form.ejs +3 -2
  41. package/src/views/page_revisions/tags/_form.ejs +3 -2
  42. package/src/views/page_revisions/visuals/_form.ejs +3 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raketa-cloud/admin",
3
- "version": "1.6.2",
3
+ "version": "1.7.0",
4
4
  "main": "src/index.js",
5
5
  "files": [
6
6
  "src/",
@@ -12,19 +12,19 @@
12
12
  "prepare": "npx tailwindcss -i ./src/styles/application.css -o ./dist/application.css"
13
13
  },
14
14
  "devDependencies": {
15
- "@babel/core": "7.27.1",
15
+ "@babel/core": "7.27.4",
16
16
  "@babel/preset-env": "7.27.2",
17
17
  "@babel/preset-typescript": "7.27.1",
18
18
  "@repo/typescript-config": "*",
19
19
  "@tailwindcss/forms": "0.5.7",
20
20
  "@tailwindcss/typography": "0.5.13",
21
- "@types/node": "22.15.19",
21
+ "@types/node": "22.15.30",
22
22
  "jest": "29.7.0",
23
23
  "tailwindcss": "3.4.4",
24
24
  "typescript": "5.8.3"
25
25
  },
26
26
  "dependencies": {
27
27
  "ejs": "3.1.10",
28
- "graphql-request": "7.1.2"
28
+ "graphql-request": "7.2.0"
29
29
  }
30
30
  }
@@ -16,14 +16,14 @@ import utils from "./helpers/utils";
16
16
 
17
17
  function escapeHtml(string) {
18
18
  const escape = {
19
- '"': '"',
20
- '&': '&',
21
- "'": ''',
22
- '<': '&lt;',
23
- '>': '&gt;'
19
+ '"': "&quot;",
20
+ "&": "&amp;",
21
+ "'": "&#39;",
22
+ "<": "&lt;",
23
+ ">": "&gt;",
24
24
  };
25
25
 
26
- return String(string).replace(/["&'<>]/g, char => escape[char]);
26
+ return String(string).replace(/["&'<>]/g, (char) => escape[char]);
27
27
  }
28
28
 
29
29
  const renderError = (message, stack) => {
@@ -38,7 +38,8 @@ const renderError = (message, stack) => {
38
38
  };
39
39
 
40
40
  // Convert to array if needed
41
- const toArray = value => Array.isArray(value) ? value : [value].filter(Boolean);
41
+ const toArray = (value) =>
42
+ Array.isArray(value) ? value : [value].filter(Boolean);
42
43
 
43
44
  // Handle nested form values
44
45
  function putIn(result, key, value) {
@@ -50,9 +51,8 @@ function putIn(result, key, value) {
50
51
 
51
52
  if (isLastSegment) {
52
53
  // Set value at final segment
53
- current[segment] = key.endsWith("[]") || Array.isArray(value)
54
- ? toArray(value)
55
- : value;
54
+ current[segment] =
55
+ key.endsWith("[]") || Array.isArray(value) ? toArray(value) : value;
56
56
  } else {
57
57
  // Create path to next segment
58
58
  current[segment] ??= {};
@@ -62,22 +62,26 @@ function putIn(result, key, value) {
62
62
  }
63
63
 
64
64
  // Normalize paths for consistent handling
65
- const sanitizePath = filePath => path.normalize(path.join("/", ...filePath.split("/")));
65
+ const sanitizePath = (filePath) =>
66
+ path.normalize(path.join("/", ...filePath.split("/")));
66
67
 
67
68
  // Find template path with fallbacks
68
69
  function getPartialPath(partialPath, views, viewsFallback) {
69
70
  const paths = [
70
71
  sanitizePath(`${process.cwd()}/src/views/${views}/${partialPath}`),
71
72
  sanitizePath(`${currentPath}/views/${views}/${partialPath}`),
72
- viewsFallback && sanitizePath(`${currentPath}/views/${viewsFallback}/${partialPath}`)
73
+ viewsFallback &&
74
+ sanitizePath(`${currentPath}/views/${viewsFallback}/${partialPath}`),
73
75
  ].filter(Boolean);
74
76
 
75
77
  // Return first existing path
76
- const existingPath = paths.find(path => existsSync(path));
78
+ const existingPath = paths.find((path) => existsSync(path));
77
79
 
78
80
  if (existingPath) return existingPath;
79
81
 
80
- throw new Error(`Template not found: "${partialPath}"\nSearched in:\n${paths.join('\n')}`);
82
+ throw new Error(
83
+ `Template not found: "${partialPath}"\nSearched in:\n${paths.join("\n")}`
84
+ );
81
85
  }
82
86
 
83
87
  // Parse request body based on content type
@@ -109,6 +113,7 @@ export default class HonoAdapter {
109
113
  this.globalConfig = config;
110
114
  this.globalBeforeFilters = [];
111
115
  this.globalAfterFilters = [];
116
+ this.localRouter = [];
112
117
 
113
118
  // TODO: Pass in a path to redirect to after login?
114
119
  if (config) {
@@ -261,21 +266,44 @@ export default class HonoAdapter {
261
266
  });
262
267
  }
263
268
 
264
- async createAction([verb, path, resource, views, action, handler], resourceConfig) {
265
- const method = this.getMethod(verb);
269
+ addRoute(verb, path, handler) {
270
+ this.localRouter = this.localRouter.filter((routeItem) => {
271
+ return !(routeItem.verb === verb && routeItem.path === path);
272
+ });
273
+
274
+ this.localRouter.push({
275
+ verb,
276
+ path,
277
+ handler,
278
+ });
279
+ }
280
+
281
+ link() {
282
+ this.localRouter.forEach((routeItem) => {
283
+ const method = this.getMethod(routeItem.verb);
284
+
285
+ method(routeItem.path, routeItem.handler);
286
+ });
287
+ }
288
+
289
+ async createAction(
290
+ [verb, path, resource, views, action, handler],
291
+ resourceConfig
292
+ ) {
266
293
  const { i18n } = this.globalConfig;
267
294
 
268
295
  Object.keys(i18n.locales).forEach((currentLocale) => {
269
296
  const completePath = `/${currentLocale}${path}`;
270
297
 
271
- // Clean up existing routes with same path/verb
272
- this.router.routes = this.router.routes.filter(route =>
273
- route[0] !== verb || route[1] !== completePath
274
- );
275
-
276
- method(completePath, async (routerCtx, next) => {
298
+ this.addRoute(verb, completePath, async (routerCtx, next) => {
277
299
  // Create context with request data
278
- const ctx = await this.createContext(routerCtx, next, verb, resource, action);
300
+ const ctx = await this.createContext(
301
+ routerCtx,
302
+ next,
303
+ verb,
304
+ resource,
305
+ action
306
+ );
279
307
 
280
308
  // Set context properties
281
309
  Object.assign(ctx, {
@@ -285,7 +313,7 @@ export default class HonoAdapter {
285
313
  maxOpenRevisions: this.globalConfig?.maxOpenRevisions || 3,
286
314
  locale: currentLocale,
287
315
  remoteConfig: { ...this.globalConfig.remoteConfig },
288
- logger: Logger
316
+ logger: Logger,
289
317
  });
290
318
 
291
319
  // Build context object
@@ -301,15 +329,17 @@ export default class HonoAdapter {
301
329
  resourceConfig,
302
330
  adminJwt: ctx.authentication?.token,
303
331
  remoteConfig: { ...this.globalConfig.remoteConfig },
304
- globalConfig: this.globalConfig
332
+ globalConfig: this.globalConfig,
305
333
  };
306
334
 
307
- ctx.logger.info(`${ctx.method} "${ctx.rawPath}" -> ${resource}/${action} | ${JSON.stringify(ctx.params)}`);
335
+ ctx.logger.info(
336
+ `${ctx.method} "${ctx.rawPath}" -> ${resource}/${action} | ${JSON.stringify(ctx.params)}`
337
+ );
308
338
 
309
339
  const assigns = {
310
340
  beforeFilterAssigns: {},
311
341
  actionAssigns: {},
312
- afterFilterAssigns: {}
342
+ afterFilterAssigns: {},
313
343
  };
314
344
 
315
345
  // Initialize global context
@@ -328,7 +358,7 @@ export default class HonoAdapter {
328
358
  }
329
359
 
330
360
  // Process action handler
331
- const result = await handler(ctx, resourceConfig) || {};
361
+ const result = (await handler(ctx, resourceConfig)) || {};
332
362
  assigns.actionAssigns = result.assigns || {};
333
363
 
334
364
  if (result.headers) this.applyHeaders(result.headers, routerCtx);
@@ -340,10 +370,14 @@ export default class HonoAdapter {
340
370
  if (pattern.test(ctx.path)) {
341
371
  const afterResult = filter(ctx) || {};
342
372
 
343
- if (afterResult.redirect) return routerCtx.redirect(afterResult.redirect);
373
+ if (afterResult.redirect)
374
+ return routerCtx.redirect(afterResult.redirect);
344
375
  if (afterResult.json) return routerCtx.json(afterResult.json);
345
376
 
346
- Object.assign(assigns.afterFilterAssigns, afterResult.assigns || {});
377
+ Object.assign(
378
+ assigns.afterFilterAssigns,
379
+ afterResult.assigns || {}
380
+ );
347
381
  }
348
382
  }
349
383
 
@@ -353,16 +387,19 @@ export default class HonoAdapter {
353
387
  ...assigns.beforeFilterAssigns,
354
388
  ...assigns.actionAssigns,
355
389
  ...assigns.afterFilterAssigns,
356
- _context
390
+ _context,
357
391
  };
358
392
 
359
393
  // Render the view
360
- const rendered = this.renderAction({
361
- views,
362
- viewsFallback: resourceConfig.viewsFallback,
363
- action,
364
- assigns: fullAssigns
365
- }, this.globalConfig?.helpers || {});
394
+ const rendered = this.renderAction(
395
+ {
396
+ views,
397
+ viewsFallback: resourceConfig.viewsFallback,
398
+ action,
399
+ assigns: fullAssigns,
400
+ },
401
+ this.globalConfig?.helpers || {}
402
+ );
366
403
 
367
404
  return routerCtx.html(rendered);
368
405
  });
@@ -371,7 +408,9 @@ export default class HonoAdapter {
371
408
 
372
409
  // Single method to handle all route types
373
410
  routeCollection(mapping, resourceConfig) {
374
- mapping.forEach(handlerMap => this.createAction(handlerMap, resourceConfig));
411
+ mapping.forEach((handlerMap) =>
412
+ this.createAction(handlerMap, resourceConfig)
413
+ );
375
414
  }
376
415
 
377
416
  // Use the same implementation for all route types
@@ -391,7 +430,9 @@ export default class HonoAdapter {
391
430
 
392
431
  applyHeaders(headers, router) {
393
432
  if (!headers) return;
394
- Object.entries(headers).forEach(([key, value]) => router.header(key, value));
433
+ Object.entries(headers).forEach(([key, value]) =>
434
+ router.header(key, value)
435
+ );
395
436
  }
396
437
 
397
438
  formatQuery(queries) {
@@ -0,0 +1,7 @@
1
+ export default function getArticlesUpdateDestination(ctx, revision_id, section) {
2
+ const base = `/${ctx.locale}/${ctx.resourceConfig.articleType}_revisions/${revision_id}/`
3
+ const shouldPublish = ctx.body && ctx.body._next === 'publish';
4
+ return shouldPublish
5
+ ? `${base}publishes/new`
6
+ : `${base}${section}/edit?success=1`
7
+ }
@@ -0,0 +1,7 @@
1
+ export default function getEventsUpdateDestination(ctx, revision_id, section) {
2
+ const base = `/${ctx.locale}/${ctx.resourceConfig.eventType}_revisions/${revision_id}/`
3
+ const shouldPublish = ctx.body && ctx.body._next === 'publish';
4
+ return shouldPublish
5
+ ? `${base}publishes/new`
6
+ : `${base}${section}/edit?success=1`
7
+ }
@@ -0,0 +1,8 @@
1
+ export default function getPagesUpdateDestination(ctx, revision_id, section) {
2
+ const base = `/${ctx.locale}/${ctx.resourceConfig.pageType}_revisions/${revision_id}/`
3
+ const shouldPublish = ctx.body && ctx.body._next === 'publish';
4
+ return shouldPublish
5
+ ? `${base}publishes/new`
6
+ : `${base}${section}/edit?success=1`
7
+ }
8
+
@@ -1,4 +1,5 @@
1
1
  import { toDateInput } from "../../utils";
2
+ import getArticlesUpdateDestination from "../_shared/getArticlesUpdateDestination";
2
3
 
3
4
  const edit_form = async (ctx) => {
4
5
  const { parent_id } = ctx.params;
@@ -64,7 +65,7 @@ const update = async (ctx) => {
64
65
  }
65
66
 
66
67
  return {
67
- redirect: `/${ctx.locale}/${ctx.resourceConfig.articleType}_revisions/${revision.id}/codes/edit?success=1`,
68
+ redirect: getArticlesUpdateDestination(ctx, revision.id, 'codes'),
68
69
  };
69
70
  };
70
71
 
@@ -1,4 +1,5 @@
1
1
  import { toDateInput } from "../../utils";
2
+ import getArticlesUpdateDestination from "../_shared/getArticlesUpdateDestination";
2
3
 
3
4
  const edit_form = async (ctx) => {
4
5
  const { parent_id } = ctx.params;
@@ -66,7 +67,7 @@ const update = async (ctx) => {
66
67
  }
67
68
 
68
69
  return {
69
- redirect: `/${ctx.locale}/${ctx.resourceConfig.articleType}_revisions/${revision.id}/general/edit?success=1`,
70
+ redirect: getArticlesUpdateDestination(ctx, revision.id, 'general'),
70
71
  };
71
72
  };
72
73
 
@@ -1,4 +1,5 @@
1
1
  import { toDateInput } from "../../utils";
2
+ import getArticlesUpdateDestination from "../_shared/getArticlesUpdateDestination";
2
3
 
3
4
  const edit_form = async (ctx) => {
4
5
  const { parent_id } = ctx.params;
@@ -64,7 +65,7 @@ const update = async (ctx) => {
64
65
  }
65
66
 
66
67
  return {
67
- redirect: `/${ctx.locale}/${ctx.resourceConfig.articleType}_revisions/${revision.id}/meta/edit?success=1`,
68
+ redirect: getArticlesUpdateDestination(ctx, revision.id, 'meta'),
68
69
  };
69
70
  };
70
71
 
@@ -1,4 +1,5 @@
1
1
  import { toDateInput } from "../../utils";
2
+ import getArticlesUpdateDestination from "../_shared/getArticlesUpdateDestination";
2
3
 
3
4
  const edit_form = async (ctx) => {
4
5
  const { parent_id } = ctx.params;
@@ -72,7 +73,7 @@ const update = async (ctx) => {
72
73
  }
73
74
 
74
75
  return {
75
- redirect: `/${ctx.locale}/${ctx.resourceConfig.articleType}_revisions/${revision.id}/tags/edit?success=1`,
76
+ redirect: getArticlesUpdateDestination(ctx, revision.id, 'tags'),
76
77
  };
77
78
  };
78
79
 
@@ -1,4 +1,5 @@
1
1
  import { toDateInput } from "../../utils";
2
+ import getArticlesUpdateDestination from "../_shared/getArticlesUpdateDestination";
2
3
 
3
4
  const edit_form = async (ctx) => {
4
5
  const { parent_id } = ctx.params;
@@ -64,7 +65,7 @@ const update = async (ctx) => {
64
65
  }
65
66
 
66
67
  return {
67
- redirect: `/${ctx.locale}/${ctx.resourceConfig.articleType}_revisions/${revision.id}/visuals/edit?success=1`,
68
+ redirect: getArticlesUpdateDestination(ctx, revision.id, 'visuals'),
68
69
  };
69
70
  };
70
71
 
@@ -1,4 +1,5 @@
1
1
  import { toDateTimeInput, toDateInput } from "../../utils";
2
+ import getEventsUpdateDestination from "../_shared/getEventsUpdateDestination";
2
3
 
3
4
  const edit_form = async (ctx) => {
4
5
  const { parent_id } = ctx.params;
@@ -62,7 +63,7 @@ const update = async (ctx) => {
62
63
  }
63
64
 
64
65
  return {
65
- redirect: `/${ctx.locale}/${ctx.resourceConfig.eventType}_revisions/${revision.id}/codes/edit?success=1`,
66
+ redirect: getEventsUpdateDestination(ctx, revision.id, 'codes'),
66
67
  };
67
68
  };
68
69
 
@@ -1,4 +1,5 @@
1
1
  import { toDateTimeInput, toDateInput } from "../../utils";
2
+ import getEventsUpdateDestination from "../_shared/getEventsUpdateDestination";
2
3
 
3
4
  const edit_form = async (ctx) => {
4
5
  const { parent_id } = ctx.params;
@@ -64,7 +65,7 @@ const update = async (ctx) => {
64
65
  }
65
66
 
66
67
  return {
67
- redirect: `/${ctx.locale}/${ctx.resourceConfig.eventType}_revisions/${revision.id}/general/edit?success=1`,
68
+ redirect: getEventsUpdateDestination(ctx, revision.id, 'general'),
68
69
  };
69
70
  };
70
71
 
@@ -1,4 +1,5 @@
1
1
  import { toDateTimeInput, toDateInput } from "../../utils";
2
+ import getEventsUpdateDestination from "../_shared/getEventsUpdateDestination";
2
3
 
3
4
  const edit_form = async (ctx) => {
4
5
  const { parent_id } = ctx.params;
@@ -62,7 +63,7 @@ const update = async (ctx) => {
62
63
  }
63
64
 
64
65
  return {
65
- redirect: `/${ctx.locale}/${ctx.resourceConfig.eventType}_revisions/${revision.id}/meta/edit?success=1`,
66
+ redirect: getEventsUpdateDestination(ctx, revision.id, 'meta'),
66
67
  };
67
68
  };
68
69
 
@@ -1,4 +1,5 @@
1
1
  import { toDateTimeInput, toDateInput } from "../../utils";
2
+ import getEventsUpdateDestination from "../_shared/getEventsUpdateDestination";
2
3
 
3
4
  const edit_form = async (ctx) => {
4
5
  const { parent_id } = ctx.params;
@@ -69,7 +70,7 @@ const update = async (ctx) => {
69
70
  }
70
71
 
71
72
  return {
72
- redirect: `/${ctx.locale}/${ctx.resourceConfig.eventType}_revisions/${revision.id}/tags/edit?success=1`,
73
+ redirect: getEventsUpdateDestination(ctx, revision.id, 'tags'),
73
74
  };
74
75
  };
75
76
 
@@ -1,4 +1,5 @@
1
1
  import { toDateTimeInput, toDateInput } from "../../utils";
2
+ import getEventsUpdateDestination from "../_shared/getEventsUpdateDestination";
2
3
 
3
4
  const edit_form = async (ctx) => {
4
5
  const { parent_id } = ctx.params;
@@ -62,7 +63,7 @@ const update = async (ctx) => {
62
63
  }
63
64
 
64
65
  return {
65
- redirect: `/${ctx.locale}/${ctx.resourceConfig.eventType}_revisions/${revision.id}/visuals/edit?success=1`,
66
+ redirect: getEventsUpdateDestination(ctx, revision.id, 'visuals'),
66
67
  };
67
68
  };
68
69
 
@@ -1,3 +1,5 @@
1
+ import getPagesUpdateDestination from "../_shared/getPagesUpdateDestination";
2
+
1
3
  const edit_form = async (ctx) => {
2
4
  const { parent_id } = ctx.params;
3
5
 
@@ -47,7 +49,7 @@ const update = async (ctx) => {
47
49
  }
48
50
 
49
51
  return {
50
- redirect: `/${ctx.locale}/${ctx.resourceConfig.pageType}_revisions/${revision.id}/codes/edit?success=1`,
52
+ redirect: getPagesUpdateDestination(ctx, revision.id, 'codes'),
51
53
  };
52
54
  };
53
55
 
@@ -1,3 +1,5 @@
1
+ import getPagesUpdateDestination from "../_shared/getPagesUpdateDestination";
2
+
1
3
  const edit_form = async (ctx) => {
2
4
  const { parent_id } = ctx.params;
3
5
 
@@ -49,7 +51,7 @@ const update = async (ctx) => {
49
51
  }
50
52
 
51
53
  return {
52
- redirect: `/${ctx.locale}/${ctx.resourceConfig.pageType}_revisions/${revision.id}/general/edit?success=1`,
54
+ redirect: getPagesUpdateDestination(ctx, revision.id, 'general'),
53
55
  };
54
56
  };
55
57
 
@@ -1,3 +1,5 @@
1
+ import getPagesUpdateDestination from "../_shared/getPagesUpdateDestination";
2
+
1
3
  const edit_form = async (ctx) => {
2
4
  const { parent_id } = ctx.params;
3
5
 
@@ -47,7 +49,7 @@ const update = async (ctx) => {
47
49
  }
48
50
 
49
51
  return {
50
- redirect: `/${ctx.locale}/${ctx.resourceConfig.pageType}_revisions/${revision.id}/meta/edit?success=1`,
52
+ redirect: getPagesUpdateDestination(ctx, revision.id, 'meta'),
51
53
  };
52
54
  };
53
55
 
@@ -1,3 +1,5 @@
1
+ import getPagesUpdateDestination from "../_shared/getPagesUpdateDestination";
2
+
1
3
  const fetchDefaultLocaleTags = async (ctx, resource) => {
2
4
  if (ctx.locale === ctx.defaultLocale) {
3
5
  return { defaultLocaleName: "", defaultLocaleTags: [] };
@@ -95,7 +97,7 @@ const update = async (ctx) => {
95
97
  }
96
98
 
97
99
  return {
98
- redirect: `/${ctx.locale}/${ctx.resourceConfig.pageType}_revisions/${revision.id}/tags/edit?success=1`,
100
+ redirect: getPagesUpdateDestination(ctx, revision.id, 'tags'),
99
101
  };
100
102
  };
101
103
 
@@ -1,3 +1,5 @@
1
+ import getPagesUpdateDestination from "../_shared/getPagesUpdateDestination";
2
+
1
3
  const edit_form = async (ctx) => {
2
4
  const { parent_id } = ctx.params;
3
5
 
@@ -48,7 +50,7 @@ const update = async (ctx) => {
48
50
  }
49
51
 
50
52
  return {
51
- redirect: `/${ctx.locale}/${ctx.resourceConfig.pageType}_revisions/${revision.id}/visuals/edit?success=1`,
53
+ redirect: getPagesUpdateDestination(ctx, revision.id, 'visuals'),
52
54
  };
53
55
  };
54
56
 
package/src/index.js CHANGED
@@ -90,5 +90,9 @@ export { default as EventTranslationsHandler } from "./handlers/events/Translati
90
90
  export { default as EventURLsHandler } from "./handlers/events/URLsHandler";
91
91
  export { default as EventsHandler } from "./handlers/events/EventsHandler";
92
92
 
93
+ export { default as getPagesUpdateDestination } from "./handlers/_shared/getPagesUpdateDestination";
94
+ export { default as getArticlesUpdateDestination } from "./handlers/_shared/getArticlesUpdateDestination";
95
+ export { default as getEventsUpdateDestination } from "./handlers/_shared/getEventsUpdateDestination";
96
+
93
97
  export { default as Factory } from "./tests/support/factory";
94
98
  export { default as Logger } from "./Logger";
@@ -1,9 +1,10 @@
1
1
  import HonoAdapter from "../HonoAdapter";
2
+ import RaketaContext from "../RaketaContext";
2
3
 
3
4
  class HonoMock {
4
5
  constructor() {
5
6
  this.router = {
6
- routes: []
7
+ routes: [],
7
8
  };
8
9
  this.get = jest.fn();
9
10
  this.post = jest.fn();
@@ -17,12 +18,12 @@ class HonoMock {
17
18
  describe("HonoAdapter", () => {
18
19
  let app;
19
20
  let adapter;
20
-
21
+
21
22
  beforeEach(() => {
22
23
  app = new HonoMock();
23
24
  adapter = new HonoAdapter(app);
24
25
  });
25
-
26
+
26
27
  // Method tests
27
28
  test("getMethod returns Hono's get for invalid verbs", () => {
28
29
  expect(adapter.getMethod("not existing")).toBe(app.get);
@@ -78,58 +79,126 @@ describe("HonoAdapter", () => {
78
79
  },
79
80
  });
80
81
  });
81
-
82
+
82
83
  test("setGlobalContext sets the global context function", () => {
83
84
  const newCtx = () => ({ test: true });
84
85
  adapter.setGlobalContext(newCtx);
85
86
  expect(adapter.globalCtx).toBe(newCtx);
86
87
  });
87
-
88
+
88
89
  test("applyHeaders applies headers to router context", () => {
89
90
  const headers = {
90
91
  "Content-Type": "application/json",
91
- "X-Custom-Header": "test-value"
92
+ "X-Custom-Header": "test-value",
92
93
  };
93
-
94
+
94
95
  adapter.applyHeaders(headers, app);
95
-
96
+
96
97
  expect(app.header).toHaveBeenCalledTimes(2);
97
98
  expect(app.header).toHaveBeenCalledWith("Content-Type", "application/json");
98
99
  expect(app.header).toHaveBeenCalledWith("X-Custom-Header", "test-value");
99
100
  });
100
-
101
+
101
102
  test("applyHeaders does nothing when headers is undefined", () => {
102
103
  adapter.applyHeaders(undefined, app);
103
104
  expect(app.header).not.toHaveBeenCalled();
104
105
  });
105
-
106
+
106
107
  test("formatQuery formats query parameters correctly", () => {
107
108
  const queries = {
108
109
  single: ["value"],
109
- multiple: ["one", "two"]
110
+ multiple: ["one", "two"],
110
111
  };
111
-
112
+
112
113
  const result = adapter.formatQuery(queries);
113
-
114
+
114
115
  expect(result).toEqual({
115
116
  single: "value",
116
- multiple: ["one", "two"]
117
+ multiple: ["one", "two"],
117
118
  });
118
119
  });
119
120
 
120
121
  test("parseFormBody handles array values", () => {
121
122
  expect(
122
123
  adapter.parseFormBody({
123
- "tags[]": ["javascript", "react", "node"]
124
+ "tags[]": ["javascript", "react", "node"],
124
125
  })
125
126
  ).toEqual({
126
- tags: ["javascript", "react", "node"]
127
+ tags: ["javascript", "react", "node"],
127
128
  });
128
129
  });
129
130
 
130
131
  test("parseFormBody handles empty values", () => {
131
- expect(
132
- adapter.parseFormBody({})
133
- ).toEqual({});
132
+ expect(adapter.parseFormBody({})).toEqual({});
133
+ });
134
+
135
+ test("renderLayout renders an existing layout template", async () => {
136
+ const result = await adapter.renderLayout(
137
+ {
138
+ _context: {
139
+ layout: {
140
+ sidebar: [],
141
+ },
142
+ path: "/en/pages",
143
+ locale: "en",
144
+ locales: {
145
+ en: {
146
+ flag: "",
147
+ name: "English",
148
+ },
149
+ },
150
+ globalConfig: {
151
+ cloud: {
152
+ images_api_key: "api-key",
153
+ },
154
+ },
155
+ },
156
+ },
157
+ "<h1>Hi</h1>"
158
+ );
159
+
160
+ expect(typeof result).toBe("string");
161
+ expect(result.length).toBeGreaterThan(0);
162
+ });
163
+
164
+ test("addRouote - adds a route to the local router", () => {
165
+ expect(adapter.localRouter).toEqual([]);
166
+
167
+ const handler = () => {};
168
+
169
+ adapter.addRoute("GET", "/en/pages", handler);
170
+
171
+ expect(adapter.localRouter).toEqual([
172
+ { verb: "GET", path: "/en/pages", handler },
173
+ ]);
174
+ });
175
+
176
+ test("addRouote - overrides routes if they already exist in the local router", () => {
177
+ expect(adapter.localRouter).toEqual([]);
178
+
179
+ const handler = () => {};
180
+ const handler2 = () => {};
181
+
182
+ adapter.addRoute("GET", "/en/pages", handler);
183
+
184
+ expect(adapter.localRouter).toEqual([
185
+ { verb: "GET", path: "/en/pages", handler },
186
+ ]);
187
+
188
+ adapter.addRoute("GET", "/en/pages", handler2);
189
+
190
+ expect(adapter.localRouter).toEqual([
191
+ { verb: "GET", path: "/en/pages", handler: handler2 },
192
+ ]);
193
+ });
194
+
195
+ test("link links local routes with the app", () => {
196
+ const handler = () => {};
197
+ adapter.addRoute("GET", "/en/pages", handler);
198
+
199
+ adapter.link();
200
+
201
+ expect(app.get).toHaveBeenCalledTimes(1);
202
+ expect(app.get).toHaveBeenCalledWith("/en/pages", handler);
134
203
  });
135
- });
204
+ });
@@ -17,6 +17,7 @@ function buildCtx(revision, updateSettings) {
17
17
  pageType: "page",
18
18
  pageTypePlural: "pages",
19
19
  },
20
+ body: {},
20
21
  };
21
22
  }
22
23
 
@@ -70,6 +71,55 @@ describe("Page revision code settings", () => {
70
71
  });
71
72
  });
72
73
 
74
+ test("update and publish", async () => {
75
+ const revision = Factory.build("page_revision");
76
+
77
+ const updateSettings = {
78
+ settings: {
79
+ codes: {
80
+ scripts: JSON.stringify([{ type: "bla" }]),
81
+ structured_json: JSON.stringify({ type: "article" }),
82
+ },
83
+ },
84
+ };
85
+
86
+ const ctx = { ...buildCtx(revision, updateSettings), body: { _next: 'publish' } };
87
+
88
+ const mockFind = jest
89
+ .spyOn(ctx.client.pageRevisions, "find")
90
+ .mockImplementation(() => revision);
91
+
92
+ const mockUpdate = jest
93
+ .spyOn(ctx.client.pageRevisions, "update")
94
+ .mockImplementation(() => ({
95
+ revision: {},
96
+ errors: null,
97
+ }));
98
+
99
+ const result = await CodeHandler.update(ctx);
100
+
101
+ expect(mockFind).toHaveBeenCalledWith(revision.id, "en");
102
+ expect(mockUpdate).toHaveBeenCalledWith(revision.id, "en", {
103
+ slug: revision.slug,
104
+ title: revision.title,
105
+ visibility: revision.visibility,
106
+ widgets: revision.widgets,
107
+ tags: revision.tags.map((t) => t.id),
108
+ categories: revision.categories.map((c) => c.id),
109
+ settings: {
110
+ ...revision.settings,
111
+ codes: {
112
+ scripts: [{ type: "bla" }],
113
+ structured_json: { type: "article" },
114
+ },
115
+ },
116
+ });
117
+
118
+ expect(result).toEqual({
119
+ redirect: `/en/page_revisions/${revision.id}/publishes/new`,
120
+ });
121
+ });
122
+
73
123
  test("update failed", async () => {
74
124
  const revision = Factory.build("page_revision");
75
125
 
@@ -19,6 +19,7 @@ function buildCtx(revision, updateSettings) {
19
19
  pageType: "page",
20
20
  pageTypePlural: "pages",
21
21
  },
22
+ body: {},
22
23
  };
23
24
  }
24
25
 
@@ -75,6 +76,58 @@ describe("Page revision general settings", () => {
75
76
  });
76
77
  });
77
78
 
79
+ test("update and publish", async () => {
80
+ const revision = Factory.build("page_revision");
81
+
82
+ const updateSettings = {
83
+ title: "Updated title",
84
+ slug: "/updated-url",
85
+ visibility: "protected",
86
+ settings: {
87
+ credentials: {
88
+ user: "user",
89
+ password: "admin123!@#",
90
+ },
91
+ },
92
+ };
93
+
94
+ const ctx = { ...buildCtx(revision, updateSettings), body: { _next: 'publish' } };
95
+
96
+ const mockFind = jest
97
+ .spyOn(ctx.client.pageRevisions, "find")
98
+ .mockImplementation(() => revision);
99
+
100
+ const mockUpdate = jest
101
+ .spyOn(ctx.client.pageRevisions, "update")
102
+ .mockImplementation(() => ({
103
+ revision: {},
104
+ errors: null,
105
+ }));
106
+
107
+ const result = await GeneralHandler.update(ctx);
108
+
109
+ expect(mockFind).toHaveBeenCalledWith(revision.id, "en");
110
+ expect(mockUpdate).toHaveBeenCalledWith(revision.id, "en", {
111
+ slug: updateSettings.slug,
112
+ title: updateSettings.title,
113
+ visibility: updateSettings.visibility,
114
+ widgets: revision.widgets,
115
+ tags: revision.tags.map((t) => t.id),
116
+ categories: revision.categories.map((c) => c.id),
117
+ settings: {
118
+ ...revision.settings,
119
+ credentials: {
120
+ user: updateSettings.settings.credentials.user,
121
+ password: updateSettings.settings.credentials.password,
122
+ },
123
+ },
124
+ });
125
+
126
+ expect(result).toEqual({
127
+ redirect: `/en/page_revisions/${revision.id}/publishes/new`,
128
+ });
129
+ });
130
+
78
131
  test("update failed", async () => {
79
132
  const revision = Factory.build("page_revision");
80
133
 
@@ -17,6 +17,7 @@ function buildCtx(revision, updateSettings) {
17
17
  pageType: "page",
18
18
  pageTypePlural: "pages",
19
19
  },
20
+ body: {},
20
21
  };
21
22
  }
22
23
 
@@ -72,6 +73,57 @@ describe("Page revision meta settings", () => {
72
73
  });
73
74
  });
74
75
 
76
+ test("update and publish", async () => {
77
+ const revision = Factory.build("page_revision");
78
+
79
+ const updateSettings = {
80
+ settings: {
81
+ meta: {
82
+ title: "An open graph title",
83
+ description: "An open graph description",
84
+ image: JSON.stringify({ id: 42 }),
85
+ },
86
+ },
87
+ };
88
+
89
+ const ctx = { ...buildCtx(revision, updateSettings), body: { _next: 'publish' } };
90
+
91
+ const mockFind = jest
92
+ .spyOn(ctx.client.pageRevisions, "find")
93
+ .mockImplementation(() => revision);
94
+
95
+ const mockUpdate = jest
96
+ .spyOn(ctx.client.pageRevisions, "update")
97
+ .mockImplementation(() => ({
98
+ revision: {},
99
+ errors: null,
100
+ }));
101
+
102
+ const result = await MetaHandler.update(ctx);
103
+
104
+ expect(mockFind).toHaveBeenCalledWith(revision.id, "en");
105
+ expect(mockUpdate).toHaveBeenCalledWith(revision.id, "en", {
106
+ slug: revision.slug,
107
+ title: revision.title,
108
+ visibility: revision.visibility,
109
+ widgets: revision.widgets,
110
+ tags: revision.tags.map((t) => t.id),
111
+ categories: revision.categories.map((c) => c.id),
112
+ settings: {
113
+ ...revision.settings,
114
+ meta: {
115
+ title: updateSettings.settings.meta.title,
116
+ description: updateSettings.settings.meta.description,
117
+ image: { id: 42 },
118
+ },
119
+ },
120
+ });
121
+
122
+ expect(result).toEqual({
123
+ redirect: `/en/page_revisions/${revision.id}/publishes/new`,
124
+ });
125
+ });
126
+
75
127
  test("update failed", async () => {
76
128
  const revision = Factory.build("page_revision");
77
129
 
@@ -28,6 +28,7 @@ function buildCtx(revision, updateSettings) {
28
28
  pageType: "page",
29
29
  pageTypePlural: "pages",
30
30
  },
31
+ body: {},
31
32
  };
32
33
  }
33
34
 
@@ -74,6 +75,48 @@ describe("Page revision tags settings", () => {
74
75
  });
75
76
  });
76
77
 
78
+ test("update and publish", async () => {
79
+ const tags = [
80
+ Factory.build("tag", { id: 42 }),
81
+ Factory.build("tag", { id: 69 }),
82
+ ];
83
+ const revision = Factory.build("page_revision", { tags: [tags[0]] });
84
+
85
+ const updateSettings = {
86
+ tags: [tags.map((t) => t.id)],
87
+ };
88
+
89
+ const ctx = { ...buildCtx(revision, updateSettings), body: { _next: 'publish' } };
90
+
91
+ const mockFind = jest
92
+ .spyOn(ctx.client.pageRevisions, "find")
93
+ .mockImplementation(() => revision);
94
+
95
+ const mockUpdate = jest
96
+ .spyOn(ctx.client.pageRevisions, "update")
97
+ .mockImplementation(() => ({
98
+ revision: {},
99
+ errors: null,
100
+ }));
101
+
102
+ const result = await TagHandler.update(ctx);
103
+
104
+ expect(mockFind).toHaveBeenCalledWith(revision.id, "en");
105
+ expect(mockUpdate).toHaveBeenCalledWith(revision.id, "en", {
106
+ slug: revision.slug,
107
+ title: revision.title,
108
+ visibility: revision.visibility,
109
+ widgets: revision.widgets,
110
+ tags: updateSettings.tags,
111
+ categories: revision.categories.map((c) => c.id),
112
+ settings: revision.settings,
113
+ });
114
+
115
+ expect(result).toEqual({
116
+ redirect: `/en/page_revisions/${revision.id}/publishes/new`,
117
+ });
118
+ });
119
+
77
120
  test("update failed", async () => {
78
121
  const tags = [
79
122
  Factory.build("tag", { id: 42 }),
@@ -17,6 +17,7 @@ function buildCtx(revision, updateSettings) {
17
17
  pageType: "page",
18
18
  pageTypePlural: "pages",
19
19
  },
20
+ body: {},
20
21
  };
21
22
  }
22
23
 
@@ -64,6 +65,49 @@ describe("Page revision visuals settings", () => {
64
65
  });
65
66
  });
66
67
 
68
+ test("update and publish", async () => {
69
+ const revision = Factory.build("page_revision");
70
+
71
+ const updateSettings = {
72
+ settings: {
73
+ layout: "bla",
74
+ },
75
+ };
76
+
77
+ const ctx = { ...buildCtx(revision, updateSettings), body: { _next: 'publish' } };
78
+
79
+ const mockFind = jest
80
+ .spyOn(ctx.client.pageRevisions, "find")
81
+ .mockImplementation(() => revision);
82
+
83
+ const mockUpdate = jest
84
+ .spyOn(ctx.client.pageRevisions, "update")
85
+ .mockImplementation(() => ({
86
+ revision: {},
87
+ errors: null,
88
+ }));
89
+
90
+ const result = await VisualHandler.update(ctx);
91
+
92
+ expect(mockFind).toHaveBeenCalledWith(revision.id, "en");
93
+ expect(mockUpdate).toHaveBeenCalledWith(revision.id, "en", {
94
+ slug: revision.slug,
95
+ title: revision.title,
96
+ visibility: revision.visibility,
97
+ widgets: revision.widgets,
98
+ tags: revision.tags.map((t) => t.id),
99
+ categories: revision.categories.map((c) => c.id),
100
+ settings: {
101
+ ...revision.settings,
102
+ layout: "bla",
103
+ },
104
+ });
105
+
106
+ expect(result).toEqual({
107
+ redirect: `/en/page_revisions/${revision.id}/publishes/new`,
108
+ });
109
+ });
110
+
67
111
  test("update failed", async () => {
68
112
  const revision = Factory.build("page_revision");
69
113
 
@@ -5,8 +5,9 @@
5
5
 
6
6
  <div class="form-actions">
7
7
  <div class="btn-toolbar">
8
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
9
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
8
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
9
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
10
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
10
11
  </div>
11
12
  </div>
12
13
  </form>
@@ -9,8 +9,9 @@
9
9
 
10
10
  <div class="form-actions">
11
11
  <div class="btn-toolbar">
12
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
13
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
12
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
13
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
14
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
14
15
  </div>
15
16
  </div>
16
17
  </form>
@@ -7,8 +7,9 @@
7
7
 
8
8
  <div class="form-actions">
9
9
  <div class="btn-toolbar">
10
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
11
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
10
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
11
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
12
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
12
13
  </div>
13
14
  </div>
14
15
  </form>
@@ -4,8 +4,9 @@
4
4
 
5
5
  <div class="form-actions">
6
6
  <div class="btn-toolbar">
7
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
8
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
7
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
8
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
9
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
9
10
  </div>
10
11
  </div>
11
12
  </form>
@@ -4,8 +4,9 @@
4
4
 
5
5
  <div class="form-actions">
6
6
  <div class="btn-toolbar">
7
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
8
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
7
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
8
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
9
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.articleTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
9
10
  </div>
10
11
  </div>
11
12
  </form>
@@ -5,8 +5,9 @@
5
5
 
6
6
  <div class="form-actions">
7
7
  <div class="btn-toolbar">
8
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
9
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
8
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
9
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
10
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
10
11
  </div>
11
12
  </div>
12
13
  </form>
@@ -11,8 +11,9 @@
11
11
 
12
12
  <div class="form-actions">
13
13
  <div class="btn-toolbar">
14
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
15
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
14
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
15
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
16
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
16
17
  </div>
17
18
  </div>
18
19
  </form>
@@ -7,8 +7,9 @@
7
7
 
8
8
  <div class="form-actions">
9
9
  <div class="btn-toolbar">
10
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
11
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
10
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
11
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
12
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
12
13
  </div>
13
14
  </div>
14
15
  </form>
@@ -4,8 +4,9 @@
4
4
 
5
5
  <div class="form-actions">
6
6
  <div class="btn-toolbar">
7
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
8
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
7
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
8
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
9
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
9
10
  </div>
10
11
  </div>
11
12
  </form>
@@ -4,8 +4,9 @@
4
4
 
5
5
  <div class="form-actions">
6
6
  <div class="btn-toolbar">
7
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
8
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
7
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
8
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
9
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.eventTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
9
10
  </div>
10
11
  </div>
11
12
  </form>
@@ -5,8 +5,9 @@
5
5
 
6
6
  <div class="form-actions">
7
7
  <div class="btn-toolbar">
8
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
9
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
8
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
9
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
10
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
10
11
  </div>
11
12
  </div>
12
13
  </form>
@@ -8,8 +8,9 @@
8
8
 
9
9
  <div class="form-actions">
10
10
  <div class="btn-toolbar">
11
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
12
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
11
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
12
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
13
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
13
14
  </div>
14
15
  </div>
15
16
  </form>
@@ -7,8 +7,9 @@
7
7
 
8
8
  <div class="form-actions">
9
9
  <div class="btn-toolbar">
10
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
11
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
10
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
11
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
12
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
12
13
  </div>
13
14
  </div>
14
15
  </form>
@@ -4,8 +4,9 @@
4
4
 
5
5
  <div class="form-actions">
6
6
  <div class="btn-toolbar">
7
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
8
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
7
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
8
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
9
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
9
10
  </div>
10
11
  </div>
11
12
  </form>
@@ -4,8 +4,9 @@
4
4
 
5
5
  <div class="form-actions">
6
6
  <div class="btn-toolbar">
7
- <%- f.submit('Save changes', { class: 'btn-primary' }) %>
8
- <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/${resource.originalId}` %>" class="btn-secondary">Back</a>
7
+ <%- f.submit('Save', { class: 'btn-primary' }) %>
8
+ <%- f.submit('Save and publish', { class: 'btn-secondary', name: '_next', value: 'publish' }) %>
9
+ <a href="<%= `/${_context.locale}/${_context.resourceConfig.pageTypePlural}/${resource.originalId}` %>" class="btn-text">Back</a>
9
10
  </div>
10
11
  </div>
11
12
  </form>