domma-cms 0.10.0 → 0.13.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 (121) hide show
  1. package/CLAUDE.md +248 -159
  2. package/admin/css/admin.css +1 -1
  3. package/admin/js/api.js +1 -1
  4. package/admin/js/app.js +7 -3
  5. package/admin/js/config/sidebar-config.js +1 -1
  6. package/admin/js/http-interceptor.js +1 -0
  7. package/admin/js/lib/safe-html.js +1 -0
  8. package/admin/js/templates/documentation.html +611 -2
  9. package/admin/js/templates/layouts.html +5 -4
  10. package/admin/js/templates/notifications.html +14 -0
  11. package/admin/js/templates/plugin-marketplace.html +16 -0
  12. package/admin/js/templates/plugins.html +17 -5
  13. package/admin/js/views/index.js +1 -1
  14. package/admin/js/views/layouts.js +1 -16
  15. package/admin/js/views/notifications.js +1 -0
  16. package/admin/js/views/plugin-marketplace.js +1 -0
  17. package/admin/js/views/plugins.js +16 -16
  18. package/config/navigation.json +5 -72
  19. package/config/plugins.json +10 -14
  20. package/config/presets.json +50 -13
  21. package/config/site.json +11 -63
  22. package/package.json +2 -1
  23. package/plugins/_template/admin/templates/index.html +17 -0
  24. package/plugins/_template/admin/views/index.js +19 -0
  25. package/plugins/_template/config.js +8 -0
  26. package/plugins/_template/plugin.js +23 -0
  27. package/plugins/_template/plugin.json +34 -0
  28. package/plugins/analytics/plugin.json +41 -31
  29. package/plugins/blog/admin/templates/blog.html +22 -0
  30. package/plugins/blog/admin/templates/categories.html +7 -0
  31. package/plugins/blog/admin/templates/comments.html +11 -0
  32. package/plugins/blog/admin/templates/post-editor.html +97 -0
  33. package/plugins/blog/admin/templates/settings.html +11 -0
  34. package/plugins/blog/admin/views/blog.js +183 -0
  35. package/plugins/blog/admin/views/categories.js +235 -0
  36. package/plugins/blog/admin/views/comments.js +187 -0
  37. package/plugins/blog/admin/views/post-editor.js +291 -0
  38. package/plugins/blog/admin/views/settings.js +100 -0
  39. package/plugins/blog/collections/categories/schema.json +12 -0
  40. package/plugins/blog/collections/comments/schema.json +16 -0
  41. package/plugins/blog/collections/posts/schema.json +19 -0
  42. package/plugins/blog/config.js +8 -0
  43. package/plugins/blog/plugin.js +352 -0
  44. package/plugins/blog/plugin.json +96 -0
  45. package/plugins/blog/roles/blog-author.json +10 -0
  46. package/plugins/blog/roles/blog-editor.json +12 -0
  47. package/plugins/blog/templates/author.html +9 -0
  48. package/plugins/blog/templates/category.html +9 -0
  49. package/plugins/blog/templates/index.html +9 -0
  50. package/plugins/blog/templates/post.html +17 -0
  51. package/plugins/blog/templates/tag.html +9 -0
  52. package/plugins/contacts/collections/user-contact-groups/schema.json +1 -1
  53. package/plugins/contacts/collections/user-contacts/schema.json +1 -1
  54. package/plugins/contacts/plugin.js +4 -10
  55. package/plugins/contacts/plugin.json +13 -3
  56. package/plugins/notes/collections/user-notes/schema.json +1 -1
  57. package/plugins/notes/plugin.js +3 -9
  58. package/plugins/notes/plugin.json +13 -3
  59. package/plugins/site-search/plugin.json +5 -2
  60. package/plugins/theme-switcher/plugin.json +1 -1
  61. package/plugins/todo/collections/todos/schema.json +1 -1
  62. package/plugins/todo/plugin.js +3 -9
  63. package/plugins/todo/plugin.json +13 -3
  64. package/public/css/site.css +1 -1
  65. package/scripts/build.js +48 -0
  66. package/scripts/create-plugin.js +113 -0
  67. package/scripts/fresh.js +6 -7
  68. package/scripts/gen-instance-secret.js +46 -0
  69. package/scripts/reset.js +3 -3
  70. package/scripts/setup.js +31 -13
  71. package/server/middleware/auth.js +48 -0
  72. package/server/middleware/managerAuth.js +36 -0
  73. package/server/routes/api/actions.js +1 -1
  74. package/server/routes/api/auth.js +4 -3
  75. package/server/routes/api/layouts.js +173 -49
  76. package/server/routes/api/notifications.js +155 -0
  77. package/server/routes/api/plugin-marketplace.js +75 -0
  78. package/server/routes/api/users.js +1 -1
  79. package/server/routes/api/views.js +1 -1
  80. package/server/routes/public.js +4 -9
  81. package/server/server.js +32 -3
  82. package/server/services/actions.js +1 -1
  83. package/server/services/managerClient.js +182 -0
  84. package/server/services/markdown.js +52 -14
  85. package/server/services/permissionRegistry.js +245 -173
  86. package/server/services/pluginInstaller.js +301 -0
  87. package/server/services/plugins.js +117 -10
  88. package/server/services/presetCollections.js +66 -251
  89. package/server/services/renderer.js +99 -0
  90. package/server/services/roles.js +191 -39
  91. package/server/services/users.js +1 -1
  92. package/server/services/views.js +1 -1
  93. package/server/templates/page.html +2 -2
  94. package/plugins/docs/admin/templates/docs.html +0 -69
  95. package/plugins/docs/admin/views/docs.js +0 -276
  96. package/plugins/docs/config.js +0 -8
  97. package/plugins/docs/data/documents/57e003f0-68f2-47dc-9c36-ed4b10ed3deb.json +0 -11
  98. package/plugins/docs/data/folders.json +0 -9
  99. package/plugins/docs/data/templates.json +0 -1
  100. package/plugins/docs/data/versions/57e003f0-68f2-47dc-9c36-ed4b10ed3deb/1.json +0 -5
  101. package/plugins/docs/plugin.js +0 -375
  102. package/plugins/docs/plugin.json +0 -23
  103. package/plugins/form-builder/data/forms/contacts.json +0 -66
  104. package/plugins/form-builder/data/forms/enquiries.json +0 -103
  105. package/plugins/form-builder/data/forms/feedback.json +0 -131
  106. package/plugins/form-builder/data/forms/notes.json +0 -79
  107. package/plugins/form-builder/data/forms/to-do.json +0 -100
  108. package/plugins/form-builder/data/submissions/contacts.json +0 -1
  109. package/plugins/form-builder/data/submissions/enquiries.json +0 -1
  110. package/plugins/form-builder/data/submissions/feedback.json +0 -1
  111. package/plugins/form-builder/data/submissions/notes.json +0 -1
  112. package/plugins/form-builder/data/submissions/to-do.json +0 -1
  113. package/plugins/garage/admin/templates/garage.html +0 -111
  114. package/plugins/garage/admin/views/garage.js +0 -622
  115. package/plugins/garage/collections/garage-vehicles/schema.json +0 -101
  116. package/plugins/garage/config.js +0 -18
  117. package/plugins/garage/data/vehicles.json +0 -70
  118. package/plugins/garage/plugin.js +0 -398
  119. package/plugins/garage/plugin.json +0 -33
  120. package/scripts/seed.js +0 -1996
  121. package/server/services/userTypes.js +0 -227
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "PLUGIN_SLUG",
3
+ "displayName": "PLUGIN_DISPLAY_NAME",
4
+ "version": "1.0.0",
5
+ "description": "PLUGIN_DESCRIPTION",
6
+ "author": "PLUGIN_AUTHOR",
7
+ "date": "PLUGIN_DATE",
8
+ "icon": "box",
9
+ "admin": {
10
+ "css": ["admin/css/index.css"],
11
+ "sidebar": [
12
+ {
13
+ "id": "PLUGIN_SLUG",
14
+ "text": "PLUGIN_DISPLAY_NAME",
15
+ "icon": "box",
16
+ "url": "#/plugins/PLUGIN_SLUG",
17
+ "section": "#/plugins/PLUGIN_SLUG"
18
+ }
19
+ ],
20
+ "routes": [
21
+ {
22
+ "path": "/plugins/PLUGIN_SLUG",
23
+ "view": "plugin-PLUGIN_SLUG",
24
+ "title": "PLUGIN_DISPLAY_NAME - Domma CMS"
25
+ }
26
+ ],
27
+ "views": {
28
+ "plugin-PLUGIN_SLUG": {
29
+ "entry": "PLUGIN_SLUG/admin/views/index.js",
30
+ "exportName": "indexView"
31
+ }
32
+ }
33
+ }
34
+ }
@@ -1,35 +1,45 @@
1
1
  {
2
2
  "name": "analytics",
3
- "displayName": "Analytics",
4
- "version": "1.0.0",
5
- "description": "Basic page view analytics. Tracks hits per page using a simple JSON store.",
6
- "author": "Darryl Waterhouse",
7
- "date": "2026-03-01",
8
- "icon": "chart-bar",
9
- "admin": {
10
- "sidebar": [
11
- { "id": "analytics", "text": "Analytics", "icon": "chart-bar", "url": "#/plugins/analytics", "section": "#/plugins/analytics" }
12
- ],
13
- "routes": [
14
- { "path": "/plugins/analytics", "view": "plugin-analytics", "title": "Analytics - Domma CMS" }
15
- ],
16
- "views": {
17
- "plugin-analytics": {
18
- "entry": "analytics/admin/views/analytics.js",
19
- "exportName": "analyticsView"
20
- }
21
- }
22
- },
23
- "inject": {
24
- "head": "public/inject-head.html",
25
- "bodyEnd": "public/inject-body.html"
26
- },
27
- "scaffold": {
28
- "reset": [
29
- {
30
- "path": "stats.json",
31
- "content": "{}"
32
- }
33
- ]
3
+ "displayName": "Analytics",
4
+ "version": "1.0.0",
5
+ "description": "Basic page view analytics. Tracks hits per page using a simple JSON store.",
6
+ "author": "Darryl Waterhouse",
7
+ "date": "2026-03-01",
8
+ "icon": "chart-bar",
9
+ "admin": {
10
+ "sidebar": [
11
+ {
12
+ "id": "analytics",
13
+ "text": "Analytics",
14
+ "icon": "chart-bar",
15
+ "url": "#/plugins/analytics",
16
+ "section": "#/plugins/analytics"
17
+ }
18
+ ],
19
+ "routes": [
20
+ {
21
+ "path": "/plugins/analytics",
22
+ "view": "plugin-analytics",
23
+ "title": "Analytics - Domma CMS"
24
+ }
25
+ ],
26
+ "views": {
27
+ "plugin-analytics": {
28
+ "entry": "analytics/admin/views/analytics.js?v=80339cdc",
29
+ "exportName": "analyticsView"
30
+ }
34
31
  }
32
+ },
33
+ "inject": {
34
+ "head": "public/inject-head.html",
35
+ "bodyEnd": "public/inject-body.html"
36
+ },
37
+ "scaffold": {
38
+ "reset": [
39
+ {
40
+ "path": "stats.json",
41
+ "content": "{}"
42
+ }
43
+ ]
44
+ }
35
45
  }
@@ -0,0 +1,22 @@
1
+ <div class="view-header d-flex align-items-center justify-content-between mb-4">
2
+ <h1 class="h3 mb-0">Blog Posts</h1>
3
+ <button id="btn-new-post" class="btn btn-primary">
4
+ <span data-icon="plus"></span> New Post
5
+ </button>
6
+ </div>
7
+
8
+ <div class="card mb-3">
9
+ <div class="card-body">
10
+ <div class="d-flex gap-2 flex-wrap">
11
+ <select id="filter-status" class="form-select" style="width:auto;">
12
+ <option value="">All statuses</option>
13
+ <option value="draft">Draft</option>
14
+ <option value="scheduled">Scheduled</option>
15
+ <option value="published">Published</option>
16
+ </select>
17
+ <input id="filter-search" type="text" class="form-control" style="width:200px;" placeholder="Search posts…">
18
+ </div>
19
+ </div>
20
+ </div>
21
+
22
+ <div id="posts-table"></div>
@@ -0,0 +1,7 @@
1
+ <div class="view-header d-flex align-items-center justify-content-between mb-4">
2
+ <h1 class="h3 mb-0">Blog Categories</h1>
3
+ <button id="btn-new-category" class="btn btn-primary">
4
+ <span data-icon="plus"></span> New Category
5
+ </button>
6
+ </div>
7
+ <div id="categories-table"></div>
@@ -0,0 +1,11 @@
1
+ <div class="view-header mb-4">
2
+ <h1 class="h3 mb-0">Blog Comments</h1>
3
+ </div>
4
+
5
+ <div class="tab-list mb-3">
6
+ <button class="tab-item active" data-tab="pending">Pending</button>
7
+ <button class="tab-item" data-tab="approved">Approved</button>
8
+ <button class="tab-item" data-tab="spam">Spam</button>
9
+ </div>
10
+
11
+ <div id="comments-table"></div>
@@ -0,0 +1,97 @@
1
+ <div class="view-header d-flex align-items-center justify-content-between mb-4">
2
+ <h1 class="h3 mb-0" id="editor-title">New Post</h1>
3
+ <div class="d-flex gap-2">
4
+ <button id="btn-save-draft" class="btn btn-secondary">Save Draft</button>
5
+ <button id="btn-schedule" class="btn btn-warning" style="display:none;">Schedule</button>
6
+ <button id="btn-publish" class="btn btn-primary">Publish Now</button>
7
+ </div>
8
+ </div>
9
+
10
+ <div class="d-flex gap-4" style="align-items:flex-start;">
11
+ <!-- Main content area -->
12
+ <div style="flex:1; min-width:0;">
13
+ <div class="card mb-3">
14
+ <div class="card-body">
15
+ <div class="mb-3">
16
+ <label class="form-label fw-bold">Title</label>
17
+ <input type="text" id="post-title" class="form-control form-control-lg" placeholder="Post title…">
18
+ </div>
19
+ <div class="mb-3">
20
+ <label class="form-label">Slug</label>
21
+ <div class="d-flex gap-2 align-items-center">
22
+ <input type="text" id="post-slug" class="form-control font-monospace">
23
+ <label class="d-flex align-items-center gap-1 text-muted small" style="white-space:nowrap;">
24
+ <input type="checkbox" id="slug-auto"> Auto
25
+ </label>
26
+ </div>
27
+ <div id="slug-warning" class="text-warning small mt-1" style="display:none;">
28
+ ⚠ Changing slug breaks existing links
29
+ </div>
30
+ </div>
31
+ <div class="mb-3">
32
+ <label class="form-label">Excerpt</label>
33
+ <textarea id="post-excerpt" class="form-control" rows="2" placeholder="Short summary…"></textarea>
34
+ </div>
35
+ <div class="mb-3">
36
+ <label class="form-label">Content</label>
37
+ <textarea id="post-content" class="form-control font-monospace" rows="20" placeholder="Write in Markdown…"></textarea>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <!-- SEO accordion -->
43
+ <div class="card mb-3">
44
+ <div class="card-header" id="seo-toggle" style="cursor:pointer;">
45
+ SEO <span class="text-muted small">(click to expand)</span>
46
+ </div>
47
+ <div class="card-body" id="seo-panel" style="display:none;">
48
+ <div class="mb-3">
49
+ <label class="form-label">SEO Title</label>
50
+ <input type="text" id="seo-title" class="form-control" placeholder="Overrides page title">
51
+ </div>
52
+ <div class="mb-3">
53
+ <label class="form-label">SEO Description</label>
54
+ <textarea id="seo-description" class="form-control" rows="2" placeholder="Overrides excerpt for meta description"></textarea>
55
+ </div>
56
+ </div>
57
+ </div>
58
+ </div>
59
+
60
+ <!-- Sidebar -->
61
+ <div style="width:280px; flex-shrink:0;">
62
+ <div class="card mb-3">
63
+ <div class="card-header">Status</div>
64
+ <div class="card-body">
65
+ <div id="schedule-picker" style="display:none;" class="mb-3">
66
+ <label class="form-label">Scheduled for</label>
67
+ <input type="datetime-local" id="post-scheduled-at" class="form-control">
68
+ </div>
69
+ <span id="post-status-badge" class="badge bg-secondary">draft</span>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="card mb-3">
74
+ <div class="card-header">Categories</div>
75
+ <div class="card-body" id="categories-checklist">
76
+ <p class="text-muted small">Loading…</p>
77
+ </div>
78
+ </div>
79
+
80
+ <div class="card mb-3">
81
+ <div class="card-header">Tags</div>
82
+ <div class="card-body">
83
+ <input type="text" id="post-tags" class="form-control" placeholder="tag1, tag2, …">
84
+ <div class="text-muted small mt-1">Comma-separated</div>
85
+ </div>
86
+ </div>
87
+
88
+ <div class="card mb-3">
89
+ <div class="card-header">Featured Image</div>
90
+ <div class="card-body">
91
+ <div id="featured-image-preview" class="mb-2"></div>
92
+ <button id="btn-pick-image" class="btn btn-sm btn-secondary">Pick Image</button>
93
+ <button id="btn-clear-image" class="btn btn-sm btn-outline-secondary" style="display:none;">Clear</button>
94
+ </div>
95
+ </div>
96
+ </div>
97
+ </div>
@@ -0,0 +1,11 @@
1
+ <div class="view-header mb-4">
2
+ <h1 class="h3 mb-0">Blog Settings</h1>
3
+ </div>
4
+
5
+ <div class="alert alert-warning mb-4">
6
+ Base path and posts-per-page changes require a server restart to take effect.
7
+ </div>
8
+
9
+ <div class="card" style="max-width:600px;">
10
+ <div class="card-body" id="settings-form"></div>
11
+ </div>
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Blog posts list view.
3
+ *
4
+ * @module blog/admin/views/blog
5
+ */
6
+
7
+ function escapeHtml(s) {
8
+ return String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#39;');
9
+ }
10
+
11
+ const BASE = '/api/plugins/blog';
12
+
13
+ const STATUS_BADGE = {
14
+ draft: 'badge-secondary',
15
+ scheduled: 'badge-warning',
16
+ published: 'badge-success'
17
+ };
18
+
19
+ export const blogView = {
20
+ templateUrl: '/plugins/blog/admin/templates/blog.html',
21
+
22
+ /**
23
+ * Mount the blog posts list view.
24
+ *
25
+ * @param {object} $container - Domma-wrapped container element
26
+ * @returns {Promise<void>}
27
+ */
28
+ async onMount($container) {
29
+ let allPosts = [];
30
+ let postsTable = null;
31
+
32
+ // ---------------------------------------------------------------
33
+ // Table
34
+ // ---------------------------------------------------------------
35
+
36
+ function buildTable(data) {
37
+ const tableEl = $container.find('#posts-table').get(0);
38
+ if (!tableEl) return;
39
+
40
+ if (postsTable) {
41
+ postsTable.setData(data);
42
+ return;
43
+ }
44
+
45
+ postsTable = T.create(tableEl, {
46
+ data,
47
+ columns: [
48
+ {
49
+ key: 'title',
50
+ title: 'Title',
51
+ sortable: true,
52
+ render: (v, row) =>
53
+ `<a href="#/plugins/blog/posts/${escapeHtml(row.id)}" style="font-weight:600;">${escapeHtml(v || 'Untitled')}</a>`
54
+ },
55
+ {
56
+ key: 'status',
57
+ title: 'Status',
58
+ sortable: true,
59
+ render: (v) => {
60
+ const cls = STATUS_BADGE[v] || 'badge-secondary';
61
+ return `<span class="badge ${cls}">${escapeHtml(v ?? 'draft')}</span>`;
62
+ }
63
+ },
64
+ {
65
+ key: 'authorId',
66
+ title: 'Author',
67
+ sortable: false,
68
+ render: (v) => escapeHtml(v) || '—'
69
+ },
70
+ {
71
+ key: 'publishedAt',
72
+ title: 'Published',
73
+ sortable: true,
74
+ render: (v) => v
75
+ ? `<span title="${escapeHtml(D(v).format('DD MMM YYYY HH:mm'))}">${escapeHtml(D(v).format('DD MMM YYYY'))}</span>`
76
+ : '<span style="opacity:0.4;">—</span>'
77
+ },
78
+ {
79
+ key: 'id',
80
+ title: 'Actions',
81
+ sortable: false,
82
+ render: (id) =>
83
+ `<div style="display:flex;gap:.4rem;">` +
84
+ `<button class="btn btn-sm btn-outline post-edit-btn" data-id="${escapeHtml(id)}">` +
85
+ `<span data-icon="edit" data-icon-size="14"></span> Edit</button>` +
86
+ `<button class="btn btn-sm btn-danger post-delete-btn" data-id="${escapeHtml(id)}">` +
87
+ `<span data-icon="trash" data-icon-size="14"></span> Delete</button>` +
88
+ `</div>`
89
+ }
90
+ ],
91
+ emptyMessage: 'No posts found.'
92
+ });
93
+
94
+ // Event delegation for action buttons
95
+ tableEl.addEventListener('click', async (e) => {
96
+ const editBtn = e.target.closest('.post-edit-btn');
97
+ const deleteBtn = e.target.closest('.post-delete-btn');
98
+
99
+ if (editBtn) {
100
+ R.navigate('#/plugins/blog/posts/' + editBtn.dataset.id);
101
+ }
102
+
103
+ if (deleteBtn) {
104
+ const id = deleteBtn.dataset.id;
105
+ const post = allPosts.find((p) => p.id === id);
106
+ const ok = await E.confirm(`Delete "${post?.title ?? 'this post'}"?`);
107
+ if (!ok) return;
108
+ try {
109
+ await H.delete(`${BASE}/posts/${id}`);
110
+ E.toast('Post deleted.', { type: 'success' });
111
+ await reload();
112
+ } catch {
113
+ E.toast('Failed to delete post.', { type: 'error' });
114
+ }
115
+ }
116
+ });
117
+
118
+ Domma.icons.scan();
119
+ }
120
+
121
+ // ---------------------------------------------------------------
122
+ // Filter
123
+ // ---------------------------------------------------------------
124
+
125
+ function applyFilters() {
126
+ const statusEl = $container.find('#filter-status').get(0);
127
+ const searchEl = $container.find('#filter-search').get(0);
128
+ const status = statusEl?.value ?? '';
129
+ const query = (searchEl?.value ?? '').toLowerCase().trim();
130
+
131
+ let filtered = allPosts;
132
+ if (status) filtered = filtered.filter((p) => p.status === status);
133
+ if (query) {
134
+ filtered = filtered.filter((p) =>
135
+ (p.title ?? '').toLowerCase().includes(query) ||
136
+ (p.excerpt ?? '').toLowerCase().includes(query)
137
+ );
138
+ }
139
+ buildTable(filtered);
140
+ }
141
+
142
+ // ---------------------------------------------------------------
143
+ // Load
144
+ // ---------------------------------------------------------------
145
+
146
+ async function reload() {
147
+ try {
148
+ const res = await H.get(`${BASE}/posts`);
149
+ if (res.error) throw new Error(res.error);
150
+ allPosts = res.data ?? [];
151
+ } catch {
152
+ allPosts = [];
153
+ E.toast('Failed to load posts.', { type: 'error' });
154
+ }
155
+ applyFilters();
156
+ }
157
+
158
+ // ---------------------------------------------------------------
159
+ // Event bindings
160
+ // ---------------------------------------------------------------
161
+
162
+ const newPostBtn = $container.find('#btn-new-post').get(0);
163
+ if (newPostBtn) newPostBtn.addEventListener('click', () => R.navigate('#/plugins/blog/posts/new'));
164
+
165
+ const filterStatus = $container.find('#filter-status').get(0);
166
+ if (filterStatus) filterStatus.addEventListener('change', applyFilters);
167
+
168
+ const filterSearch = $container.find('#filter-search').get(0);
169
+ if (filterSearch) {
170
+ let searchTimer = null;
171
+ filterSearch.addEventListener('input', () => {
172
+ clearTimeout(searchTimer);
173
+ searchTimer = setTimeout(applyFilters, 250);
174
+ });
175
+ }
176
+
177
+ // ---------------------------------------------------------------
178
+ // Initial load
179
+ // ---------------------------------------------------------------
180
+ await reload();
181
+ Domma.icons.scan();
182
+ }
183
+ };