@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.
- package/README.md +41 -0
- package/app.config.ts +10 -0
- package/app.vue +13 -0
- package/assets/css/main.css +13 -0
- package/components/data/display/row/data-avatar.vue +23 -0
- package/components/data/display/row/data-badge.vue +34 -0
- package/components/data/display/row/data-color.vue +17 -0
- package/components/data/display/row/data-display-badge-publish-status.vue +29 -0
- package/components/data/display/row/data-label.vue +37 -0
- package/components/data/display/row/data-last-update.vue +45 -0
- package/components/data/display/row/data-spacer.vue +4 -0
- package/components/data/display/row/data-summary.vue +44 -0
- package/components/data/display/row/data-text.vue +14 -0
- package/components/data/display/row/data-toggle.vue +16 -0
- package/components/data/display/row.vue +111 -0
- package/components/data/lists/data-list.vue +85 -0
- package/components/data/lists/data-lists-table.vue +155 -0
- package/components/elements/avatars/avatar-circular.vue +68 -0
- package/components/elements/avatars/avatar-with-text.vue +47 -0
- package/components/elements/badges/badge.vue +85 -0
- package/components/elements/buttons/primary.vue +32 -0
- package/components/elements/buttons/secondary.vue +35 -0
- package/components/elements/buttons/tertiary.vue +25 -0
- package/components/elements/dropdowns/dots.vue +75 -0
- package/components/elements/media/image.vue +82 -0
- package/components/elements/notifications/default.vue +90 -0
- package/components/forms/handlers/handler-collections.vue +189 -0
- package/components/forms/handlers/handler-publication-status.vue +193 -0
- package/components/forms/handlers/handler-records.vue +138 -0
- package/components/forms/handlers/handler-roles.vue +176 -0
- package/components/forms/handlers/handler-schedules.vue +218 -0
- package/components/forms/handlers/handler-steps.vue +147 -0
- package/components/forms/handlers/handler-tokens.vue +79 -0
- package/components/forms/inputs/input-color-picker.vue +159 -0
- package/components/forms/inputs/input-editor.vue +363 -0
- package/components/forms/inputs/input-files-media.vue +150 -0
- package/components/forms/inputs/input-files.vue +98 -0
- package/components/forms/inputs/input-json.vue +65 -0
- package/components/forms/inputs/input-location.vue +179 -0
- package/components/forms/inputs/input-text-area.vue +65 -0
- package/components/forms/inputs/input-text.vue +122 -0
- package/components/forms/inputs/input-toggle.vue +125 -0
- package/components/forms/inputs/media-section/media-editor.vue +95 -0
- package/components/forms/layouts/form.vue +203 -0
- package/components/forms/layouts/layout.vue +47 -0
- package/components/forms/layouts/modal.vue +96 -0
- package/components/forms/layouts/navigation.vue +48 -0
- package/components/forms/layouts/section.vue +118 -0
- package/components/forms/selects/select-collection-enum.vue +195 -0
- package/components/forms/selects/select-date.vue +245 -0
- package/components/forms/selects/select-enum.vue +186 -0
- package/components/forms/selects/select-record.vue +274 -0
- package/components/layout/headings/headings-collection.vue +29 -0
- package/components/layout/headings/headings-record.vue +33 -0
- package/components/layout/navigations/primary/dropdown-publish.vue +147 -0
- package/components/layout/navigations/primary.vue +132 -0
- package/components/layout/notifications/notifications-list.vue +19 -0
- package/composables/core/domains/use-definitions.ts +57 -0
- package/composables/core/domains/use-entries.ts +62 -0
- package/composables/core/domains/use-media.ts +82 -0
- package/composables/core/use-core.ts +12 -0
- package/composables/use-ability.js +38 -0
- package/composables/use-models.js +27 -0
- package/composables/use-session.ts +80 -0
- package/composables/use-timer.js +62 -0
- package/composables/use-trpc.js +17 -0
- package/editor/index.ts +44 -0
- package/editor/views/CalloutView.vue +22 -0
- package/editor/views/DetailsContentView.vue +11 -0
- package/editor/views/DetailsSummaryView.vue +11 -0
- package/editor/views/DetailsView.vue +72 -0
- package/editor/views/EmbedView.vue +53 -0
- package/editor/views/FileView.vue +79 -0
- package/editor/views/ImageView.vue +56 -0
- package/editor/views/UploadView.vue +71 -0
- package/layouts/default.vue +21 -0
- package/layouts/entrance.vue +10 -0
- package/layouts/record.vue +21 -0
- package/middleware/00.auth.global.js +16 -0
- package/middleware/01.instance.global.js +21 -0
- package/middleware/03.verify-permissions.global.js +11 -0
- package/middleware/04.prefetch-page.global.js +18 -0
- package/middleware/05.headings-page.global.js +25 -0
- package/models/_commun.js +243 -0
- package/models/_factory.js +34 -0
- package/models/collection.js +87 -0
- package/models/entrance.js +42 -0
- package/models/media.js +82 -0
- package/models/organization.js +92 -0
- package/models/record.js +165 -0
- package/models/role.js +66 -0
- package/models/token.js +56 -0
- package/models/user.js +97 -0
- package/nuxt.config.ts +151 -0
- package/package.json +71 -0
- package/pages/[collection]/[record]/index.vue +88 -0
- package/pages/[collection]/index.vue +55 -0
- package/pages/_lab/editor.vue +149 -0
- package/pages/auth/callback.vue +31 -0
- package/pages/index/index.vue +85 -0
- package/pages/index/password/define/[token].vue +79 -0
- package/pages/index/password/recover/index.vue +72 -0
- package/pages/index/welcome/[token].vue +90 -0
- package/pages/index.vue +44 -0
- package/pages/management/index.vue +15 -0
- package/pages/management/members.vue +176 -0
- package/pages/management/organizations.vue +192 -0
- package/pages/management/roles.vue +183 -0
- package/pages/overview.vue +15 -0
- package/pages/settings.vue +197 -0
- package/plugins/01.session.client.ts +5 -0
- package/plugins/02.vue-query.ts +25 -0
- package/public/favicon.ico +0 -0
- package/public/logo-black.svg +1 -0
- package/public/logo-white.svg +1 -0
- package/server/api/trpc/[...trpc].ts +28 -0
- package/server/api/v1/content/deployment.get.ts +45 -0
- package/server/api/v1/content/records.get.ts +12 -0
- package/server/middleware/0.security.ts +19 -0
- package/server/middleware/1.cors.ts +31 -0
- package/server/middleware/2.session.ts +20 -0
- package/server/middleware/3.api-token.ts +20 -0
- package/server/plugins/core.ts +8 -0
- package/server/routes/_tasks/[name].post.ts +20 -0
- package/server/routes/health.get.ts +6 -0
- package/server/tasks/deploy.ts +26 -0
- package/server/types.d.ts +8 -0
- package/server/utils/core.ts +18 -0
- package/store/layout/headings.js +21 -0
- package/store/layout/modal.js +80 -0
- package/store/layout/notifications.js +52 -0
- 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,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
|
+
})
|