@open-commune/web 0.1.3

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 (132) hide show
  1. package/README.md +41 -0
  2. package/app.config.ts +10 -0
  3. package/app.vue +13 -0
  4. package/assets/css/main.css +13 -0
  5. package/components/data/display/row/data-avatar.vue +23 -0
  6. package/components/data/display/row/data-badge.vue +34 -0
  7. package/components/data/display/row/data-color.vue +17 -0
  8. package/components/data/display/row/data-display-badge-publish-status.vue +29 -0
  9. package/components/data/display/row/data-label.vue +37 -0
  10. package/components/data/display/row/data-last-update.vue +45 -0
  11. package/components/data/display/row/data-spacer.vue +4 -0
  12. package/components/data/display/row/data-summary.vue +44 -0
  13. package/components/data/display/row/data-text.vue +14 -0
  14. package/components/data/display/row/data-toggle.vue +16 -0
  15. package/components/data/display/row.vue +111 -0
  16. package/components/data/lists/data-list.vue +85 -0
  17. package/components/data/lists/data-lists-table.vue +155 -0
  18. package/components/elements/avatars/avatar-circular.vue +68 -0
  19. package/components/elements/avatars/avatar-with-text.vue +47 -0
  20. package/components/elements/badges/badge.vue +85 -0
  21. package/components/elements/buttons/primary.vue +32 -0
  22. package/components/elements/buttons/secondary.vue +35 -0
  23. package/components/elements/buttons/tertiary.vue +25 -0
  24. package/components/elements/dropdowns/dots.vue +75 -0
  25. package/components/elements/media/image.vue +82 -0
  26. package/components/elements/notifications/default.vue +90 -0
  27. package/components/forms/handlers/handler-collections.vue +189 -0
  28. package/components/forms/handlers/handler-publication-status.vue +193 -0
  29. package/components/forms/handlers/handler-records.vue +138 -0
  30. package/components/forms/handlers/handler-roles.vue +176 -0
  31. package/components/forms/handlers/handler-schedules.vue +218 -0
  32. package/components/forms/handlers/handler-steps.vue +147 -0
  33. package/components/forms/handlers/handler-tokens.vue +79 -0
  34. package/components/forms/inputs/input-color-picker.vue +159 -0
  35. package/components/forms/inputs/input-editor.vue +363 -0
  36. package/components/forms/inputs/input-files-media.vue +150 -0
  37. package/components/forms/inputs/input-files.vue +98 -0
  38. package/components/forms/inputs/input-json.vue +65 -0
  39. package/components/forms/inputs/input-location.vue +179 -0
  40. package/components/forms/inputs/input-text-area.vue +65 -0
  41. package/components/forms/inputs/input-text.vue +122 -0
  42. package/components/forms/inputs/input-toggle.vue +125 -0
  43. package/components/forms/inputs/media-section/media-editor.vue +95 -0
  44. package/components/forms/layouts/form.vue +203 -0
  45. package/components/forms/layouts/layout.vue +47 -0
  46. package/components/forms/layouts/modal.vue +96 -0
  47. package/components/forms/layouts/navigation.vue +48 -0
  48. package/components/forms/layouts/section.vue +118 -0
  49. package/components/forms/selects/select-collection-enum.vue +195 -0
  50. package/components/forms/selects/select-date.vue +245 -0
  51. package/components/forms/selects/select-enum.vue +186 -0
  52. package/components/forms/selects/select-record.vue +274 -0
  53. package/components/layout/headings/headings-collection.vue +29 -0
  54. package/components/layout/headings/headings-record.vue +33 -0
  55. package/components/layout/navigations/primary/dropdown-publish.vue +147 -0
  56. package/components/layout/navigations/primary.vue +132 -0
  57. package/components/layout/notifications/notifications-list.vue +19 -0
  58. package/composables/core/domains/use-definitions.ts +57 -0
  59. package/composables/core/domains/use-entries.ts +62 -0
  60. package/composables/core/domains/use-media.ts +82 -0
  61. package/composables/core/use-core.ts +12 -0
  62. package/composables/use-ability.js +38 -0
  63. package/composables/use-models.js +27 -0
  64. package/composables/use-session.ts +80 -0
  65. package/composables/use-timer.js +62 -0
  66. package/composables/use-trpc.js +17 -0
  67. package/editor/index.ts +44 -0
  68. package/editor/views/CalloutView.vue +22 -0
  69. package/editor/views/DetailsContentView.vue +11 -0
  70. package/editor/views/DetailsSummaryView.vue +11 -0
  71. package/editor/views/DetailsView.vue +72 -0
  72. package/editor/views/EmbedView.vue +53 -0
  73. package/editor/views/FileView.vue +79 -0
  74. package/editor/views/ImageView.vue +56 -0
  75. package/editor/views/UploadView.vue +71 -0
  76. package/layouts/default.vue +21 -0
  77. package/layouts/entrance.vue +10 -0
  78. package/layouts/record.vue +21 -0
  79. package/middleware/00.auth.global.js +16 -0
  80. package/middleware/01.instance.global.js +21 -0
  81. package/middleware/03.verify-permissions.global.js +11 -0
  82. package/middleware/04.prefetch-page.global.js +18 -0
  83. package/middleware/05.headings-page.global.js +25 -0
  84. package/models/_commun.js +243 -0
  85. package/models/_factory.js +34 -0
  86. package/models/collection.js +87 -0
  87. package/models/entrance.js +42 -0
  88. package/models/media.js +82 -0
  89. package/models/organization.js +92 -0
  90. package/models/record.js +165 -0
  91. package/models/role.js +66 -0
  92. package/models/token.js +56 -0
  93. package/models/user.js +97 -0
  94. package/nuxt.config.ts +151 -0
  95. package/package.json +71 -0
  96. package/pages/[collection]/[record]/index.vue +88 -0
  97. package/pages/[collection]/index.vue +55 -0
  98. package/pages/_lab/editor.vue +149 -0
  99. package/pages/auth/callback.vue +31 -0
  100. package/pages/index/index.vue +85 -0
  101. package/pages/index/password/define/[token].vue +79 -0
  102. package/pages/index/password/recover/index.vue +72 -0
  103. package/pages/index/welcome/[token].vue +90 -0
  104. package/pages/index.vue +44 -0
  105. package/pages/management/index.vue +15 -0
  106. package/pages/management/members.vue +176 -0
  107. package/pages/management/organizations.vue +192 -0
  108. package/pages/management/roles.vue +183 -0
  109. package/pages/overview.vue +15 -0
  110. package/pages/settings.vue +197 -0
  111. package/plugins/01.session.client.ts +5 -0
  112. package/plugins/02.vue-query.ts +25 -0
  113. package/public/favicon.ico +0 -0
  114. package/public/logo-black.svg +1 -0
  115. package/public/logo-white.svg +1 -0
  116. package/server/api/trpc/[...trpc].ts +28 -0
  117. package/server/api/v1/content/deployment.get.ts +45 -0
  118. package/server/api/v1/content/records.get.ts +12 -0
  119. package/server/middleware/0.security.ts +19 -0
  120. package/server/middleware/1.cors.ts +31 -0
  121. package/server/middleware/2.session.ts +20 -0
  122. package/server/middleware/3.api-token.ts +20 -0
  123. package/server/plugins/core.ts +8 -0
  124. package/server/routes/_tasks/[name].post.ts +20 -0
  125. package/server/routes/health.get.ts +6 -0
  126. package/server/tasks/deploy.ts +26 -0
  127. package/server/types.d.ts +8 -0
  128. package/server/utils/core.ts +18 -0
  129. package/store/layout/headings.js +21 -0
  130. package/store/layout/modal.js +80 -0
  131. package/store/layout/notifications.js +52 -0
  132. package/store/layout/workspace.js +80 -0
@@ -0,0 +1,26 @@
1
+ import { defineTask } from 'nitropack/runtime';
2
+ import { DeployHookMissingError } from '@open-commune/core';
3
+ import { useCore } from '../utils/core.ts';
4
+
5
+ /**
6
+ * Triggers the site build. A failure surfaces as a task error rather than being
7
+ * reported as a success. A missing hook is an explicit no-op.
8
+ */
9
+ export default defineTask({
10
+ meta: {
11
+ name: 'deploy',
12
+ description: 'Trigger the site build through the configured deploy hook',
13
+ },
14
+ async run(): Promise<{ result: { triggered: boolean; status?: number; reason?: string } }> {
15
+ const { services } = useCore();
16
+ try {
17
+ const { status } = await services.organization.deploy();
18
+ return { result: { triggered: true, status } };
19
+ } catch (error) {
20
+ if (error instanceof DeployHookMissingError) {
21
+ return { result: { triggered: false, reason: 'no deploy hook configured' } };
22
+ }
23
+ throw error;
24
+ }
25
+ },
26
+ });
@@ -0,0 +1,8 @@
1
+ import type { AuthSession } from '@open-commune/core';
2
+
3
+ declare module 'h3' {
4
+ interface H3EventContext {
5
+ /** Authenticated session (or null), resolved by middleware/2.session.ts. */
6
+ session: AuthSession | null;
7
+ }
8
+ }
@@ -0,0 +1,18 @@
1
+ import { createCore, type Core } from '@open-commune/core';
2
+
3
+ /**
4
+ * The wired Core, as a singleton. `plugins/core.ts` makes the first call at
5
+ * server start, so migrations and housekeeping run at boot rather than on the
6
+ * first request.
7
+ */
8
+ let core: Core | undefined;
9
+
10
+ export function useCore(): Core {
11
+ if (!core) {
12
+ core = createCore();
13
+ // Boot housekeeping: SQLite has no TTL indexes, so expired rows are
14
+ // purged explicitly at start-up.
15
+ void core.services.users.purgeExpired();
16
+ }
17
+ return core;
18
+ }
@@ -0,0 +1,21 @@
1
+ import { defineStore } from 'pinia'
2
+
3
+ export const useHeadingsStore = defineStore('layout/headings', () => {
4
+ // State
5
+ const headings = ref({
6
+ title: '',
7
+ description: '',
8
+ mainAction: ''
9
+ })
10
+
11
+ // Getters
12
+ const hasHeadings = computed(() => Object.values(headings.value).filter(val => !!val).length)
13
+
14
+ // Actions
15
+ function updateHeadings({ title, description, mainAction }) {
16
+ Object.assign(headings.value, { title, description, mainAction })
17
+ }
18
+
19
+ return { headings, hasHeadings, updateHeadings }
20
+ })
21
+
@@ -0,0 +1,80 @@
1
+ import { defineStore } from 'pinia'
2
+
3
+ const DEFAULT_FN = () => true
4
+
5
+ export const useModalStore = defineStore('layout/modal', () => {
6
+ // State
7
+ const _display = ref(false)
8
+ const _attributes = ref([])
9
+ const _details = ref({})
10
+ const _record = ref({})
11
+ const _updateRecord = ref({})
12
+ const _onSubmit = ref(DEFAULT_FN)
13
+
14
+ // Getters
15
+ const isEditing = computed(() => Object.keys(_record.value).includes('_id'))
16
+ const display = computed(() => _display.value)
17
+ const attributes = computed(() => _attributes.value)
18
+ const record = computed(() => _record.value)
19
+ const updatedRecord = computed(() => _updateRecord.value)
20
+ const title = computed(() => _details.value?.title)
21
+ const subTitle = computed(() => _details.value?.subTitle)
22
+ const closeButton = computed(() => _details.value?.closeButton || 'Fermer')
23
+ const acceptButton = computed(() => _details.value?.acceptButton || 'Créer')
24
+
25
+ // Actions
26
+ function open(data = { details: {}, record: {}, attributes: [] }) {
27
+ _details.value = { ...data.details }
28
+ _record.value = { ...data.record }
29
+ _updateRecord.value = { ...data.record }
30
+ _attributes.value = [...data.attributes]
31
+ _onSubmit.value = data.onSubmit || DEFAULT_FN
32
+ _display.value = true
33
+ }
34
+ function close () {
35
+ _details.value = {}
36
+ _record.value = {}
37
+ _updateRecord.value = {}
38
+ _attributes.value = []
39
+ _onSubmit.value = DEFAULT_FN
40
+ _display.value = false
41
+ }
42
+ /**
43
+ * Ce que le FORMULAIRE a modifié — et lui seul. Il n'émet que les champs
44
+ * qu'on a touchés, jamais l'enregistrement entier : c'est un accumulateur
45
+ * de changements, pas une copie de la fiche.
46
+ */
47
+ function updateRecord(data) {
48
+ _updateRecord.value = data
49
+ }
50
+
51
+ /**
52
+ * L'enregistrement tel qu'il est APRÈS édition : la fiche d'origine, sur
53
+ * laquelle on repose les champs modifiés.
54
+ *
55
+ * Soumettre le seul accumulateur revenait à remplacer la fiche par sa
56
+ * fraction changée. Modifier l'état d'une étape sans toucher au reste la
57
+ * réduisait à { _id, status } : titre, description et contenu, jamais
58
+ * émis parce que jamais touchés, disparaissaient à l'enregistrement.
59
+ */
60
+ async function submit () {
61
+ await _onSubmit.value({ ..._record.value, ..._updateRecord.value })
62
+ close()
63
+ }
64
+
65
+ return {
66
+ isEditing,
67
+ display,
68
+ attributes,
69
+ record,
70
+ updateRecord,
71
+ title,
72
+ subTitle,
73
+ closeButton,
74
+ acceptButton,
75
+ open,
76
+ close,
77
+ submit
78
+ }
79
+ })
80
+
@@ -0,0 +1,52 @@
1
+ import { defineStore } from 'pinia'
2
+
3
+ export const useNotificationsStore = defineStore('layout/notifications', () => {
4
+ // State
5
+ const _notifications = ref([])
6
+
7
+ // Getters
8
+ const notifications = computed(() => _notifications.value)
9
+
10
+ // Actions
11
+ /**
12
+ * Adds a notification to the notifications array.
13
+ *
14
+ * @param {Object} notification - The notification object to add.
15
+ * @param {string} notification.title - The title of the notification.
16
+ * @param {string} notification.type - The type of notification (warning / error / info / default)
17
+ * @param {string} [notification.description] - Optional description of the notification.
18
+ * @param {string} [notification.icon] - Optional icon for the notification.
19
+ * @param {Boolean} [notification.closeButton] - Optional close button for the notification.
20
+ * @param {number} notification.timeout - The timeout duration for the notification.
21
+ * @param {Function} [notification.callback] - Optional callback function for the notification.
22
+ * @param {string} [notification.color] - Optional color for the notification.
23
+ *
24
+ * @returns {Object} - The added notification object.
25
+ */
26
+ function add(notification) {
27
+ console.log('[NOTIFICATION] - add', notification)
28
+ Object.assign(notification, { id: new Date().getTime().toString() })
29
+
30
+ const index = notifications.value.findIndex((n) => n.id === notification.id)
31
+ if (index === -1) {
32
+ notifications.value.push(notification)
33
+ }
34
+
35
+ return notification
36
+ }
37
+
38
+ function close (id) {
39
+ console.log('[NOTIFICATION] - close')
40
+ const index = notifications.value.findIndex((n) => n.id === id)
41
+ if (index >= 0) {
42
+ notifications.value.splice(index, 1)
43
+ }
44
+ }
45
+
46
+ return {
47
+ notifications,
48
+ add,
49
+ close,
50
+ }
51
+ })
52
+
@@ -0,0 +1,80 @@
1
+ import { defineStore } from 'pinia'
2
+
3
+ // Single-tenant : les permissions dérivent du rôle Commun de l'utilisateur.
4
+ // `manage:all` est un joker CASL (toute action sur tout sujet).
5
+ //
6
+ // `redacteur` reprend EXACTEMENT le rôle legacy « Éditeur de contenu », que
7
+ // portaient tous les agents des collectivités. La première version n'avait
8
+ // retenu que les records : les agents perdaient la publication du site, la
9
+ // médiathèque et la lecture des collections — le bouton « Publier » avait donc
10
+ // disparu pour tous sauf les comptes admin.
11
+ const ROLE_PERMISSIONS = {
12
+ admin: ['manage:all'],
13
+ redacteur: [
14
+ 'entrance:self',
15
+ 'read:organizations',
16
+ 'read:collections',
17
+ 'read:records',
18
+ 'create:records',
19
+ 'update:records',
20
+ 'delete:records',
21
+ 'publish:records',
22
+ 'read:media',
23
+ 'create:media',
24
+ 'update:media',
25
+ 'delete:media',
26
+ 'read:deployments',
27
+ 'create:deployments'
28
+ ]
29
+ }
30
+
31
+ export const useWorkspaceStore = defineStore('layout/workspace', () => {
32
+ const $session = useSession()
33
+ const $models = useModels()
34
+ const $abilities = useAbility()
35
+
36
+ // State
37
+ const organization = ref(undefined)
38
+
39
+ // Getters
40
+ const workspace = computed(() => organization.value)
41
+ const workspaceId = computed(() => organization.value?._id)
42
+ const workspaceName = computed(() => organization.value?.name)
43
+ const workspacePath = computed(() => organization.value?.path)
44
+ const workspaceLogo = computed(() => organization.value?.logo)
45
+ const workspaceSettings = computed(() => organization.value?.settings)
46
+ const workspaceCollections = computed(() => organization.value?.collections)
47
+
48
+ // Actions
49
+ function updateAbilities() {
50
+ const user = $session.user.value
51
+ $abilities.assignAbilities([
52
+ 'entrance:self',
53
+ ...(ROLE_PERMISSIONS[user?.role] ?? [])
54
+ ])
55
+ }
56
+
57
+ async function updateOrganization(selectedOrganization) {
58
+ if (selectedOrganization) {
59
+ organization.value = selectedOrganization
60
+ await Promise.all([
61
+ $models.Record.list('labels', organization.value._id, { limit: 100 }),
62
+ $models.Collection.list(),
63
+ updateAbilities()
64
+ ])
65
+ }
66
+ }
67
+
68
+ return {
69
+ organization,
70
+ workspace,
71
+ workspaceId,
72
+ workspaceName,
73
+ workspacePath,
74
+ workspaceLogo,
75
+ workspaceSettings,
76
+ workspaceCollections,
77
+ updateOrganization,
78
+ updateAbilities,
79
+ }
80
+ })