@docfy/ember 0.5.0 → 0.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.
package/.docfy-config.js CHANGED
@@ -31,6 +31,7 @@ module.exports = {
31
31
  ],
32
32
  labels: {
33
33
  components: 'Components',
34
+ plugins: 'Plugins',
34
35
  helpers: 'Helpers',
35
36
  core: '@docfy/core',
36
37
  ember: '@docfy/ember',
@@ -20,3 +20,35 @@ repository URL as well as the branch to edit.
20
20
  {{/if}}
21
21
  </DocfyOutput>
22
22
  ```
23
+
24
+ For this to work, you need to include `repository` in your docfy-config:
25
+
26
+ ```js
27
+ // in your docfy-config.js
28
+ module.exports = {
29
+ repository: {
30
+ url: 'https://github.com/@username/repo-name',
31
+ editBranch: 'main',
32
+ },
33
+ ...// rest of your config
34
+ }
35
+ ```
36
+
37
+
38
+ ## Enterprise (aka on premise) git services
39
+
40
+ `page.editUrl` works for Github, Bitbucket, Gitlab and Sourcehut.
41
+
42
+ For on-premise instances git solutions (i.e. on-premise Gitlab, or on-premise Bitbucket), we expose the `page.relativePath` so that you might construct your own custom editUrl:
43
+
44
+ ```hbs
45
+ <DocfyOutput @fromCurrentURL={{true}} as |page|>
46
+ {{#if page.relativePath}}
47
+ <a href=(concat "http://some-enterpise.com/browse/" page.relativePath)
48
+ Click here to edit this page
49
+ </a>
50
+ {{/if}}
51
+ </DocfyOutput>
52
+ ```
53
+
54
+ Note: the edit url for your on-premise instance might be more complex than the example above. But the `page.relativePath` will give you the relative path to that file in your repo.
@@ -22,7 +22,7 @@ Below you can see how a demo markdown file looks like.
22
22
 
23
23
  ```
24
24
 
25
- The demo will be injected into the owner file as a new section called "Examples";
25
+ The demo will be inserted into the owner file as a new section called "Examples";
26
26
  you can see it below.
27
27
 
28
28
  Please note that you must pass a metadata to the code block, it can be seen
@@ -57,3 +57,53 @@ And here you can see how it looks like when rendered:
57
57
  ```hbs preview-template
58
58
  Click in the link to navigate to the home page: <DocfyLink @to="/">Home</DocfyLink>
59
59
  ```
60
+
61
+ ## Manual Insertion
62
+
63
+ To make getting started with Docfy as simple as possible, by default demos will
64
+ be automatically inserted into the owner file under a new section called
65
+ "Examples" before the second heading of the page.
66
+
67
+ If you want more control over how demos are inserted into the page, you can
68
+ declare `manualDemoInsertion` in a markdown page's frontmatter.
69
+
70
+ ```
71
+ ---
72
+ title: Document with many examples
73
+ manualDemoInsertion: true
74
+ ---
75
+ ```
76
+
77
+ When a page is using `manualDemoInsertion`, by default no demos are inserted
78
+ into the page. Instead, you must provide markers in your markdown that will be
79
+ replaced. They follow the form `[[demo:name-of-demo]]`.
80
+
81
+ ```md
82
+ # Title here
83
+
84
+ The demo will be inserted after this line.
85
+
86
+ [[demo:demo1]]
87
+
88
+ And the prose of the document will continue exactly how the author wishes.
89
+ ```
90
+
91
+ Sometimes as an author, you want control over where in the page demos will be
92
+ inserted, but you don't need to control this location demo by demo. As a
93
+ shorthand, you can provide the `[[demos-all]]` marker to insert all demos.
94
+
95
+ ```md
96
+ # Title here
97
+
98
+ All demos go here.
99
+
100
+ [[demos-all]]
101
+
102
+ Below is the equivalent if you had to mark all demos individually.
103
+
104
+ [[demo:demo1]]
105
+
106
+ [[demo:demo2]]
107
+
108
+ [[demo:demo3]]
109
+ ```
@@ -0,0 +1,15 @@
1
+ # Tomster
2
+
3
+ Lovable hamster
4
+
5
+ ```hbs template
6
+ <div data-test-id="tomster">
7
+ {{#let (array 1 2 3 4 5) as |numbers|}}
8
+ <ol>
9
+ {{#each numbers as |number|}}
10
+ <li>Here is number {{number}}</li>
11
+ {{/each}}
12
+ </ol>
13
+ {{/let}}
14
+ </div>
15
+ ```
@@ -0,0 +1,9 @@
1
+ # Zoey
2
+
3
+ Tomster's sister (also a lovable hamster).
4
+
5
+ ```hbs template
6
+ <div data-test-id="zoey">
7
+ Ember is <em>so</em> {{concat 'g' 'r' 'e' 'a' 't'}}!
8
+ </div>
9
+ ```
@@ -0,0 +1,31 @@
1
+ ---
2
+ category: ember
3
+ subcategory: plugins
4
+ manualDemoInsertion: true
5
+ ---
6
+
7
+ # Manual Demo Insertion
8
+
9
+ Docfy will automatically insert demos for a page into the page at build time. However, this convenience comes at the cost of choosing where the demos should be rendered.
10
+
11
+ If you want control over where demos are rendered in a page, add `manualDemoInsertion: true` to your page's frontmatter. Then you can use `[[demo:name]]` or `[[demos-all]]` to mark where a specific demo or all demos should be rendered.
12
+
13
+ These markers must be on their own line!
14
+
15
+ ## It works like this
16
+
17
+ Here is the tomster demo
18
+
19
+ [[demo:tomster]]
20
+
21
+ And here is the zoey demo
22
+
23
+ [[demo:zoey]]
24
+
25
+ ## Or you can add all demos
26
+
27
+ [[demos-all]]
28
+
29
+ ## Conclusion
30
+
31
+ With Docfy you get demos added to your pages for free, but with a little more work you can also get a bit more control.
package/lib/index.js CHANGED
@@ -63,12 +63,16 @@ class DocfyBroccoli extends broccoli_plugin_1.default {
63
63
  debug('Config: ', this.config);
64
64
  const docfy = new core_1.default(this.config);
65
65
  const result = yield docfy.run(this.config.sources);
66
+ const snippets = {
67
+ components: {}
68
+ };
66
69
  result.content.forEach((page) => {
67
70
  const parts = [this.outputPath, 'templates', page.meta.url];
68
71
  if (page.meta.url[page.meta.url.length - 1] === '/') {
69
72
  parts.push('index');
70
73
  }
71
74
  const fileName = `${path_1.default.join(...parts)}.hbs`;
75
+ // console.log(fileName, "RENDERED AS", page.rendered);
72
76
  ensureDirectoryExistence(fileName);
73
77
  fs_1.default.writeFileSync(fileName, page.rendered);
74
78
  const demoComponents = page.pluginData.demoComponents;
@@ -77,11 +81,13 @@ class DocfyBroccoli extends broccoli_plugin_1.default {
77
81
  component.chunks.forEach((chunk) => {
78
82
  const chunkPath = path_1.default.join(this.outputPath, 'components', `${component.name.dashCase}.${chunk.ext}`);
79
83
  ensureDirectoryExistence(chunkPath);
84
+ snippets.components[`${component.name.dashCase}`] = Object.assign(Object.assign({}, (snippets.components[`${component.name.dashCase}`] || {})), { [chunk.ext]: chunk.code });
80
85
  fs_1.default.writeFileSync(chunkPath, chunk.code);
81
86
  });
82
87
  if (!hasBackingJS(component.chunks)) {
83
88
  const chunkPath = path_1.default.join(this.outputPath, 'components', `${component.name.dashCase}.js`);
84
89
  ensureDirectoryExistence(chunkPath);
90
+ snippets.components[`${component.name.dashCase}`] = Object.assign(Object.assign({}, (snippets.components[`${component.name.dashCase}`] || {})), { js: templateOnlyComponent });
85
91
  fs_1.default.writeFileSync(chunkPath, templateOnlyComponent);
86
92
  }
87
93
  });
@@ -91,6 +97,9 @@ class DocfyBroccoli extends broccoli_plugin_1.default {
91
97
  const urlsJsonFile = path_1.default.join(this.outputPath, 'public', 'docfy-urls.json');
92
98
  ensureDirectoryExistence(urlsJsonFile);
93
99
  fs_1.default.writeFileSync(urlsJsonFile, JSON.stringify(result.content.map((page) => page.meta.url)));
100
+ const snippetsJsonFile = path_1.default.join(this.outputPath, 'public', 'docfy-snippets.json');
101
+ ensureDirectoryExistence(snippetsJsonFile);
102
+ fs_1.default.writeFileSync(snippetsJsonFile, JSON.stringify(snippets));
94
103
  result.staticAssets.forEach((asset) => {
95
104
  const dest = path_1.default.join(this.outputPath, 'public', asset.toPath);
96
105
  ensureDirectoryExistence(dest);
@@ -22,6 +22,14 @@ function createHeading(ctx) {
22
22
  heading.depth = 2;
23
23
  return heading;
24
24
  }
25
+ const isTextMarker = (node) => node.type === 'paragraph' &&
26
+ node.children.length === 1 &&
27
+ node.children[0].type === 'text';
28
+ const demoMarkerRegex = /^\[\[demo:(.+?)\]\]$/;
29
+ const demoMarker = (node) => isTextMarker(node) && demoMarkerRegex.test(node.children[0].value);
30
+ const demosAllMarkerRegex = /^\[\[demos-all\]\]$/;
31
+ const demosAllMarker = (node) => isTextMarker(node) &&
32
+ demosAllMarkerRegex.test(node.children[0].value);
25
33
  /*
26
34
  * Insert Demo nodes into the page.
27
35
  */
@@ -37,6 +45,42 @@ function insertDemoNodesIntoPage(page, toInsert) {
37
45
  }
38
46
  }
39
47
  }
48
+ function replaceDemoMarkers(page, demos) {
49
+ if (Array.isArray(page.ast.children)) {
50
+ const markers = [];
51
+ const allMarkers = [];
52
+ (0, unist_util_visit_1.default)(page.ast, 'paragraph', (node) => {
53
+ if (demoMarker(node))
54
+ markers.push(node);
55
+ if (demosAllMarker(node))
56
+ allMarkers.push(node);
57
+ });
58
+ markers.forEach((marker) => {
59
+ const child = marker.children[0];
60
+ const matches = child.value.match(demoMarkerRegex);
61
+ if (!matches)
62
+ return;
63
+ // TODO: This is an inner loop and can cause perf issues if someone
64
+ // out there has many demos on a single page. It would be better to
65
+ // create a demo component hash that can be looked up by demo name.
66
+ const demoName = matches[1];
67
+ const demo = demos.find((d) => d.name.dashCase.endsWith(demoName));
68
+ if (!demo) {
69
+ console.warn(`Found demo marker "${demoName}" with no matching demo component in ${page.source}`);
70
+ return;
71
+ }
72
+ marker.type = 'div';
73
+ marker.children.splice(0, 1, ...(0, utils_1.createDemoNodes)(demo));
74
+ });
75
+ allMarkers.forEach((marker) => {
76
+ const demoNodes = demos
77
+ .map((component) => (0, utils_1.createDemoNodes)(component))
78
+ .flat();
79
+ marker.type = 'div';
80
+ marker.children.splice(0, 1, ...demoNodes);
81
+ });
82
+ }
83
+ }
40
84
  exports.default = (0, plugin_1.default)({
41
85
  runWithMdast(ctx) {
42
86
  const seenNames = new Set();
@@ -79,11 +123,20 @@ exports.default = (0, plugin_1.default)({
79
123
  (0, utils_1.deleteNode)(demo.ast.children, snippet);
80
124
  });
81
125
  });
82
- const toInsert = [createHeading(ctx)];
83
- demoComponents.forEach((component) => {
84
- toInsert.push(...(0, utils_1.createDemoNodes)(component));
85
- });
86
- insertDemoNodesIntoPage(page, toInsert);
126
+ if (page.meta.frontmatter.manualDemoInsertion) {
127
+ // Manual demo insertion inserts demos into markdown files
128
+ // wherever there is a demo marker ([[demo:name]] or [[demos-all]])
129
+ replaceDemoMarkers(page, demoComponents);
130
+ }
131
+ else {
132
+ // Automatic demo insertion creates an Example block after
133
+ // the first heading.
134
+ const toInsert = [createHeading(ctx)];
135
+ demoComponents.forEach((component) => {
136
+ toInsert.push(...(0, utils_1.createDemoNodes)(component));
137
+ });
138
+ insertDemoNodesIntoPage(page, toInsert);
139
+ }
87
140
  if ((0, utils_1.isDemoComponents)(page.pluginData.demoComponents)) {
88
141
  page.pluginData.demoComponents.push(...demoComponents);
89
142
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docfy/ember",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Ember integration with Docfy",
5
5
  "keywords": [
6
6
  "ember-addon",
@@ -30,7 +30,7 @@
30
30
  "test:ember-compatibility": "echo 'ember try:each TODO'"
31
31
  },
32
32
  "dependencies": {
33
- "@docfy/core": "^0.5.0",
33
+ "@docfy/core": "^0.7.0",
34
34
  "broccoli-bridge": "^1.0.0",
35
35
  "broccoli-file-creator": "^2.1.1",
36
36
  "broccoli-funnel": "^3.0.8",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "devDependencies": {
54
54
  "@babel/helper-define-map": "^7.16.7",
55
- "@docfy/plugin-with-prose": "^0.5.0",
55
+ "@docfy/plugin-with-prose": "^0.7.0",
56
56
  "@ember/optional-features": "^2.0.0",
57
57
  "@ember/test-helpers": "^2.6.0",
58
58
  "@embroider/compat": "^1.0.0",
@@ -135,5 +135,5 @@
135
135
  "prember"
136
136
  ]
137
137
  },
138
- "gitHead": "660408a83302ec4dc727acc0339a506fe0316b54"
138
+ "gitHead": "d24cfd0a838a890c850d0030ec30856d0608267c"
139
139
  }
package/src/index.ts CHANGED
@@ -60,7 +60,9 @@ class DocfyBroccoli extends Plugin {
60
60
  debug('Config: ', this.config);
61
61
  const docfy = new Docfy(this.config);
62
62
  const result = await docfy.run(this.config.sources as SourceConfig[]);
63
-
63
+ const snippets = {
64
+ components: {}
65
+ };
64
66
  result.content.forEach((page) => {
65
67
  const parts = [this.outputPath, 'templates', page.meta.url];
66
68
 
@@ -70,6 +72,7 @@ class DocfyBroccoli extends Plugin {
70
72
 
71
73
  const fileName = `${path.join(...parts)}.hbs`;
72
74
 
75
+ // console.log(fileName, "RENDERED AS", page.rendered);
73
76
  ensureDirectoryExistence(fileName);
74
77
  fs.writeFileSync(fileName, page.rendered);
75
78
 
@@ -83,6 +86,11 @@ class DocfyBroccoli extends Plugin {
83
86
  `${component.name.dashCase}.${chunk.ext}`
84
87
  );
85
88
  ensureDirectoryExistence(chunkPath);
89
+
90
+ snippets.components[`${component.name.dashCase}`] = {
91
+ ...(snippets.components[`${component.name.dashCase}`] || {}),
92
+ [chunk.ext]: chunk.code
93
+ };
86
94
  fs.writeFileSync(chunkPath, chunk.code);
87
95
  });
88
96
 
@@ -93,6 +101,10 @@ class DocfyBroccoli extends Plugin {
93
101
  `${component.name.dashCase}.js`
94
102
  );
95
103
  ensureDirectoryExistence(chunkPath);
104
+ snippets.components[`${component.name.dashCase}`] = {
105
+ ...(snippets.components[`${component.name.dashCase}`] || {}),
106
+ js: templateOnlyComponent
107
+ };
96
108
  fs.writeFileSync(chunkPath, templateOnlyComponent);
97
109
  }
98
110
  });
@@ -109,11 +121,19 @@ class DocfyBroccoli extends Plugin {
109
121
  'public',
110
122
  'docfy-urls.json'
111
123
  );
124
+
112
125
  ensureDirectoryExistence(urlsJsonFile);
113
126
  fs.writeFileSync(
114
127
  urlsJsonFile,
115
128
  JSON.stringify(result.content.map((page) => page.meta.url))
116
129
  );
130
+ const snippetsJsonFile = path.join(
131
+ this.outputPath,
132
+ 'public',
133
+ 'docfy-snippets.json'
134
+ );
135
+ ensureDirectoryExistence(snippetsJsonFile);
136
+ fs.writeFileSync(snippetsJsonFile, JSON.stringify(snippets));
117
137
  result.staticAssets.forEach((asset) => {
118
138
  const dest = path.join(this.outputPath, 'public', asset.toPath);
119
139
  ensureDirectoryExistence(dest);
@@ -1,7 +1,7 @@
1
1
  import visit from 'unist-util-visit';
2
2
  import { Context, PageContent } from '@docfy/core/lib/types';
3
3
  import plugin from '@docfy/core/lib/plugin';
4
- import { Node } from 'unist';
4
+ import { Node, Parent } from 'unist';
5
5
  import findNode from 'unist-util-find';
6
6
  import toString from 'mdast-util-to-string';
7
7
  import { DemoComponent, DemoComponentChunk, CodeNode } from './types';
@@ -30,6 +30,20 @@ function createHeading(ctx: Context): Node {
30
30
  return heading;
31
31
  }
32
32
 
33
+ const isTextMarker = (node: Parent): boolean =>
34
+ node.type === 'paragraph' &&
35
+ node.children.length === 1 &&
36
+ node.children[0].type === 'text';
37
+
38
+ const demoMarkerRegex = /^\[\[demo:(.+?)\]\]$/;
39
+ const demoMarker = (node: Parent): boolean =>
40
+ isTextMarker(node) && demoMarkerRegex.test(node.children[0].value as string);
41
+
42
+ const demosAllMarkerRegex = /^\[\[demos-all\]\]$/;
43
+ const demosAllMarker = (node: Parent): boolean =>
44
+ isTextMarker(node) &&
45
+ demosAllMarkerRegex.test(node.children[0].value as string);
46
+
33
47
  /*
34
48
  * Insert Demo nodes into the page.
35
49
  */
@@ -49,6 +63,49 @@ function insertDemoNodesIntoPage(page: PageContent, toInsert: Node[]): void {
49
63
  }
50
64
  }
51
65
 
66
+ function replaceDemoMarkers(page: PageContent, demos: DemoComponent[]): void {
67
+ if (Array.isArray(page.ast.children)) {
68
+ const markers: Parent[] = [];
69
+ const allMarkers: Parent[] = [];
70
+
71
+ visit(page.ast, 'paragraph', (node: Parent) => {
72
+ if (demoMarker(node)) markers.push(node);
73
+ if (demosAllMarker(node)) allMarkers.push(node);
74
+ });
75
+
76
+ markers.forEach((marker) => {
77
+ const child = marker.children[0];
78
+ const matches = (child.value as string).match(demoMarkerRegex);
79
+ if (!matches) return;
80
+
81
+ // TODO: This is an inner loop and can cause perf issues if someone
82
+ // out there has many demos on a single page. It would be better to
83
+ // create a demo component hash that can be looked up by demo name.
84
+ const demoName = matches[1];
85
+ const demo = demos.find((d) => d.name.dashCase.endsWith(demoName));
86
+
87
+ if (!demo) {
88
+ console.warn(
89
+ `Found demo marker "${demoName}" with no matching demo component in ${page.source}`
90
+ );
91
+ return;
92
+ }
93
+
94
+ marker.type = 'div';
95
+ marker.children.splice(0, 1, ...createDemoNodes(demo));
96
+ });
97
+
98
+ allMarkers.forEach((marker) => {
99
+ const demoNodes = demos
100
+ .map((component) => createDemoNodes(component))
101
+ .flat();
102
+
103
+ marker.type = 'div';
104
+ marker.children.splice(0, 1, ...demoNodes);
105
+ });
106
+ }
107
+ }
108
+
52
109
  export default plugin({
53
110
  runWithMdast(ctx: Context): void {
54
111
  const seenNames: Set<string> = new Set();
@@ -114,11 +171,19 @@ export default plugin({
114
171
  });
115
172
  });
116
173
 
117
- const toInsert: Node[] = [createHeading(ctx)];
118
- demoComponents.forEach((component) => {
119
- toInsert.push(...createDemoNodes(component));
120
- });
121
- insertDemoNodesIntoPage(page, toInsert);
174
+ if (page.meta.frontmatter.manualDemoInsertion) {
175
+ // Manual demo insertion inserts demos into markdown files
176
+ // wherever there is a demo marker ([[demo:name]] or [[demos-all]])
177
+ replaceDemoMarkers(page, demoComponents);
178
+ } else {
179
+ // Automatic demo insertion creates an Example block after
180
+ // the first heading.
181
+ const toInsert: Node[] = [createHeading(ctx)];
182
+ demoComponents.forEach((component) => {
183
+ toInsert.push(...createDemoNodes(component));
184
+ });
185
+ insertDemoNodesIntoPage(page, toInsert);
186
+ }
122
187
 
123
188
  if (isDemoComponents(page.pluginData.demoComponents)) {
124
189
  page.pluginData.demoComponents.push(...demoComponents);