asljs-part-srv 0.1.1 → 0.1.2

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 CHANGED
@@ -9,8 +9,8 @@
9
9
  editor in the browser, as a tree of folders, each opened as a tab of its
10
10
  details - path, size, modification time - and the artefact definitions that
11
11
  match it. Each `part.json` of the working directory, else of its folders, is a
12
- project, chosen in a list box. It reads a project on every request and never
13
- serves the content of a file.
12
+ project, chosen in a selector that finds it by its title. It reads a project on
13
+ every request and never serves the content of a file.
14
14
 
15
15
  ## Installation
16
16
 
package/dist/app.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { type EditorApp, type EditorAppContext } from 'asljs-appbase';
2
2
  /**
3
3
  * The part application as an editor plugin: its projects, by `part.json`.
4
- * Each `part.json` `findProjects` finds from the working directory is a
4
+ * Each `part.json` `findAppProjects` finds from the working directory is a
5
5
  * project. A project's root, definitions and state folder come from its own
6
6
  * `part.json` only, else the default, when it is first requested; no option
7
7
  * or environment variable sets them, since one value cannot suit every
package/dist/app.js CHANGED
@@ -1,15 +1,34 @@
1
- import { configPath, configStrings, createEditorApp, findBrowserModules, findProjects, stateFolder } from 'asljs-appbase';
1
+ import { configPath, configStrings, createEditorApp, findAppProjects, findBrowserModules, stateFolder } from 'asljs-appbase';
2
2
  import { CONFIG_FILE, resolveDefinitionSource } from 'asljs-part';
3
3
  import { ICONS } from 'asljs-ui-editor';
4
4
  import path from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { createProjectHandler } from './view.js';
7
7
  /**
8
- * The style of an artefact's details: a scrolling view of the tab, its
9
- * table of the location, path, size and modification time, and its list of
10
- * definitions.
8
+ * The style of an artefact's details: the tab, the details with the preview
9
+ * of a file on their right and an action bar along its bottom, as the graph
10
+ * of `rq-srv` has them; a scrolling view of the details, their table of the
11
+ * location, path, size and modification time, and their list of
12
+ * definitions; and the file's content in the preview.
11
13
  */
12
- const STYLE = `.part-view { height: 100%; box-sizing: border-box; overflow: auto; padding: 0.75rem 1rem; line-height: 1.5; }
14
+ const STYLE = `.part-document { height: 100%; display: flex; flex-direction: column; background: #fff; }
15
+ .part-stage { flex: 1; min-height: 0; display: flex; }
16
+ .part-view { flex: 1; min-width: 0; box-sizing: border-box; overflow: auto; padding: 0.75rem 1rem; line-height: 1.5; }
17
+ asljs-diagram-preview { width: min(40rem, 45vw); border-left: 1px solid #ddd; background: #fff; }
18
+ .asljs-diagram-preview-splitter { background: transparent; transition: background 0.15s; }
19
+ .asljs-diagram-preview-splitter:hover, .asljs-diagram-preview-splitter:focus-visible { background: #1a73e8; outline: none; }
20
+ .asljs-diagram-preview-placeholder, .asljs-diagram-preview-body > .asljs-plugin-note { margin: 1rem; color: #757575; }
21
+ .part-actionbar { display: flex; align-items: center; justify-content: flex-end; padding: 2px 8px; background: #f3f4f6; border-top: 1px solid #ddd; }
22
+ .asljs-diagram-preview-tabs { gap: 2px; }
23
+ .asljs-diagram-preview-tab { border: 0; border-radius: 4px; background: none; padding: 3px 10px; font: inherit; color: #555; cursor: pointer; }
24
+ .asljs-diagram-preview-tab:hover:not(:disabled) { background: #e5e7eb; }
25
+ .asljs-diagram-preview-tab[aria-selected="true"] { background: #dbe7fd; color: #1a73e8; }
26
+ .asljs-diagram-preview-toggle { width: 28px; height: 28px; display: grid; place-items: center; border: 0; border-radius: 4px; background: none; color: #555; cursor: pointer; }
27
+ .asljs-diagram-preview-toggle:hover:not(:disabled) { background: #e5e7eb; }
28
+ .asljs-diagram-preview-toggle[aria-expanded="true"] { color: #1a73e8; }
29
+ .part-content { flex: 1; min-height: 0; overflow: auto; margin: 0; padding: 0.75rem 1rem; box-sizing: border-box; }
30
+ .part-text { font-family: ui-monospace, monospace; font-size: 0.85rem; line-height: 1.45; white-space: pre; }
31
+ .part-image { max-width: 100%; }
13
32
  .part-details { border-collapse: collapse; margin: 0 0 1rem; }
14
33
  .part-details th { text-align: left; color: #555; font-weight: normal; padding: 2px 1rem 2px 0; vertical-align: top; }
15
34
  .part-details td { padding: 2px 0; word-break: break-all; }
@@ -18,15 +37,16 @@ const STYLE = `.part-view { height: 100%; box-sizing: border-box; overflow: auto
18
37
  .part-note { color: #757575; }`;
19
38
  /**
20
39
  * The part application as an editor plugin: its projects, by `part.json`.
21
- * Each `part.json` `findProjects` finds from the working directory is a
40
+ * Each `part.json` `findAppProjects` finds from the working directory is a
22
41
  * project. A project's root, definitions and state folder come from its own
23
42
  * `part.json` only, else the default, when it is first requested; no option
24
43
  * or environment variable sets them, since one value cannot suit every
25
44
  * project.
26
45
  */
27
46
  export async function createApp(context) {
28
- const projects = await findProjects(context.cwd, CONFIG_FILE, context.files);
29
- return createEditorApp({ id: 'part',
47
+ const projects = await findAppProjects(context, CONFIG_FILE, { required: true });
48
+ return createEditorApp({
49
+ id: 'part',
30
50
  title: 'Part',
31
51
  icon: ICONS.document,
32
52
  base: context.base,
@@ -35,8 +55,11 @@ export async function createApp(context) {
35
55
  plugin: fileURLToPath(new URL('./browser/plugin.js', import.meta.url)),
36
56
  // The page loads the plugin's packages, and what they import, from
37
57
  // the server.
38
- modules: findBrowserModules(['asljs-ui-editor'], path.dirname(fileURLToPath(import.meta.url))),
39
- style: STYLE });
58
+ modules: findBrowserModules(['asljs-ui-diagram',
59
+ 'asljs-ui-editor',
60
+ 'asljs-ui-markdown'], path.dirname(fileURLToPath(import.meta.url))),
61
+ style: STYLE
62
+ });
40
63
  }
41
64
  /**
42
65
  * The project part reads in an application project: `project`,
@@ -5,7 +5,9 @@ import { type EditorPluginFactory } from 'asljs-ui-editor';
5
5
  * `_/data.json` once, when it is made. Its Artefacts operation is a
6
6
  * `pathTree` of their locations, grouped by `locationSegments`, its groups
7
7
  * folded. Choosing an entry opens its details as a document whose id is
8
- * `part:<location>`, rendered here from `_/artefact.json`. Its search finds
8
+ * `part:<location>`, rendered here from `_/artefact.json`, with the preview
9
+ * of a file on their right, `previewOf`, in an `asljs-diagram-preview`
10
+ * whose state, shared by the tabs, this browser keeps. Its search finds
9
11
  * artefacts by location (`searchMatcher`), at most `MAX_RESULTS` of them: a
10
12
  * file by its path, with or without `file:`, and another artefact by its
11
13
  * location. Every URL is relative to the project's `base`.
@@ -1,16 +1,29 @@
1
- import { element, fetchJson, ICONS, note, pathTree, searchMatcher, tree } from 'asljs-ui-editor';
1
+ import { observable } from 'asljs-observable';
2
+ import { createPreviewModel } from 'asljs-ui-diagram';
3
+ import { element, fetchJson, ICONS, note, pathHref, pathTree, projectPath, searchMatcher, tree } from 'asljs-ui-editor';
4
+ import { markdownDocument } from 'asljs-ui-markdown';
2
5
  import { locationSegments } from './locations.js';
3
6
  /**
4
7
  * How many matches a search shows.
5
8
  */
6
9
  const MAX_RESULTS = 200;
10
+ /**
11
+ * Where the browser keeps the state of the details' preview.
12
+ */
13
+ const LAYOUT_KEY = 'part-view-layout';
14
+ /**
15
+ * The largest file the preview shows, in bytes.
16
+ */
17
+ const PREVIEW_LIMIT = 1024 * 1024;
7
18
  /**
8
19
  * The plugin of a project, the browser module of `asljs-ui-editor` the
9
20
  * server serves. It loads the project's files and other artefacts from
10
21
  * `_/data.json` once, when it is made. Its Artefacts operation is a
11
22
  * `pathTree` of their locations, grouped by `locationSegments`, its groups
12
23
  * folded. Choosing an entry opens its details as a document whose id is
13
- * `part:<location>`, rendered here from `_/artefact.json`. Its search finds
24
+ * `part:<location>`, rendered here from `_/artefact.json`, with the preview
25
+ * of a file on their right, `previewOf`, in an `asljs-diagram-preview`
26
+ * whose state, shared by the tabs, this browser keeps. Its search finds
14
27
  * artefacts by location (`searchMatcher`), at most `MAX_RESULTS` of them: a
15
28
  * file by its path, with or without `file:`, and another artefact by its
16
29
  * location. Every URL is relative to the project's `base`.
@@ -30,11 +43,82 @@ const createPlugin = async ({ id, base, model }) => {
30
43
  document: location => documentOf(location, locationSegments(location).at(-1)),
31
44
  open: false }));
32
45
  };
46
+ // The preview of the details: whether it is shown, its mode and its
47
+ // width, kept in this browser.
48
+ let stored = {};
49
+ try {
50
+ stored =
51
+ JSON.parse(localStorage.getItem(LAYOUT_KEY) ?? '{}');
52
+ }
53
+ catch {
54
+ // Without storage, the preview starts open.
55
+ }
56
+ const preview = createPreviewModel(stored.preview
57
+ ?? { open: true,
58
+ mode: 'view' });
59
+ preview.on('change', () => {
60
+ try {
61
+ localStorage.setItem(LAYOUT_KEY, JSON.stringify({ preview: { open: preview.open,
62
+ mode: preview.mode,
63
+ width: preview.width } }));
64
+ }
65
+ catch {
66
+ // Without storage, the preview lasts until the page is reloaded.
67
+ }
68
+ });
69
+ // A link of a previewed markdown file to another file of the project
70
+ // opens that file's details.
71
+ const openLink = (url) => {
72
+ const relative = projectPath(base, url);
73
+ const file = relative === null
74
+ ? undefined
75
+ : files.find(candidate => candidate.replaceAll('\\', '/')
76
+ === relative);
77
+ if (file === undefined) {
78
+ return false;
79
+ }
80
+ model.open(documentOf(`file:${file}`, locationSegments(`file:${file}`).at(-1)));
81
+ return true;
82
+ };
33
83
  const renderDocument = (opened) => {
34
84
  const { location } = opened.data;
35
- const view = element('div', 'part-view');
85
+ const view = element('div', 'part-document');
86
+ const details = element('div', 'part-view');
87
+ // The panel shows the file of the details, once they are read: its
88
+ // selection is the file's path, and none for another artefact.
89
+ const shown = observable({ selection: [] });
90
+ let file = null;
91
+ const modes = [{ id: 'view',
92
+ title: 'View',
93
+ isEnabled: selection => selection.length === 1
94
+ && file !== null,
95
+ render: async () => await previewOf(base, file, openLink) }];
96
+ const panel = document.createElement('asljs-diagram-preview');
97
+ panel.placeholder = '';
98
+ panel.modes = modes;
99
+ panel.model = shown;
100
+ panel.preview = preview;
101
+ const stage = element('div', 'part-stage');
102
+ stage.append(details, panel);
103
+ const tabs = document.createElement('asljs-diagram-preview-tabs');
104
+ tabs.modes = modes;
105
+ tabs.preview = preview;
106
+ const bar = element('div', 'part-actionbar');
107
+ bar.append(tabs);
108
+ view.append(stage, bar);
36
109
  void fetchJson(base, `_/artefact.json?location=${encodeURIComponent(location)}`)
37
- .then(details => view.append(...detailsView(details)), (error) => view.append(note(error instanceof Error
110
+ .then((read) => {
111
+ details.append(...detailsView(read));
112
+ if (read.path === null) {
113
+ panel.placeholder =
114
+ 'Only a file has a preview.';
115
+ }
116
+ else {
117
+ file = read;
118
+ }
119
+ shown.selection =
120
+ [location];
121
+ }, (error) => details.append(note(error instanceof Error
38
122
  ? error.message
39
123
  : String(error))));
40
124
  return view;
@@ -47,6 +131,9 @@ const createPlugin = async ({ id, base, model }) => {
47
131
  icon: ICONS.document,
48
132
  render: artefactsPanel }],
49
133
  renderDocument,
134
+ documentOf: location => documentOf(location, location
135
+ .split(/[\\/]/)
136
+ .at(-1)),
50
137
  search: async (query) => {
51
138
  const matches = searchMatcher(query);
52
139
  return [...files
@@ -86,6 +173,61 @@ function detailsView(details) {
86
173
  element('h2', undefined, 'Definitions'),
87
174
  definitionsView(details.definitions)];
88
175
  }
176
+ /**
177
+ * The preview of a file, read from the server as it is: an image as an image,
178
+ * a markdown file rendered, `openLink` opening its links to other files, and
179
+ * other text as it is; a note for a binary file, one over `PREVIEW_LIMIT`,
180
+ * and one the server does not serve.
181
+ */
182
+ async function previewOf(base, file, openLink) {
183
+ if ((file.size
184
+ ?? 0)
185
+ > PREVIEW_LIMIT) {
186
+ return note(`The file is too large to preview, ${file.size} bytes.`);
187
+ }
188
+ const url = new URL(pathHref(base, file.path), location.href);
189
+ try {
190
+ const response = await fetch(url);
191
+ if (!response.ok) {
192
+ return note(`The file has no preview: ${response.status} ${response.statusText}`
193
+ .trim());
194
+ }
195
+ if ((response.headers.get('content-type') ?? '')
196
+ .startsWith('image/')) {
197
+ const image = element('img', 'part-image');
198
+ image.src = url.href;
199
+ image.alt = file.name;
200
+ const frame = element('div', 'part-content');
201
+ frame.append(image);
202
+ return frame;
203
+ }
204
+ let text;
205
+ try {
206
+ text =
207
+ new TextDecoder('utf-8', { fatal: true })
208
+ .decode(await response.arrayBuffer());
209
+ }
210
+ catch {
211
+ text = '\0';
212
+ }
213
+ if (text.includes('\0')) {
214
+ return note('A binary file has no preview.');
215
+ }
216
+ if (/\.md$/i.test(file.path)) {
217
+ return markdownDocument({ url,
218
+ load: async () => ({ body: text }),
219
+ openLink });
220
+ }
221
+ const code = element('pre', 'part-content part-text');
222
+ code.append(element('code', undefined, text));
223
+ return code;
224
+ }
225
+ catch (error) {
226
+ return note(error instanceof Error
227
+ ? error.message
228
+ : String(error));
229
+ }
230
+ }
89
231
  function row(label, value) {
90
232
  const made = element('tr');
91
233
  const cell = element('td');
package/dist/project.js CHANGED
@@ -1,5 +1,5 @@
1
- import { isInside } from 'asljs-locator';
2
1
  import { toDisplayPath } from 'asljs-appbase';
2
+ import { isInside } from 'asljs-locator';
3
3
  import { FILE_SCHEME, providersFactory } from 'asljs-part';
4
4
  import { stat } from 'node:fs/promises';
5
5
  import path from 'node:path';
package/dist/view.d.ts CHANGED
@@ -13,8 +13,14 @@ export declare const DATA_PATH = "/_/data.json";
13
13
  /**
14
14
  * The handler of a project, relative to where it is mounted. It answers
15
15
  * data, which the plugin renders: `DATA_PATH` the files and the other
16
- * artefacts of the project, and `ARTEFACT_PATH` the details of an artefact,
17
- * or 404 with `{ error }` saying why there is none. No file of the project
18
- * is served; the project is read on every request.
16
+ * artefacts of the project, `ARTEFACT_PATH` the details of an artefact, or
17
+ * 404 with `{ error }` saying why there is none, and a file of the project
18
+ * root by its path, as it is, for the preview, unless `isServed` refuses it.
19
+ * The project is read on every request.
19
20
  */
20
21
  export declare function createProjectHandler(project: Project): RequestHandler;
22
+ /**
23
+ * Whether a file of the project is served, by its path relative to the
24
+ * root: not one in a dot folder, a dot file or one under `node_modules`.
25
+ */
26
+ export declare function isServed(relative: string): boolean;
package/dist/view.js CHANGED
@@ -13,9 +13,10 @@ export const DATA_PATH = '/_/data.json';
13
13
  /**
14
14
  * The handler of a project, relative to where it is mounted. It answers
15
15
  * data, which the plugin renders: `DATA_PATH` the files and the other
16
- * artefacts of the project, and `ARTEFACT_PATH` the details of an artefact,
17
- * or 404 with `{ error }` saying why there is none. No file of the project
18
- * is served; the project is read on every request.
16
+ * artefacts of the project, `ARTEFACT_PATH` the details of an artefact, or
17
+ * 404 with `{ error }` saying why there is none, and a file of the project
18
+ * root by its path, as it is, for the preview, unless `isServed` refuses it.
19
+ * The project is read on every request.
19
20
  */
20
21
  export function createProjectHandler(project) {
21
22
  return createHandler({ folder: project.root,
@@ -28,5 +29,15 @@ export function createProjectHandler(project) {
28
29
  return jsonReply(404, { error: `${messageOf(error)}.` });
29
30
  }
30
31
  } },
31
- allow: () => false });
32
+ allow: isServed });
33
+ }
34
+ /**
35
+ * Whether a file of the project is served, by its path relative to the
36
+ * root: not one in a dot folder, a dot file or one under `node_modules`.
37
+ */
38
+ export function isServed(relative) {
39
+ return !relative
40
+ .split('/')
41
+ .some(segment => segment.startsWith('.')
42
+ || segment === 'node_modules');
32
43
  }
package/docs/part-srv.md CHANGED
@@ -26,7 +26,10 @@ folder, `--working-dir` or else the working directory:
26
26
  path from the working folder, leaving out what `.gitignore` ignores, `.git`
27
27
  and `node_modules`.
28
28
  - With none, the working folder is the one project, with the nearest `part.json`
29
- of a parent, or none.
29
+ of a parent.
30
+ - With no `part.json` there either, `part-srv` stops with an error, since a
31
+ project's `part.json` says which artefacts it supports; `factory-srv` serves
32
+ its other applications.
30
33
 
31
34
  The root, the definitions and the state folder of each project come from its own
32
35
  `part.json` only, `project`, `definitions` and `stateDir`, its paths resolved
@@ -49,8 +52,9 @@ server that shows several applications serves it at `/part/`.
49
52
  icon opens its panel next to the column; the button at the bottom of the
50
53
  column hides and shows the panel. Dragging the panel's inner edge resizes it,
51
54
  and the browser remembers the width, and the project shown, for the next
52
- visit. With more than one project, a list box heads the panel; choosing
53
- another project closes the details of the one before.
55
+ visit. With more than one project, a selector heads the panel, its filter box
56
+ finding a project by its title; choosing another project shows its artefacts,
57
+ and the details of the one before stay open.
54
58
  - **Artefacts** - the artefacts of the project as one tree of their locations,
55
59
  folded at first: grouped by scheme, such as `file:`, `npm:` or `git:`, then
56
60
  by each segment of the path, anything between `/` or `\`, as `npm:` >
@@ -75,6 +79,16 @@ server that shows several applications serves it at `/part/`.
75
79
  first paragraph of its description; a file no definition matches says so. A
76
80
  file that does not exist, a path outside the project and an unknown location
77
81
  give a note that says so.
82
+
83
+ The details have a preview of their file on their right, as the graph of
84
+ `rq-srv` has of its node: an `asljs-diagram-preview` of `asljs-ui-diagram`,
85
+ read from the server as the file is. An image is shown, a markdown file is
86
+ rendered - a link to another file of the project opens that file's details -
87
+ and other text is shown as it is. A binary file, one over 1 MiB, one the
88
+ server does not serve, and an artefact that is not a file have a note instead.
89
+ The View tab at the right of the action bar under the details shows it, and
90
+ the button after it shows and hides it; dragging its left edge resizes it, and
91
+ the browser remembers its width and whether it is shown, for every tab.
78
92
  - `/_/app.json` - the application and its projects, each with the path it is
79
93
  served at; `/_/plugin/plugin.js` - the plugin the editor loads, a browser
80
94
  module, with the modules beside it under `/_/plugin/`.
@@ -90,12 +104,15 @@ server that shows several applications serves it at `/part/`.
90
104
  a `file:` location, a path relative to the project root, or the location of
91
105
  another artefact. When there is none, the answer is 404 with `{ error }`,
92
106
  the message the details tab shows.
93
-
94
- The content of a file is never read or served: the server answers the details
95
- only, and serves nothing of a project but its data. The project is read on every
96
- request, so a reload shows the files as they are. The page loads
97
- `asljs-ui-editor` and what it imports from the server, under `/modules/`, so it
98
- needs no network.
107
+ - `/p/<project>/<path>` - a file of the project root, by its path, as it is,
108
+ for the preview: a `.md` file as `text/markdown`. A file in a dot folder, a
109
+ dot file and a file under `node_modules` are not found.
110
+
111
+ The server renders nothing: it answers the details and the files as they are,
112
+ and the preview is made in the browser. The project is read on every request, so
113
+ a reload shows the files as they are. The page loads `asljs-ui-editor`,
114
+ `asljs-ui-diagram`, `asljs-ui-markdown` and what they import from the server,
115
+ under `/modules/`, so it needs no network.
99
116
 
100
117
  ## Options
101
118
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "asljs-part-srv",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "The part-srv web server: a project's files in an editor, each with its details and the artefact definitions that match it.",
5
5
  "type": "module",
6
6
  "asljs": {
@@ -22,22 +22,37 @@
22
22
  "dist/**",
23
23
  "docs/**",
24
24
  "README.md",
25
- "LICENSE.md"
25
+ "LICENSE.md",
26
+ "!dist/*.tsbuildinfo"
26
27
  ],
27
28
  "scripts": {
28
29
  "flint": "toolkit flint",
29
- "clean": "toolkit clean",
30
- "build": "npm run codegen --if-present && tsc -p tsconfig.build.json",
31
- "build:dist": "npm run codegen --if-present && tsc -p tsconfig.dist.json",
30
+ "clean:build": "node ../../../scripts/cleanBuildOutDir.js",
31
+ "clean:dist": "node ../../../scripts/cleanDistOutDir.js",
32
+ "compile:build": "npm run codegen --if-present && tsc -p tsconfig.build.json",
33
+ "compile:dist": "npm run codegen --if-present && tsc -p tsconfig.dist.json",
34
+ "build:dist": "node ../../../scripts/buildDistDeps.js && npm run compile:dist",
32
35
  "typecheck": "npm run codegen --if-present && tsc -p tsconfig.build.json --noEmit",
33
36
  "guard:clean-git": "toolkit ensure-clean-working-directory",
34
- "prepack": "npm run clean && npm run build:dist && npm run build && npm run test && npm run guard:clean-git",
37
+ "prepack": "npm run clean:build && npm run clean:dist && npm run build:dist && npm run compile:build && npm run test && npm run guard:clean-git",
35
38
  "postpublish": "toolkit tag-release-revision",
36
39
  "test": "node --test --enable-source-maps \"build/**/*.test.js\"",
37
40
  "test:watch": "node --watch --test \"build/**/*.test.js\"",
38
41
  "release:patch": "toolkit release-patch",
39
- "all": "npm run clean && npm run build:dist && npm run flint && npm run typecheck && npm run build && npm run test"
42
+ "build": "npm run clean:build && npm run flint && npm run typecheck && npm run compile:build && npm run test"
40
43
  },
44
+ "distDependencies": [
45
+ "asljs-appbase",
46
+ "asljs-locator",
47
+ "asljs-logging",
48
+ "asljs-observable",
49
+ "asljs-part",
50
+ "asljs-testing",
51
+ "asljs-tmpdir",
52
+ "asljs-ui-diagram",
53
+ "asljs-ui-editor",
54
+ "asljs-ui-markdown"
55
+ ],
41
56
  "keywords": [
42
57
  "artefacts",
43
58
  "project",
@@ -57,8 +72,11 @@
57
72
  "asljs-locator": "^0.1.1",
58
73
  "asljs-logging": "^0.1.1",
59
74
  "asljs-appbase": "^0.1.3",
75
+ "asljs-observable": "^0.5.6",
60
76
  "asljs-part": "^0.3.2",
77
+ "asljs-ui-diagram": "^0.1.2",
61
78
  "asljs-ui-editor": "^0.1.1",
79
+ "asljs-ui-markdown": "^0.1.1",
62
80
  "commander": "^15.0.0"
63
81
  },
64
82
  "devDependencies": {