# All files / src/Layout Layout.ts

12.24% Statements 6/49
0% Branches 0/5
0% Functions 0/9
12.76% Lines 6/47

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    1215x
    5x
    5x
    5x

    5x

    5x

     import { v4 as uuidv4 } from 'uuid';
    import { PageSources } from '../Page/PageSources';
    import { NodeProcessor } from '../html/NodeProcessor';
    import * as logger from '../utils/logger';
    import { ExternalManager, ExternalManagerConfig } from '../External/ExternalManager';

    const LAYOUT_PAGE_BODY_VARIABLE = 'content';

    export type LayoutConfig = ExternalManagerConfig & { externalManager: ExternalManager };

    export type PageNjkAssets = {
      headTop: string[];
      headBottom: string[];
      scriptBottom: string[];
      layoutUserScriptsAndStyles: string[];
    };

    export class Layout {
      sourceFilePath: string;
      config: LayoutConfig;
      includedFiles: Set<string>;
      layoutProcessed: string;
      layoutPageBodyVariable: string;
      layoutPageNavUuid: string;
      headTop: string[];
      headBottom: string[];
      scriptBottom: string[];
      layoutUserScriptsAndStyles: string[];
      generatePromise: Promise<void> | undefined;

      constructor(sourceFilePath: string, config: LayoutConfig) {
        this.sourceFilePath = sourceFilePath;
        this.config = config;

        this.includedFiles = new Set([this.sourceFilePath]);
        this.layoutProcessed = '';
        this.layoutPageBodyVariable = '';
        this.layoutPageNavUuid = '';
        this.headTop = [];
        this.headBottom = [];
        this.scriptBottom = [];
        this.layoutUserScriptsAndStyles = [];

        this.generatePromise = undefined;
      }

      shouldRegenerate(filePaths: string[]): boolean {
        return filePaths.some(filePath => this.includedFiles.has(filePath));
      }

      async generate(): Promise<void> {
        let triesLeft = 10;
        let numBodyVars;
        let pageSources: PageSources;
        let nodeProcessor: NodeProcessor;

        do {
          const fileConfig = {
            ...this.config,
            headerIdMap: {},
          };
          pageSources = new PageSources();
          this.layoutPageBodyVariable = `{{${uuidv4()}-${uuidv4()}}}`;
          const layoutVariables = { [LAYOUT_PAGE_BODY_VARIABLE]: this.layoutPageBodyVariable };

          const { variableProcessor, pluginManager, siteLinkManager } = this.config;

          const nunjucksProcessed = variableProcessor.renderWithSiteVariables(
            this.sourceFilePath, pageSources, layoutVariables);
          nodeProcessor = new NodeProcessor(fileConfig, pageSources, variableProcessor,
                                            pluginManager, siteLinkManager, this.layoutUserScriptsAndStyles,
                                            'layout');
          // eslint-disable-next-line no-await-in-loop
          this.layoutProcessed = await nodeProcessor.process(this.sourceFilePath, nunjucksProcessed.toString(),
                                                             this.sourceFilePath, layoutVariables) as string;
          this.layoutPageNavUuid = nodeProcessor.pageNavProcessor.getUuid();

          this.layoutProcessed = pluginManager.postRender(nodeProcessor.frontmatter, this.layoutProcessed);

          const pageBodyVarRegex = new RegExp(this.layoutPageBodyVariable, 'g');
          const bodyVarMatch = this.layoutProcessed.match(pageBodyVarRegex);
          numBodyVars = bodyVarMatch ? bodyVarMatch.length : 0;

          triesLeft -= 1;
        } while (triesLeft > 0 && numBodyVars > 1);

        Iif (triesLeft === 0) {
          logger.error(
            `Layout ${this.sourceFilePath} uses more than one {{ ${LAYOUT_PAGE_BODY_VARIABLE} }} variable.`);
          return;
        }

        this.headTop = nodeProcessor.headTop;
        this.headBottom = nodeProcessor.headBottom;
        this.scriptBottom = nodeProcessor.scriptBottom;

        pageSources.addAllToSet(this.includedFiles);
        await this.config.externalManager.generateDependencies(
          pageSources.getDynamicIncludeSrc(),
          this.includedFiles,
          this.layoutUserScriptsAndStyles);
      }

      insertPage(pageContent: string, pageNav: string, pageIncludedFiles: Set<string>): string {
        this.includedFiles.forEach(filePath => pageIncludedFiles.add(filePath));
        // Use function for .replace, in case string contains special patterns (e.g. $$, $&, $1, ...)
        return this.layoutProcessed
          .replace(this.layoutPageBodyVariable, () => pageContent)
          .replace(this.layoutPageNavUuid, () => pageNav);
      }

      getPageNjkAssets(): PageNjkAssets {
        return {
          headTop: this.headTop,
          headBottom: this.headBottom,
          scriptBottom: this.scriptBottom,
          layoutUserScriptsAndStyles: this.layoutUserScriptsAndStyles,
        };
      }
    }