diff --git a/.vscode/settings.json b/.vscode/settings.json
index 945952f74..2c87465b2 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,5 @@
{
- "cSpell.words": ["Scully", "Scullyio"],
+ "cSpell.words": ["Scully", "Scullyio", "ngcontent"],
"peacock.affectActivityBar": true,
"peacock.affectTitleBar": true,
"peacock.affectStatusBar": true,
diff --git a/projects/sampleBlog/src/app/blog/blog-list/blog-list.component.ts b/projects/sampleBlog/src/app/blog/blog-list/blog-list.component.ts
index 15e327490..d65fbc505 100644
--- a/projects/sampleBlog/src/app/blog/blog-list/blog-list.component.ts
+++ b/projects/sampleBlog/src/app/blog/blog-list/blog-list.component.ts
@@ -9,7 +9,8 @@ import {map} from 'rxjs/operators';
})
export class BlogListComponent implements OnInit {
blogs$ = this.srs.available$.pipe(
- map(routeList => routeList.filter((route: ScullyRoute) => route.route.startsWith(`/blog/`)))
+ map(routeList => routeList.filter((route: ScullyRoute) => route.route.startsWith(`/blog/`))),
+ map(blogs => blogs.sort((a, b) => (a.date < b.date ? -1 : 1)))
);
constructor(private srs: ScullyRoutesService) {}
diff --git a/projects/sampleBlog/src/app/blog/blog.component.css b/projects/sampleBlog/src/app/blog/blog.component.css
index aadded8e9..eff20d6d6 100644
--- a/projects/sampleBlog/src/app/blog/blog.component.css
+++ b/projects/sampleBlog/src/app/blog/blog.component.css
@@ -1,4 +1,4 @@
-::slotted(h1) {
+h1 {
color: rgb(51, 6, 37);
background-color: rgb(248, 211, 236);
padding: 5px;
diff --git a/projects/scullyio/ng-lib/src/lib/scully-content/scully-content.component.ts b/projects/scullyio/ng-lib/src/lib/scully-content/scully-content.component.ts
index 82abb9739..fb12b7488 100644
--- a/projects/scullyio/ng-lib/src/lib/scully-content/scully-content.component.ts
+++ b/projects/scullyio/ng-lib/src/lib/scully-content/scully-content.component.ts
@@ -2,18 +2,26 @@ import {
ChangeDetectionStrategy,
Component,
ElementRef,
- Input,
OnDestroy,
OnInit,
ViewEncapsulation,
} from '@angular/core';
import {Router} from '@angular/router';
-import {Observable, Subscription} from 'rxjs';
import {take} from 'rxjs/operators';
import {IdleMonitorService} from '../idleMonitor/idle-monitor.service';
import {ScullyRoutesService} from '../route-service/scully-routes.service';
import {fetchHttp} from '../utils/fetchHttp';
+import {findComments} from '../utils/findComments';
+interface ScullyContent {
+ html: string;
+ cssId: string;
+}
+declare global {
+ interface Window {
+ scullyContent: ScullyContent;
+ }
+}
/** this is needed, because otherwise the CLI borks while building */
const scullyBegin = '';
const scullyEnd = '';
@@ -36,22 +44,15 @@ const scullyEnd = '';
preserveWhitespaces: true,
})
export class ScullyContentComponent implements OnInit, OnDestroy {
- @Input() type = 'MD';
-
elm = this.elmRef.nativeElement as HTMLElement;
- // mutationSubscription: Subscription;
+ /** pull in all available routes into an eager promise */
routes = this.srs.available$.pipe(take(1)).toPromise();
- constructor(
- private elmRef: ElementRef,
- private srs: ScullyRoutesService,
- private router: Router,
- private idle: IdleMonitorService
- ) {}
+ constructor(private elmRef: ElementRef, private srs: ScullyRoutesService, private router: Router) {}
ngOnInit() {
- /** make sure the idle-check is loaded. */
- this.idle.init();
+ // /** make sure the idle-check is loaded. */
+ // this.idle.init();
if (this.elm) {
/** this will only fire in a browser environment */
this.handlePage();
@@ -64,11 +65,16 @@ export class ScullyContentComponent implements OnInit, OnDestroy {
*/
private async handlePage() {
const template = document.createElement('template');
- // tslint:disable-next-line: no-string-literal
- if (window['scullyContent']) {
+ const currentCssId = this.getCSSId(this.elm);
+ if (window.scullyContent) {
/** upgrade existing static content */
- // tslint:disable-next-line: no-string-literal
- template.innerHTML = window['scullyContent'];
+ const htmlString = window.scullyContent.html;
+ if (currentCssId !== window.scullyContent.cssId) {
+ /** replace the angular cssId */
+ template.innerHTML = htmlString.split(window.scullyContent.cssId).join(currentCssId);
+ } else {
+ template.innerHTML = htmlString;
+ }
} else {
const curPage = location.href;
/**
@@ -81,7 +87,12 @@ export class ScullyContentComponent implements OnInit, OnDestroy {
await fetchHttp(curPage, 'text')
.then((html: string) => {
try {
- template.innerHTML = html.split(scullyBegin)[1].split(scullyEnd)[0];
+ const htmlString = html.split(scullyBegin)[1].split(scullyEnd)[0];
+ if (htmlString.includes('_ngcontent')) {
+ /** update the angular cssId */
+ const atr = '_ngcontent' + htmlString.split('_ngcontent')[1].split('=')[0];
+ template.innerHTML = htmlString.split(atr).join(currentCssId);
+ }
} catch (e) {
template.innerHTML = `
Sorry, could not parse static page content
This might happen if you are not using the static generated pages.
`;
@@ -93,6 +104,7 @@ export class ScullyContentComponent implements OnInit, OnDestroy {
console.error('problem during loading static scully content', e);
});
}
+ /** insert the whole thing just before the `` element */
const parent = this.elm.parentElement || document.body;
const begin = document.createComment('scullyContent-begin');
const end = document.createComment('scullyContent-end');
@@ -130,7 +142,7 @@ export class ScullyContentComponent implements OnInit, OnDestroy {
return;
}
/** delete the content, as it is now out of date! */
- window['scullyContent'] = undefined;
+ window.scullyContent = undefined;
/** check for the same route with different "data", and NOT a level higher (length) */
if (curSplit.every((part, i) => splitRoute[i] === part) && splitRoute.length > curSplit.length) {
/**
@@ -155,59 +167,9 @@ export class ScullyContentComponent implements OnInit, OnDestroy {
}
}
- ngOnDestroy() {
- // if (this.mutationSubscription) {
- // this.mutationSubscription.unsubscribe();
- // }
+ getCSSId(elm: HTMLElement) {
+ return elm.getAttributeNames().find(a => a.startsWith('_ngcontent')) || 'none_found';
}
-}
-/**
- * Returns an observable that fires a mutation when the domMutationObserves does that.
- * if flattens the mutations to make handling easier, so you only get 1 mutationRecord at a time.
- * @param elm the elm to obse with a mutationObserver
- * @param config the config for the mutationobserver
- */
-export function fromMutationObserver(
- elm: HTMLElement,
- config: MutationObserverInit
-): Observable {
- return new Observable(obs => {
- const observer = new MutationObserver(mutations => mutations.forEach(mutation => obs.next(mutation)));
- observer.observe(elm, config);
- return () => observer.disconnect();
- });
-}
-
-/**
- * Returns an array of nodes coninting all the html comments in the element.
- * When a searchText is given this is narrowed down to only comments that contian this text
- * @param rootElem Element to search nto
- * @param searchText optional string that needs to be in a HTML comment
- */
-function findComments(rootElem: HTMLElement, searchText?: string) {
- const comments = [];
- // Fourth argument, which is actually obsolete according to the DOM4 standard, seems required in IE 11
- const iterator = document.createNodeIterator(
- rootElem,
- NodeFilter.SHOW_COMMENT,
- {
- acceptNode: node => {
- // Logic to determine whether to accept, reject or skip node
- // In this case, only accept nodes that have content
- // that is containing our searchText, by rejecting any other nodes.
- if (searchText && node.nodeValue && !node.nodeValue.includes(searchText)) {
- return NodeFilter.FILTER_REJECT;
- }
- return NodeFilter.FILTER_ACCEPT;
- },
- }
- // , false // IE-11 support requires this parameter.
- );
- let curNode;
- // tslint:disable-next-line: no-conditional-assignment
- while ((curNode = iterator.nextNode())) {
- comments.push(curNode);
- }
- return comments;
+ ngOnDestroy() {}
}
diff --git a/projects/scullyio/ng-lib/src/lib/utils/findComments.ts b/projects/scullyio/ng-lib/src/lib/utils/findComments.ts
new file mode 100644
index 000000000..da9ab49b6
--- /dev/null
+++ b/projects/scullyio/ng-lib/src/lib/utils/findComments.ts
@@ -0,0 +1,32 @@
+/**
+ * Returns an array of nodes coninting all the html comments in the element.
+ * When a searchText is given this is narrowed down to only comments that contian this text
+ * @param rootElem Element to search nto
+ * @param searchText optional string that needs to be in a HTML comment
+ */
+export function findComments(rootElem: HTMLElement, searchText?: string) {
+ const comments = [];
+ // Fourth argument, which is actually obsolete according to the DOM4 standard, seems required in IE 11
+ const iterator = document.createNodeIterator(
+ rootElem,
+ NodeFilter.SHOW_COMMENT,
+ {
+ acceptNode: node => {
+ // Logic to determine whether to accept, reject or skip node
+ // In this case, only accept nodes that have content
+ // that is containing our searchText, by rejecting any other nodes.
+ if (searchText && node.nodeValue && !node.nodeValue.includes(searchText)) {
+ return NodeFilter.FILTER_REJECT;
+ }
+ return NodeFilter.FILTER_ACCEPT;
+ },
+ }
+ // , false // IE-11 support requires this parameter.
+ );
+ let curNode;
+ // tslint:disable-next-line: no-conditional-assignment
+ while ((curNode = iterator.nextNode())) {
+ comments.push(curNode);
+ }
+ return comments;
+}
diff --git a/projects/scullyio/ng-lib/src/lib/utils/fromMutationObserver.ts b/projects/scullyio/ng-lib/src/lib/utils/fromMutationObserver.ts
new file mode 100644
index 000000000..551eee2d6
--- /dev/null
+++ b/projects/scullyio/ng-lib/src/lib/utils/fromMutationObserver.ts
@@ -0,0 +1,17 @@
+import {Observable} from 'rxjs';
+/**
+ * Returns an observable that fires a mutation when the domMutationObserves does that.
+ * if flattens the mutations to make handling easier, so you only get 1 mutationRecord at a time.
+ * @param elm the elm to obse with a mutationObserver
+ * @param config the config for the mutationobserver
+ */
+export function fromMutationObserver(
+ elm: HTMLElement,
+ config: MutationObserverInit
+): Observable {
+ return new Observable(obs => {
+ const observer = new MutationObserver(mutations => mutations.forEach(mutation => obs.next(mutation)));
+ observer.observe(elm, config);
+ return () => observer.disconnect();
+ });
+}
diff --git a/schematics/scully/src/files/blog-module/blog.component.ts b/schematics/scully/src/files/blog-module/blog.component.ts
index c8c53d6a4..ad226167e 100644
--- a/schematics/scully/src/files/blog-module/blog.component.ts
+++ b/schematics/scully/src/files/blog-module/blog.component.ts
@@ -1,5 +1,5 @@
import {Component, OnInit, ViewEncapsulation} from '@angular/core';
-import {ActivatedRoute, Router, ROUTES} from '@angular/router';
+import {ActivatedRoute, Router} from '@angular/router';
declare var ng: any;
diff --git a/scully.sampleBlog.config.js b/scully.sampleBlog.config.js
index 83494658c..52e4cc481 100644
--- a/scully.sampleBlog.config.js
+++ b/scully.sampleBlog.config.js
@@ -47,32 +47,15 @@ exports.config = {
property: 'id',
},
},
- '/todos/:todoId': {
- // Type is mandatory
- type: 'json',
- /**
- * Every parameter in the route must exist here
- */
- todoId: {
- url: 'https://jsonplaceholder.typicode.com/todos',
- property: 'id',
- /**
- * Headers can be sent optionally
- */
- headers: {
- 'API-KEY': '0123456789',
- },
- },
- },
'/blog/:slug': {
type: 'contentFolder',
- postRenderers: ['toc'],
+ // postRenderers: ['toc'],
slug: {
folder: './blog',
},
},
'/**': {
- type: 'void',
+ type: 'ignored',
},
},
};
diff --git a/scully/renderPlugins/content-render-utils/getScript.ts b/scully/renderPlugins/content-render-utils/getScript.ts
index e3123afe5..04de0ea5e 100644
--- a/scully/renderPlugins/content-render-utils/getScript.ts
+++ b/scully/renderPlugins/content-render-utils/getScript.ts
@@ -2,7 +2,7 @@
* @returns a string representing the script that parses the page and loads the scullyContent variable.
* The string is kept on one line as the focus is to keep it as small as possible.
*/
-export function getScript(): string {
+export function getScript(attr): string {
// tslint:disable-next-line:no-unused-expression
- return ``;
+ return ``;
}
diff --git a/scully/renderPlugins/contentRenderPlugin.ts b/scully/renderPlugins/contentRenderPlugin.ts
index c72dfa786..ea1e2ffdd 100644
--- a/scully/renderPlugins/contentRenderPlugin.ts
+++ b/scully/renderPlugins/contentRenderPlugin.ts
@@ -5,6 +5,8 @@ import {getScript} from './content-render-utils/getScript';
import {handleFile} from './content-render-utils/handleFile';
import {insertContent} from './content-render-utils/insertContent';
import {readFileAndCheckPrePublishSlug} from './content-render-utils/readFileAndCheckPrePublishSlug';
+import {JSDOM} from 'jsdom';
+import {nodeModuleNameResolver} from 'typescript';
registerPlugin('render', 'contentFolder', contentRenderPlugin);
@@ -18,8 +20,15 @@ export async function contentRenderPlugin(html: string, route: HandledRoute) {
const {meta, fileContent} = await readFileAndCheckPrePublishSlug(file, route);
// TODO: create additional "routes" for every slug
route.data = {...route.data, ...meta};
+ const attr = getIdAttrName(
+ html
+ .split('')[0]
+ .trim()
+ );
const additionalHTML = await handleFile(extension, fileContent);
- return insertContent(scullyBegin, scullyEnd, html, additionalHTML, getScript());
+ const htmlWithNgAttr = addNgIdAttribute(additionalHTML, attr);
+ return insertContent(scullyBegin, scullyEnd, html, htmlWithNgAttr, getScript(attr));
} catch (e) {
logWarn(
`Error, probably missing "${yellow('')}" or "${yellow(
@@ -28,3 +37,31 @@ export async function contentRenderPlugin(html: string, route: HandledRoute) {
);
}
}
+
+function addNgIdAttribute(html: string, id: string): string {
+ try {
+ const dom = new JSDOM(html, {runScripts: 'outside-only'});
+ const {window} = dom;
+ const {document} = window;
+ const walk = document.createTreeWalker(document.body as any);
+ let cur = (walk.currentNode as any) as HTMLElement;
+ while (cur) {
+ if (cur.nodeType === 1) {
+ cur.setAttribute(id, '');
+ }
+ cur = (walk.nextNode() as any) as HTMLElement;
+ }
+ return document.body.innerHTML;
+ } catch (e) {
+ console.error(e);
+ }
+
+ return '';
+}
+
+function getIdAttrName(attrs: string): string {
+ return attrs
+ .split(' ')
+ .find((at: string) => at.trim().startsWith('_ngcontent'))
+ .split('=')[0];
+}