diff --git a/.prettierrc b/.prettierrc index c1a6f6671..d81dbaeb5 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,5 @@ { "singleQuote": true, - "trailingComma": "es5" + "trailingComma": "es5", + "endOfLine": "auto" } diff --git a/packages/cli/test/fixtures/patternlab-config.json b/packages/cli/test/fixtures/patternlab-config.json index 4bd371e26..b59f39961 100644 --- a/packages/cli/test/fixtures/patternlab-config.json +++ b/packages/cli/test/fixtures/patternlab-config.json @@ -73,6 +73,7 @@ "patternExportDirectory": "./pattern_exports/", "patternExportPatternPartials": [], "patternMergeVariantArrays": true, + "renderFlatPatternsOnViewAllPages": false, "serverOptions": { "wait": 1000 }, diff --git a/packages/core/patternlab-config.json b/packages/core/patternlab-config.json index 28d62a2d0..519282d2c 100644 --- a/packages/core/patternlab-config.json +++ b/packages/core/patternlab-config.json @@ -76,6 +76,7 @@ "patternExportPreserveDirectoryStructure": true, "patternExportRaw": false, "patternMergeVariantArrays": true, + "renderFlatPatternsOnViewAllPages": false, "serverOptions": { "wait": 1000 }, diff --git a/packages/core/src/lib/markdown_parser.js b/packages/core/src/lib/markdown_parser.js index c940a5f67..224d8012a 100644 --- a/packages/core/src/lib/markdown_parser.js +++ b/packages/core/src/lib/markdown_parser.js @@ -13,8 +13,9 @@ const markdown_parser = function() { let returnObject = {}; try { - //for each block process the yaml frontmatter and markdown - const frontmatterRE = /---\r?\n{1}([\s\S]*)---\r?\n{1}([\s\S]*)+/gm; + // for each block process the yaml frontmatter and markdown + // even if the pattern only has pattern data without further documentation + const frontmatterRE = /---\r?\n{1}([\s\S]*)---([\s\S]*)+/gm; const chunks = frontmatterRE.exec(block); if (chunks) { diff --git a/packages/core/src/lib/object_factory.js b/packages/core/src/lib/object_factory.js index 83524a9c7..e4523d9bd 100644 --- a/packages/core/src/lib/object_factory.js +++ b/packages/core/src/lib/object_factory.js @@ -1,108 +1,98 @@ 'use strict'; -const patternEngines = require('./pattern_engines'); + +const _ = require('lodash'); const path = require('path'); +const patternEngines = require('./pattern_engines'); -// patternPrefixMatcher is intended to match the leading maybe-underscore, +// prefixMatcher is intended to match the leading maybe-underscore, // zero or more digits, and maybe-dash at the beginning of a pattern file name we can hack them // off and get at the good part. -const patternPrefixMatcher = /^_?(\d+-)?/; +const prefixMatcher = /^_?(\d+-)?/; -// Pattern properties /** - * Pattern constructor - * @constructor + * Pattern constructor / Pattern properties + * + * Before changing functionalities of the pattern object please read the following pull requests + * to get more details about the behavior of the folder structure + * https://patternlab.io/docs/overview-of-patterns/#heading-deeper-nesting + * https://github.com/pattern-lab/patternlab-node/pull/992 + * https://github.com/pattern-lab/patternlab-node/pull/1016 + * https://github.com/pattern-lab/patternlab-node/pull/1143 + * + * @param {string} relPath relative directory + * @param {Object} jsonFileData The JSON used to render values in the pattern. + * @param {Patternlab} patternlab The actual pattern lab instance + * @param {boolean} isPromoteToFlatPatternRun specifies if the pattern needs to be removed from its deep nesting folder */ -const Pattern = function(relPath, data, patternlab) { +const Pattern = function( + relPath, + jsonFileData, + patternlab, + isPromoteToFlatPatternRun +) { + this.relPath = path.normalize(relPath); // '00-atoms/00-global/00-colors.mustache' + /** * We expect relPath to be the path of the pattern template, relative to the * root of the pattern tree. Parse out the path parts and save the useful ones. - * @param {relPath} relative directory - * @param {data} The JSON used to render values in the pattern. - * @param {patternlab} rendered html files for the pattern */ - const pathObj = path.parse(path.normalize(relPath)); - const info = {}; - // 00-colors(.mustache) is subbed in 00-atoms-/00-global/00-colors - info.hasDir = - path.basename(pathObj.dir).replace(patternPrefixMatcher, '') === - pathObj.name.replace(patternPrefixMatcher, '') || - path.basename(pathObj.dir).replace(patternPrefixMatcher, '') === - pathObj.name.split('~')[0].replace(patternPrefixMatcher, ''); - - info.dir = info.hasDir ? pathObj.dir.split(path.sep).pop() : ''; - info.dirLevel = pathObj.dir.split(path.sep).length; + const pathObj = path.parse(this.relPath); + + const info = this.getPatternInfo( + pathObj, + patternlab, + isPromoteToFlatPatternRun + ); - this.relPath = path.normalize(relPath); // '00-atoms/00-global/00-colors.mustache' this.fileName = pathObj.name; // '00-colors' this.subdir = pathObj.dir; // '00-atoms/00-global' - if ((this.subdir.match(/\w(?=\\)|\w(?=\/)/g) || []).length > 1) { - this.subdir = this.subdir.split(/\/|\\/, 2).join(path.sep); // '00-atoms/03-controls/00-button' -> '00-atoms/03-controls' - } this.fileExtension = pathObj.ext; // '.mustache' - // this is the unique name, subDir + fileName (sans extension) - this.name = ''; - if (info.hasDir && info.dirLevel > 2) { - let variant = ''; - - if (this.fileName.indexOf('~') !== -1) { - variant = '-' + this.fileName.split('~')[1]; - } - this.name = this.subdir.replace(/[\/\\]/g, '-') + variant; + // TODO: Remove if when dropping ordering by prefix and keep else code + if (info.patternHasOwnDir) { + // Since there is still the requirement of having the numbers provided for sorting + // this will be required to keep the folder prefix and the variant name + // /00-atoms/00-global/00-colors/colors~variant.hbs + // -> 00-atoms-00-global-00-colors-variant + this.name = `${info.shortNotation}-${path.parse(pathObj.dir).base}${ + this.fileName.indexOf('~') !== -1 ? '-' + this.fileName.split('~')[1] : '' + }`; } else { - this.name = - this.subdir.replace(/[\/\\]/g, '-') + - '-' + - this.fileName.replace('~', '-'); // '00-atoms-00-global-00-colors' + // this is the unique name, subDir + fileName (sans extension) + this.name = `${info.shortNotation}-${this.fileName.replace('~', '-')}`; } // the JSON used to render values in the pattern - this.jsonFileData = data || {}; + this.jsonFileData = jsonFileData || {}; // strip leading "00-" from the file name and flip tildes to dashes this.patternBaseName = this.fileName - .replace(patternPrefixMatcher, '') + .replace(prefixMatcher, '') .replace('~', '-'); // 'colors' - // Fancy name. No idea how this works. 'Colors' - this.patternName = this.patternBaseName - .split('-') - .reduce(function(val, working) { - return ( - val.charAt(0).toUpperCase() + - val.slice(1) + - ' ' + - working.charAt(0).toUpperCase() + - working.slice(1) - ); - }, '') - .trim(); //this is the display name for the ui. strip numeric + hyphen prefixes + // Fancy name - Uppercase letters of pattern name partials. + // global-colors -> 'Global Colors' + // this is the display name for the ui. strip numeric + hyphen prefixes + this.patternName = _.startCase(this.patternBaseName); //00-atoms if needed - this.patternType = this.getDirLevel(0); + this.patternType = this.getDirLevel(0, info); // the top-level pattern group this pattern belongs to. 'atoms' - this.patternGroup = this.patternType.replace(patternPrefixMatcher, ''); + this.patternGroup = this.patternType.replace(prefixMatcher, ''); //00-colors if needed - this.patternSubType = this.getDirLevel(1); + this.patternSubType = this.getDirLevel(1, info); // the sub-group this pattern belongs to. - this.patternSubGroup = this.patternSubType.replace(patternPrefixMatcher, ''); // 'global' + this.patternSubGroup = this.patternSubType.replace(prefixMatcher, ''); // 'global' // the joined pattern group and subgroup directory - this.flatPatternPath = - info.hasDir && info.dirLevel > 2 - ? this.subdir - .replace(/[/\\]/g, '-') - .replace(new RegExp('-' + info.dir + '$'), '') - : this.subdir.replace(/[\/\\]/g, '-'); // '00-atoms-00-global' - - // calculated path from the root of the public directory to the generated + this.flatPatternPath = info.shortNotation; // '00-atoms-00-global' + + // Calculated path from the root of the public directory to the generated // (rendered!) html file for this pattern, to be shown in the iframe - this.patternLink = this.patternSectionSubtype - ? `$${this.name}/index.html` - : patternlab + this.patternLink = patternlab ? this.getPatternLink(patternlab, 'rendered') : null; @@ -112,11 +102,24 @@ const Pattern = function(relPath, data, patternlab) { // Let's calculate the verbose name ahead of time! We don't use path.sep here // on purpose. This isn't a file name! - this.verbosePartial = - this.subdir.split(path.sep).join('/') + '/' + this.fileName; + this.verbosePartial = `${info.shortNotation}/${this.fileName}`; + + /** + * Definition of flat pattern: + * The flat pattern is a high level pattern which is attached directly to + * the main root folder or to a root directory. + * --- This --- + * root + * flatpattern + * --- OR That --- + * root + * molecules + * flatpattern + */ + this.isFlatPattern = + this.patternGroup === this.patternSubGroup || !this.patternSubGroup; this.isPattern = true; - this.isFlatPattern = this.patternGroup === this.patternSubGroup; this.patternState = ''; this.template = ''; this.patternPartialCode = ''; @@ -125,7 +128,7 @@ const Pattern = function(relPath, data, patternlab) { this.lineageR = []; this.lineageRIndex = []; this.isPseudoPattern = false; - this.order = Number.MAX_SAFE_INTEGER; + this.order = 0; this.engine = patternEngines.getEngineForPattern(this); /** @@ -135,7 +138,7 @@ const Pattern = function(relPath, data, patternlab) { this.compileState = null; /** - * Timestamp in milliseconds when the pattern template or auxilary file (e.g. json) were modified. + * Timestamp in milliseconds when the pattern template or auxiliary file (e.g. json) were modified. * If multiple files are affected, this is the timestamp of the most recent change. * * @see {@link pattern} @@ -175,9 +178,16 @@ Pattern.prototype = { } }, - // calculated path from the root of the public directory to the generated html - // file for this pattern. - // Should look something like '00-atoms-00-global-00-colors/00-atoms-00-global-00-colors.html' + /** + * calculated path from the root of the public directory to the generated html + * file for this pattern. + * + * Should look something like '00-atoms-00-global-00-colors/00-atoms-00-global-00-colors.html' + * + * @param {Patternlab} patternlab Current patternlab instance + * @param {string} suffixType File suffix + * @param {string} customfileExtension Custom extension + */ getPatternLink: function(patternlab, suffixType, customfileExtension) { // if no suffixType is provided, we default to rendered const suffixConfig = patternlab.config.outputFileSuffixes; @@ -196,8 +206,10 @@ Pattern.prototype = { return this.name + path.sep + this.name + suffix + '.html'; }, - // the finders all delegate to the PatternEngine, which also encapsulates all - // appropriate regexes + /** + * The finders all delegate to the PatternEngine, which also + * encapsulates all appropriate regexes + */ findPartials: function() { return this.engine.findPartials(this); }, @@ -214,34 +226,116 @@ Pattern.prototype = { return this.engine.findListItems(this); }, - findPartial: function(partialString) { - return this.engine.findPartial(partialString); + findPartial: function(partialstring) { + return this.engine.findPartial(partialstring); }, - getDirLevel: function(level) { + /** + * Get a directory on a specific level of the pattern path + * + * @param {Number} level Level of folder to get + * @param {Object} pInfo general information about the pattern + */ + getDirLevel: function(level, pInfo) { const items = this.subdir.split(path.sep); + pInfo && pInfo.patternHasOwnDir && items.pop(); if (items[level]) { return items[level]; - } else if (items[level - 1]) { - return items[level - 1]; - } else { + } else if (level >= 1) { return ''; + } else { + // I'm not quite sure about that but its better than empty node + // TODO: verify + return 'root'; } }, + + /** + * Reset the information that the pattern has it's own directory, + * so that this pattern will not be handled as flat pattern if it + * is located on a top level folder. + * + * @param {Patternlab} patternlab Current patternlab instance + */ + promoteFromFlatPatternToDirectory: function(patternlab) { + const p = new Pattern(this.relPath, this.jsonFileData, patternlab, true); + // Only reset the specific fields, not everything + Object.assign(this, { + name: p.name, + patternLink: p.patternLink, + patternGroup: p.patternGroup, + patternType: p.patternType, + patternSubGroup: p.patternSubGroup, + patternSubType: p.patternSubType, + isFlatPattern: p.isFlatPattern, + flatPatternPath: p.flatPatternPath, + patternPartial: p.patternPartial, + verbosePartial: p.verbosePartial, + }); + }, + + /** + * The "info" object contains information about pattern structure if it is + * a nested pattern or if it just a sub folder structure. It's just used for + * internal purposes. Remember every pattern information based on "this.*" + * will be used by other functions + * + * @param pathObj path.parse() object containing useful path information + */ + getPatternInfo: (pathObj, patternlab, isPromoteToFlatPatternRun) => { + const info = { + // 00-colors(.mustache) is deeply nested in 00-atoms-/00-global/00-colors + patternlab: patternlab, + patternHasOwnDir: !isPromoteToFlatPatternRun + ? path.basename(pathObj.dir).replace(prefixMatcher, '') === + pathObj.name.replace(prefixMatcher, '') || + path.basename(pathObj.dir).replace(prefixMatcher, '') === + pathObj.name.split('~')[0].replace(prefixMatcher, '') + : false, + }; + + info.dir = info.patternHasOwnDir ? pathObj.dir.split(path.sep).pop() : ''; + info.dirLevel = pathObj.dir.split(path.sep).filter(s => !!s).length; + + if (info.dirLevel === 0 || (info.dirLevel === 1 && info.patternHasOwnDir)) { + // -> ./ + info.shortNotation = 'root'; + } else if (info.dirLevel === 2 && info.patternHasOwnDir) { + // -> ./folder + info.shortNotation = path.dirname(pathObj.dir); + } else { + // -> ./folder/folder + info.shortNotation = pathObj.dir + .split(/\/|\\/, 2) + .join('-') + .replace(new RegExp(`-${info.dir}$`), ''); + info.verbosePartial = pathObj.dir + .split(/\/|\\/, 2) + .join('/') + .replace(new RegExp(`-${info.dir}$`), ''); + } + + return info; + }, }; // Pattern static methods -// factory: creates an empty Pattern for miscellaneous internal use, such as -// by list_item_hunter +/** + * factory: creates an empty Pattern for miscellaneous internal use, such as + * by list_item_hunter + * + * @param {Object} customProps Properties to apply to new pattern + * @param {Patternlab} patternlab Current patternlab instance + */ Pattern.createEmpty = function(customProps, patternlab) { let relPath = ''; if (customProps) { if (customProps.relPath) { relPath = customProps.relPath; } else if (customProps.subdir && customProps.filename) { - relPath = customProps.subdir + path.sep + customProps.filename; + relPath = path.join(customProps.subdir, customProps.filename); } } @@ -249,9 +343,11 @@ Pattern.createEmpty = function(customProps, patternlab) { return Object.assign(pattern, customProps); }; -// factory: creates an Pattern object on-demand from a hash; the hash accepts -// parameters that replace the positional parameters that the Pattern -// constructor takes. +/** + * factory: creates a Pattern object on-demand from a hash; the hash accepts + * parameters that replace the positional parameters that the Pattern + * constructor takes. + */ Pattern.create = function(relPath, data, customProps, patternlab) { const newPattern = new Pattern(relPath || '', data || null, patternlab); return Object.assign(newPattern, customProps); diff --git a/packages/core/src/lib/parameter_hunter.js b/packages/core/src/lib/parameter_hunter.js index eeec121dc..aa2ce38dc 100644 --- a/packages/core/src/lib/parameter_hunter.js +++ b/packages/core/src/lib/parameter_hunter.js @@ -248,7 +248,7 @@ const parameter_hunter = function() { //compile this partial immeadiately, essentially consuming it. function findparameters(pattern, patternlab) { if (pattern.parameteredPartials && pattern.parameteredPartials.length > 0) { - logger.debug(`processing patternParameters for ${pattern.partialName}`); + logger.debug(`processing patternParameters for ${pattern.patternName}`); return pattern.parameteredPartials.reduce((previousPromise, pMatch) => { return previousPromise diff --git a/packages/core/src/lib/parseLink.js b/packages/core/src/lib/parseLink.js index d3d0c455e..368ce4cc8 100644 --- a/packages/core/src/lib/parseLink.js +++ b/packages/core/src/lib/parseLink.js @@ -20,37 +20,64 @@ module.exports = function(patternlab, obj, key) { const linkMatches = dataObjAsString.match(linkRE); if (linkMatches) { - for (let i = 0; i < linkMatches.length; i++) { - const dataLink = linkMatches[i]; + linkMatches.forEach(dataLink => { if (dataLink && dataLink.split('.').length >= 2) { //get the partial the link refers to - const linkPatternPartial = dataLink - .split('.')[1] - .replace('"', '') - .replace("'", ''); - const pattern = getPartial(linkPatternPartial, patternlab); - if (pattern !== undefined) { - //get the full built link and replace it - let fullLink = patternlab.data.link[linkPatternPartial]; - if (fullLink) { - fullLink = path.normalize(fullLink).replace(/\\/g, '/'); + const linkPatternPartial = dataLink.split('.')[1].replace(/'|"/g, ''); + const rawLink = `link.${linkPatternPartial}`; + let replacement = null; - logger.debug( - `expanded data link from ${dataLink} to ${fullLink} inside ${key}` - ); + if (linkPatternPartial.match(/viewall\-.+\-all/)) { + // Reverse engineer viewall-group-all link (if there is a pattern with that + // group there will be a view all page for that group) + const partial = linkPatternPartial + .replace('viewall-', '') + .replace('-all', ''); + const pattern = patternlab.patterns.find( + p => p.patternGroup === partial + ); - //also make sure our global replace didn't mess up a protocol - fullLink = fullLink.replace(/:\//g, '://'); - dataObjAsString = dataObjAsString.replace( - 'link.' + linkPatternPartial, - fullLink - ); + if (pattern) { + replacement = `/patterns/${partial}/index.html`; } + } else if (linkPatternPartial.match(/viewall\-.+/)) { + // Reverse engineer viewall-group-subgroup link (if there is a pattern with that + // group and subgroup there will be a view all page for that group) + const partial = linkPatternPartial.replace('viewall-', ''); + const pattern = patternlab.patterns.find( + p => `${p.patternGroup}-${p.patternSubGroup}` === partial + ); + + if (pattern) { + replacement = `/patterns/${pattern.flatPatternPath}/index.html`; + } + } else { + // Just search for the pattern partial + const pattern = getPartial(linkPatternPartial, patternlab); + + if (pattern) { + // get the full built link and replace it + let fullLink = patternlab.data.link[linkPatternPartial]; + if (fullLink) { + fullLink = path.normalize(fullLink).replace(/\\/g, '/'); + + logger.debug( + `expanded data link from ${dataLink} to ${fullLink} inside ${key}` + ); + + // also make sure our global replace didn't mess up a protocol + replacement = fullLink.replace(/:\//g, '://'); + } + } + } + + if (replacement) { + dataObjAsString = dataObjAsString.replace(rawLink, replacement); } else { logger.warning(`pattern not found for ${dataLink} inside ${key}`); } } - } + }); } let dataObj; diff --git a/packages/core/src/lib/readDocumentation.js b/packages/core/src/lib/readDocumentation.js index 2670bb307..edd11de6e 100644 --- a/packages/core/src/lib/readDocumentation.js +++ b/packages/core/src/lib/readDocumentation.js @@ -54,6 +54,15 @@ module.exports = function(pattern, patternlab) { if (markdownObject.links) { pattern.links = markdownObject.links; } + + if ( + !markdownObject.hasOwnProperty('deeplyNested') || + (markdownObject.hasOwnProperty('deeplyNested') && + !markdownObject.deeplyNested) + ) { + // Reset to pattern without own pattern-directory + pattern.promoteFromFlatPatternToDirectory(patternlab); + } } else { logger.warning(`error processing markdown for ${pattern.patternPartial}`); } diff --git a/packages/core/src/lib/ui_builder.js b/packages/core/src/lib/ui_builder.js index 5dcb36986..4a077a613 100644 --- a/packages/core/src/lib/ui_builder.js +++ b/packages/core/src/lib/ui_builder.js @@ -3,12 +3,11 @@ const path = require('path'); const _ = require('lodash'); -const of = require('./object_factory'); -const Pattern = of.Pattern; +const Pattern = require('./object_factory').Pattern; const logger = require('./log'); const uikitExcludePattern = require('./uikitExcludePattern'); -//these are mocked in unit tests, so let them be overridden +// these are mocked in unit tests, so let them be overridden let render = require('./render'); //eslint-disable-line prefer-const let fs = require('fs-extra'); //eslint-disable-line prefer-const let buildFooter = require('./buildFooter'); //eslint-disable-line prefer-const @@ -26,7 +25,7 @@ const ui_builder = function() { patternlab.patternPaths[pattern.patternGroup] = {}; } - //only add real patterns + // only add real patterns if (pattern.isPattern && !pattern.isDocPattern) { patternlab.patternPaths[pattern.patternGroup][pattern.patternBaseName] = pattern.name; @@ -44,20 +43,18 @@ const ui_builder = function() { } if ( - !patternlab.viewAllPaths[pattern.patternGroup][pattern.patternSubGroup] + !patternlab.viewAllPaths[pattern.patternGroup][pattern.patternSubGroup] && + pattern.patternSubGroup ) { + // note these retain any number prefixes if present, because these paths match the filesystem patternlab.viewAllPaths[pattern.patternGroup][ pattern.patternSubGroup - ] = {}; + ] = `${pattern.patternType}-${pattern.patternSubType}`; } - //note these retain any number prefixes if present, because these paths match the filesystem - patternlab.viewAllPaths[pattern.patternGroup][pattern.patternSubGroup] = - pattern.patternType + '-' + pattern.patternSubType; - - //add all if it does not exist yet + // add all if it does not exist yet if (!patternlab.viewAllPaths[pattern.patternGroup].all) { - patternlab.viewAllPaths[pattern.patternGroup].all = pattern.patternType; + patternlab.viewAllPaths[pattern.patternGroup].all = pattern.patternGroup; } } @@ -89,7 +86,7 @@ const ui_builder = function() { return true; } - //this is meant to be a homepage that is not present anywhere else + // this is meant to be a homepage that is not present anywhere else isOmitted = pattern.patternPartial === patternlab.config.defaultPattern; if (isOmitted) { logger.info( @@ -99,7 +96,7 @@ const ui_builder = function() { return true; } - //this pattern is contained with a directory prefixed with an underscore (a handy way to hide whole directories from the nav + // this pattern is contained with a directory prefixed with an underscore (a handy way to hide whole directories from the nav isOmitted = pattern.relPath.charAt(0) === '_' || pattern.relPath.indexOf(path.sep + '_') > -1; @@ -110,7 +107,7 @@ const ui_builder = function() { return true; } - //this pattern is a head or foot pattern + // this pattern is a head or foot pattern isOmitted = pattern.isMetaPattern; if (isOmitted) { logger.info( @@ -119,7 +116,7 @@ const ui_builder = function() { return true; } - //yay, let's include this on the front end + // yay, let's include this on the front end return isOmitted; } @@ -131,7 +128,7 @@ const ui_builder = function() { * @returns the found or created pattern object */ function injectDocumentationBlock(pattern, patternlab, isSubtypePattern) { - //first see if loadPattern processed one already + // first see if loadPattern processed one already let docPattern = patternlab.subtypePatterns[ pattern.patternGroup + @@ -139,29 +136,31 @@ const ui_builder = function() { ]; if (docPattern) { docPattern.isDocPattern = true; - docPattern.order = -Number.MAX_SAFE_INTEGER; + docPattern.order = Number.MIN_SAFE_INTEGER; return docPattern; } - //if not, create one now + // if not, create one now docPattern = new Pattern.createEmpty( { name: pattern.flatPatternPath, - patternName: isSubtypePattern - ? pattern.patternSubGroup - : pattern.patternGroup, + patternName: _.startCase( + isSubtypePattern ? pattern.patternSubGroup : pattern.patternGroup + ), patternDesc: '', - patternPartial: - 'viewall-' + - pattern.patternGroup + - (isSubtypePattern ? '-' + pattern.patternSubGroup : ''), - patternSectionSubtype: isSubtypePattern, - patternLink: pattern.flatPatternPath + path.sep + 'index.html', + patternPartial: `viewall-${pattern.patternGroup}-${ + isSubtypePattern ? pattern.patternSubGroup : 'all' + }`, + patternSectionSubtype: true, + patternLink: path.join( + isSubtypePattern ? pattern.flatPatternPath : pattern.patternGroup, + 'index.html' + ), isPattern: false, engine: null, flatPatternPath: pattern.flatPatternPath, isDocPattern: true, - order: -Number.MAX_SAFE_INTEGER, + order: Number.MIN_SAFE_INTEGER, }, patternlab ); @@ -176,10 +175,8 @@ const ui_builder = function() { */ function addPatternType(patternlab, pattern) { patternlab.patternTypes.push({ - patternTypeLC: pattern.patternGroup.toLowerCase(), - patternTypeUC: - pattern.patternGroup.charAt(0).toUpperCase() + - pattern.patternGroup.slice(1), + patternTypeLC: _.kebabCase(pattern.patternGroup), + patternTypeUC: _.startCase(pattern.patternGroup), patternType: pattern.patternType, patternTypeDash: pattern.patternGroup, //todo verify patternTypeItems: [], @@ -237,10 +234,8 @@ const ui_builder = function() { */ function addPatternSubType(patternlab, pattern) { const newSubType = { - patternSubtypeLC: pattern.patternSubGroup.toLowerCase(), - patternSubtypeUC: - pattern.patternSubGroup.charAt(0).toUpperCase() + - pattern.patternSubGroup.slice(1), + patternSubtypeLC: _.kebabCase(pattern.patternSubGroup), + patternSubtypeUC: _.startCase(pattern.patternSubGroup), patternSubtype: pattern.patternSubType, patternSubtypeDash: pattern.patternSubGroup, //todo verify patternSubtypeItems: [], @@ -258,32 +253,17 @@ const ui_builder = function() { * Creates a patternSubTypeItem object from a pattern * This is a menu item you click on * @param pattern - the pattern to derive the subtypeitem from - * @returns {{patternPartial: string, patternName: (*|string), patternState: string, patternSrcPath: string, patternPath: string}} + * @returns {{patternPartial: string, patternName: (*|string), patternState: string, patternPath: string}} */ function createPatternSubTypeItem(pattern) { - let patternPath = ''; - if (pattern.isFlatPattern) { - patternPath = - pattern.flatPatternPath + - '-' + - pattern.fileName + - '/' + - pattern.flatPatternPath + - '-' + - pattern.fileName + - '.html'; - } else { - patternPath = - pattern.flatPatternPath + '/' + pattern.flatPatternPath + '.html'; - } - return { patternPartial: pattern.patternPartial, patternName: pattern.patternName, patternState: pattern.patternState, - patternSrcPath: encodeURI(pattern.subdir + '/' + pattern.fileName), - patternPath: patternPath, - order: pattern.order, + patternPath: pattern.patternLink, + name: pattern.name, + isDocPattern: false, + order: Number(pattern.order) || 0, // Failsafe is someone entered a string }; } @@ -305,11 +285,13 @@ const ui_builder = function() { newSubTypeItem = { patternPartial: 'viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup, - patternName: 'View All', + patternName: `View All`, patternPath: encodeURI(pattern.flatPatternPath + '/index.html'), patternType: pattern.patternType, patternSubtype: pattern.patternSubtype, - order: 0, + name: pattern.flatPatternPath, + isDocPattern: true, + order: Number.MAX_SAFE_INTEGER, }; } else { newSubTypeItem = createPatternSubTypeItem(pattern); @@ -336,20 +318,16 @@ const ui_builder = function() { ); } - if (!patternType.patternItems) { - patternType.patternItems = []; - } - + patternType.patternItems = patternType.patternItems || []; if (isViewAllVariant) { - if (!pattern.isFlatPattern) { - //todo: it'd be nice if we could get this into createPatternSubTypeItem someday - patternType.patternItems.push({ - patternPartial: 'viewall-' + pattern.patternGroup + '-all', - patternName: 'View All', - patternPath: encodeURI(pattern.patternType + '/index.html'), - order: -Number.MAX_SAFE_INTEGER, - }); - } + patternType.patternItems.push({ + patternPartial: `viewall-${pattern.patternGroup}-all`, + patternName: `View all ${_.startCase(pattern.patternGroup)}`, + patternPath: encodeURI(pattern.patternGroup + '/index.html'), + name: pattern.patternGroup, + isDocPattern: true, + order: Number.MAX_SAFE_INTEGER, + }); } else { patternType.patternItems.push(createPatternSubTypeItem(pattern)); } @@ -359,14 +337,6 @@ const ui_builder = function() { ]); } - // function getPatternItems(patternlab, patternType) { - // var patternType = _.find(patternlab.patternTypes, ['patternTypeLC', patternType]); - // if (patternType) { - // return patternType.patternItems; - // } - // return []; - // } - /** * Sorts patterns based on order property found within pattern markdown, falling back on name. * @param patternsArray - patterns to sort @@ -385,7 +355,7 @@ const ui_builder = function() { aOrder = Number.MAX_SAFE_INTEGER; } - //alwasy return a docPattern first + // always return a docPattern first if (a.isDocPattern && !b.isDocPattern) { return -1; } @@ -394,8 +364,8 @@ const ui_builder = function() { return 1; } - //use old alphabetical ordering if we have nothing else to use - //pattern.order will be Number.MAX_SAFE_INTEGER if never defined by markdown, or markdown parsing fails + // use old alphabetical ordering if we have nothing else to use + // pattern.order will be Number.MAX_SAFE_INTEGER if never defined by markdown, or markdown parsing fails if ( aOrder === Number.MAX_SAFE_INTEGER && bOrder === Number.MAX_SAFE_INTEGER @@ -408,7 +378,7 @@ const ui_builder = function() { } } - //if we get this far, we can sort safely + // if we get this far, we can sort safely if (aOrder && bOrder) { if (aOrder > bOrder) { return 1; @@ -425,7 +395,7 @@ const ui_builder = function() { * Returns an object representing how the front end styleguide and navigation is structured * @param patternlab - global data store * @param uikit - the current uikit being built - * @returns ptterns grouped by type -> subtype like atoms -> global -> pattern, pattern, pattern + * @returns patterns grouped by type -> subtype like atoms -> global -> pattern, pattern, pattern */ function groupPatterns(patternlab, uikit) { const groupedPatterns = { @@ -433,7 +403,7 @@ const ui_builder = function() { }; _.forEach(patternlab.patterns, function(pattern) { - //ignore patterns we can omit from rendering directly + // ignore patterns we can omit from rendering directly pattern.omitFromStyleguide = isPatternExcluded( pattern, patternlab, @@ -447,14 +417,17 @@ const ui_builder = function() { groupedPatterns.patternGroups[pattern.patternGroup] = {}; pattern.isSubtypePattern = false; addPatternType(patternlab, pattern); - - //todo: Pattern Type View All and Documentation - //groupedPatterns.patternGroups[pattern.patternGroup]['viewall-' + pattern.patternGroup] = injectDocumentationBlock(pattern, patternlab, false); - addPatternItem(patternlab, pattern, true); + if ( + !pattern.isFlatPattern || + patternlab.config.renderFlatPatternsOnViewAllPages + ) { + addPatternItem(patternlab, pattern, true); + } + addToViewAllPaths(patternlab, pattern); } - //continue building navigation for nested patterns - if (pattern.patternGroup !== pattern.patternSubGroup) { + // continue building navigation for nested patterns + if (!pattern.isFlatPattern) { if ( !groupedPatterns.patternGroups[pattern.patternGroup][ pattern.patternSubGroup @@ -491,6 +464,23 @@ const ui_builder = function() { return groupedPatterns; } + /** + * Search all flat patterns of a specific pattern type + * + * @param {Patternlab} patternlab Current patternlab instance + * @param {string} patternType indicator which patterns to search for + */ + function getFlatPatternItems(patternlab, patternType) { + const patterns = _.filter( + patternlab.patterns, + pattern => pattern.patternGroup === patternType && pattern.isFlatPattern + ); + if (patterns) { + return sortPatterns(patterns); + } + return []; + } + /** * Takes a set of patterns and builds a viewall HTML page for them * Used by the type and subtype viewall sets @@ -501,15 +491,15 @@ const ui_builder = function() { */ function buildViewAllHTML(patternlab, patterns, patternPartial, uikit) { return render( - Pattern.createEmpty({ extendedTemplate: uikit.viewAll }), + Pattern.createEmpty({ extendedTemplate: uikit.viewAll }, patternlab), { - //data + // data partials: patterns, patternPartial: 'viewall-' + patternPartial, cacheBuster: patternlab.cacheBuster, }, { - //templates + // templates patternSection: uikit.patternSection, patternSectionSubtype: uikit.patternSectionSubType, } @@ -534,57 +524,59 @@ const ui_builder = function() { ) { const paths = patternlab.config.paths; let patterns = []; - let writeViewAllFile = true; - //loop through the grouped styleguide patterns, building at each level + // loop through the grouped styleguide patterns, building at each level const allPatternTypePromises = _.map( styleguidePatterns.patternGroups, (patternGroup, patternType) => { let typePatterns = []; let styleguideTypePatterns = []; - const styleGuideExcludes = - patternlab.config.styleGuideExcludes || - patternlab.config.styleguideExcludes; + const styleGuideExcludes = patternlab.config.styleGuideExcludes || []; + + /** + * View all pages for subgroups + */ const subTypePromises = _.map( _.values(patternGroup), (patternSubtypes, patternSubtype, originalPatternGroup) => { let p; - const samplePattern = _.find(patternSubtypes, st => { - return !st.patternPartial.startsWith('viewall-'); - }); + const samplePattern = _.find( + patternSubtypes, + st => !st.patternPartial.startsWith('viewall-') + ); const patternName = Object.keys( _.values(originalPatternGroup)[patternSubtype] )[1]; const patternPartial = patternType + '-' + samplePattern.patternSubType; - //do not create a viewall page for flat patterns + // do not create a viewall page for flat patterns if (patternType === patternName) { - writeViewAllFile = false; logger.debug( `skipping ${patternType} as flat patterns do not have view all pages` ); return Promise.resolve(); } - //render the footer needed for the viewall template + // render the footer needed for the viewall template return buildFooter(patternlab, `viewall-${patternPartial}`, uikit) .then(footerHTML => { - //render the viewall template by finding these smallest subtype-grouped patterns + // render the viewall template by finding these smallest subtype-grouped patterns const subtypePatterns = sortPatterns(_.values(patternSubtypes)); - //determine if we should write at this time by checking if these are flat patterns or grouped patterns + // determine if we should write at this time by checking if these are flat patterns or grouped patterns p = _.find(subtypePatterns, function(pat) { return pat.isDocPattern; }); - //determine if we should omit this subpatterntype completely from the viewall page + // determine if we should omit this subpatterntype completely from the viewall page const omitPatternType = styleGuideExcludes && styleGuideExcludes.length && - _.some(styleGuideExcludes, function(exclude) { - return exclude === patternType + '/' + patternName; - }); + _.some( + styleGuideExcludes, + exclude => exclude === `${patternType}/${patternName}` + ); if (omitPatternType) { logger.debug( `Omitting ${patternType}/${patternName} from building a viewall page because its patternSubGroup is specified in styleguideExcludes.` @@ -597,7 +589,7 @@ const ui_builder = function() { typePatterns = typePatterns.concat(subtypePatterns); - //render the viewall template for the subtype + // render the viewall template for the subtype return buildViewAllHTML( patternlab, subtypePatterns, @@ -609,9 +601,10 @@ const ui_builder = function() { path.join( process.cwd(), uikit.outputDir, - paths.public.patterns + - p.flatPatternPath + - '/index.html' + path.join( + `${paths.public.patterns}${p.flatPatternPath}`, + 'index.html' + ) ), mainPageHeadHtml + viewAllHTML + footerHTML ); @@ -621,107 +614,115 @@ const ui_builder = function() { logger.error('Error building ViewAllHTML'); }); }) - .then(() => { - //do not create a viewall page for flat patterns - if (!writeViewAllFile || !p) { + .catch(reason => { + console.log(reason); + logger.error('Error building footer HTML'); + }); + } + ); + + /** + * View all pages for groups + */ + return Promise.all(subTypePromises) + .then(() => { + // render the footer needed for the viewall template + return buildFooter(patternlab, `viewall-${patternType}-all`, uikit) + .then(footerHTML => { + const sortedFlatPatterns = getFlatPatternItems( + patternlab, + patternType + ); + + if (patternlab.config.renderFlatPatternsOnViewAllPages) { + // Check if this is a flat pattern group + typePatterns = sortedFlatPatterns.concat(typePatterns); + } + + // get the appropriate patternType + const anyPatternOfType = _.find(typePatterns, function(pat) { + return pat.patternType && pat.patternType !== ''; + }); + + if (!anyPatternOfType || !typePatterns.length) { logger.debug( `skipping ${patternType} as flat patterns do not have view all pages` ); - return Promise.resolve(); + return Promise.resolve([]); } - //render the footer needed for the viewall template - return buildFooter( + // render the viewall template for the type + return buildViewAllHTML( patternlab, - 'viewall-' + patternType + '-all', + typePatterns, + patternType, uikit ) - .then(footerHTML => { - //add any flat patterns - //todo this isn't quite working yet - //typePatterns = typePatterns.concat(getPatternItems(patternlab, patternType)); - - //get the appropriate patternType - const anyPatternOfType = _.find(typePatterns, function( - pat - ) { - return pat.patternType && pat.patternType !== ''; - }); - - if (!anyPatternOfType) { + .then(viewAllHTML => { + fs.outputFileSync( + path.join( + process.cwd(), + uikit.outputDir, + path.join( + `${paths.public.patterns}${patternType}`, + 'index.html' + ) + ), + mainPageHeadHtml + viewAllHTML + footerHTML + ); + + // determine if we should omit this patterntype completely from the viewall page + const omitPatternType = + styleGuideExcludes && + styleGuideExcludes.length && + _.some(styleGuideExcludes, function(exclude) { + return exclude === patternType; + }); + if (omitPatternType) { logger.debug( - `skipping ${patternType} as flat patterns do not have view all pages` + `Omitting ${patternType} from building a viewall page because its patternGroup is specified in styleguideExcludes.` ); - return Promise.resolve(); + } else { + if (patternlab.config.renderFlatPatternsOnViewAllPages) { + patterns = sortedFlatPatterns; + patterns = patterns.concat(styleguideTypePatterns); + } else { + patterns = styleguideTypePatterns; + } } - - //render the viewall template for the type - return buildViewAllHTML( - patternlab, - typePatterns, - patternType, - uikit - ) - .then(viewAllHTML => { - fs.outputFileSync( - path.join( - process.cwd(), - uikit.outputDir, - paths.public.patterns + - anyPatternOfType.patternType + - '/index.html' - ), - mainPageHeadHtml + viewAllHTML + footerHTML - ); - - //determine if we should omit this patterntype completely from the viewall page - const omitPatternType = - styleGuideExcludes && - styleGuideExcludes.length && - _.some(styleGuideExcludes, function(exclude) { - return exclude === patternType; - }); - if (omitPatternType) { - logger.debug( - `Omitting ${patternType} from building a viewall page because its patternGroup is specified in styleguideExcludes.` - ); - } else { - patterns = patterns.concat(styleguideTypePatterns); - } - return Promise.resolve(patterns); - }) - .catch(reason => { - console.log(reason); - logger.error('Error building ViewAllHTML'); - }); + return Promise.resolve(patterns); }) .catch(reason => { console.log(reason); - logger.error('Error building footerHTML'); + logger.error('Error building ViewAllHTML'); }); }) .catch(reason => { console.log(reason); - logger.error('Error building footer HTML'); + logger.error('Error building footerHTML'); }); - } - ); - - return Promise.all(subTypePromises).catch(reason => { - console.log(reason); - logger.error('Error during buildViewAllPages'); - }); + }) + .catch(reason => { + console.log(reason); + logger.error('Error during buildViewAllPages'); + }); } ); - return Promise.all(allPatternTypePromises).catch(reason => { - console.log(reason); - logger.error('Error during buildViewAllPages'); - }); + return Promise.all(allPatternTypePromises) + .then(allPatterns => + Promise.resolve(_.filter(allPatterns, p => p.length)) + ) + .catch(reason => { + console.log(reason); + logger.error('Error during buildViewAllPages'); + }); } /** * Reset any global data we use between builds to guard against double adding things + * + * @param {Patternlab} patternlab Actual patternlab instance */ function resetUIBuilderState(patternlab) { patternlab.patternPaths = {}; @@ -729,8 +730,32 @@ const ui_builder = function() { patternlab.patternTypes = []; } + /** + * Uniques all generated patterns and groups, also adds a group document pattern before + * each group. Used for generating view all page and all its pattern. + * + * @param {[Pattern[]]} allPatterns All generated patterns + * @param {Patternlab} patternlab Actual patternlab instance + */ + function uniqueAllPatterns(allPatterns, patternlab) { + return _.uniq( + _.flatMapDeep( + _.map(allPatterns, patterns => [ + injectDocumentationBlock( + _.find(patterns, p => !p.patternPartial.startsWith('viewall-')), + patternlab, + false + ), + ...patterns, + ]), + pattern => pattern + ) + ); + } + /** * The main entry point for ui_builder + * * @param patternlab - global data store * @returns {Promise} a promise fulfilled when build is complete */ @@ -740,13 +765,13 @@ const ui_builder = function() { const paths = patternlab.config.paths; const uikitPromises = _.map(patternlab.uikits, uikit => { - //determine which patterns should be included in the front-end rendering + // determine which patterns should be included in the front-end rendering const styleguidePatterns = groupPatterns(patternlab, uikit); return new Promise(resolve => { - //set the pattern-specific header by compiling the general-header with data, and then adding it to the meta header + // set the pattern-specific header by compiling the general-header with data, and then adding it to the meta header const headerPromise = render( - Pattern.createEmpty({ extendedTemplate: uikit.header }), + Pattern.createEmpty({ extendedTemplate: uikit.header }, patternlab), { cacheBuster: patternlab.cacheBuster, } @@ -762,9 +787,9 @@ const ui_builder = function() { logger.error('error during header render()'); }); - //set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer + // set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer const footerPromise = render( - Pattern.createEmpty({ extendedTemplate: uikit.footer }), + Pattern.createEmpty({ extendedTemplate: uikit.footer }, patternlab), { patternData: '{}', cacheBuster: patternlab.cacheBuster, @@ -782,7 +807,7 @@ const ui_builder = function() { return Promise.all([headerPromise, footerPromise]).then( headFootPromiseResults => { - //build the viewall pages + // build the viewall pages return buildViewAllPages( headFootPromiseResults[0], @@ -791,24 +816,26 @@ const ui_builder = function() { uikit ) .then(allPatterns => { - //todo track down why we need to make this unique in the first place - const uniquePatterns = _.uniq( - _.flatMapDeep(allPatterns, pattern => { - return pattern; - }) + // todo track down why we need to make this unique in the first place + const uniquePatterns = uniqueAllPatterns( + allPatterns, + patternlab ); - //add the defaultPattern if we found one + // add the defaultPattern if we found one if (patternlab.defaultPattern) { uniquePatterns.push(patternlab.defaultPattern); addToPatternPaths(patternlab, patternlab.defaultPattern); } - //build the main styleguide page + // build the main styleguide page return render( - Pattern.createEmpty({ - extendedTemplate: uikit.viewAll, - }), + Pattern.createEmpty( + { + extendedTemplate: uikit.viewAll, + }, + patternlab + ), { partials: uniquePatterns, }, @@ -834,7 +861,7 @@ const ui_builder = function() { logger.info('Built Pattern Lab front end'); - //move the index file from its asset location into public root + // move the index file from its asset location into public root let patternlabSiteHtml; try { patternlabSiteHtml = fs.readFileSync( @@ -864,7 +891,7 @@ const ui_builder = function() { patternlabSiteHtml ); - //write out patternlab.data object to be read by the client + // write out patternlab.data object to be read by the client exportData(patternlab); resolve(); }) @@ -885,31 +912,12 @@ const ui_builder = function() { } return { - buildFrontend: function(patternlab) { - return buildFrontend(patternlab); - }, - isPatternExcluded: function(pattern, patternlab, uikit) { - return isPatternExcluded(pattern, patternlab, uikit); - }, - groupPatterns: function(patternlab, uikit) { - return groupPatterns(patternlab, uikit); - }, - resetUIBuilderState: function(patternlab) { - resetUIBuilderState(patternlab); - }, - buildViewAllPages: function( - mainPageHeadHtml, - patternlab, - styleguidePatterns, - uikit - ) { - return buildViewAllPages( - mainPageHeadHtml, - patternlab, - styleguidePatterns, - uikit - ); - }, + buildFrontend: buildFrontend, + isPatternExcluded: isPatternExcluded, + groupPatterns: groupPatterns, + resetUIBuilderState: resetUIBuilderState, + uniqueAllPatterns: uniqueAllPatterns, + buildViewAllPages: buildViewAllPages, }; }; diff --git a/packages/core/test/files/_patterns/00-test/nav.json b/packages/core/test/files/_patterns/00-test/nav.json index 32a6c0952..8d47a2491 100644 --- a/packages/core/test/files/_patterns/00-test/nav.json +++ b/packages/core/test/files/_patterns/00-test/nav.json @@ -1,11 +1,29 @@ { - "brad" : { - "url" : "link.twitter-brad" + "brad": { + "url": "link.twitter-brad" }, - "dave" : { - "url" : "link.twitter-dave" + "dave": { + "url": "link.twitter-dave" }, - "brian" : { - "url" : "link.twitter-brian" + "brian": { + "url": "link.twitter-brian" + }, + "someone": { + "url": "link.twitter-someone" + }, + "someone2": { + "url": "link.facebook-someone2" + }, + "viewall-twitter": { + "url": "link.viewall-twitter-all" + }, + "viewall-twitter-people": { + "url": "link.viewall-twitter-people" + }, + "viewall-facebook": { + "url": "link.viewall-facebook-all" + }, + "viewall-facebook-people": { + "url": "link.viewall-facebook-people" } -} +} \ No newline at end of file diff --git a/packages/core/test/object_factory_tests.js b/packages/core/test/object_factory_tests.js index 910c02d89..b561e8fa9 100644 --- a/packages/core/test/object_factory_tests.js +++ b/packages/core/test/object_factory_tests.js @@ -79,8 +79,8 @@ tap.test( path.sep + 'colors.mustache' ); - test.equals(p.name, '00-atoms-00-global'); - test.equals(p.subdir, '00-atoms' + path.sep + '00-global'); + test.equals(p.name, '00-atoms-00-global-00-colors'); + test.equals(p.subdir, path.join('00-atoms', '00-global', '00-colors')); test.equals(p.fileName, 'colors'); test.equals(p.fileExtension, '.mustache'); test.equals(p.jsonFileData.d, 123); @@ -88,10 +88,12 @@ tap.test( test.equals(p.patternName, 'Colors'); test.equals( p.getPatternLink(pl), - '00-atoms-00-global' + path.sep + '00-atoms-00-global.rendered.html' + '00-atoms-00-global-00-colors' + + path.sep + + '00-atoms-00-global-00-colors.rendered.html' ); test.equals(p.patternGroup, 'atoms'); - test.equals(p.patternSubGroup, 'global'); //because of p.info.hasDir + test.equals(p.patternSubGroup, 'global'); test.equals(p.flatPatternPath, '00-atoms-00-global'); test.equals(p.patternPartial, 'atoms-colors'); test.equals(p.template, ''); @@ -106,14 +108,17 @@ tap.test( ); tap.test('test Pattern name for variants correctly initialzed', function(test) { - var p1 = new Pattern('00-atoms/00-global/colors~variant.mustache', { - d: 123, - }); - var p2 = new Pattern('00-atoms/00-global/colors~variant-minus.json', { + var p1 = new Pattern('00-atoms/00-global/00-colors/colors~variant.mustache', { d: 123, }); - test.equals(p1.name, '00-atoms-00-global-colors-variant'); - test.equals(p2.name, '00-atoms-00-global-colors-variant-minus'); + var p2 = new Pattern( + '00-atoms/00-global/00-colors/colors~variant-minus.json', + { + d: 123, + } + ); + test.equals(p1.name, '00-atoms-00-global-00-colors-variant'); + test.equals(p2.name, '00-atoms-00-global-00-colors-variant-minus'); test.end(); }); @@ -144,6 +149,38 @@ tap.test('test Pattern with one-directory subdir works as expected', function( test.end(); }); +tap.test('test Pattern with own-directory gets resetted as expected', function( + test +) { + var p = new Pattern('00-atoms/00-button/button.mustache', { d: 123 }, pl); + p.promoteFromFlatPatternToDirectory(pl); + + test.equals(p.relPath, path.join('00-atoms', '00-button', 'button.mustache')); + test.equals(p.name, '00-atoms-00-button-button'); + test.equals(p.subdir, path.join('00-atoms', '00-button')); + test.equals(p.fileName, 'button'); + test.equals(p.fileExtension, '.mustache'); + test.equals(p.jsonFileData.d, 123); + test.equals(p.patternBaseName, 'button'); + test.equals(p.patternName, 'Button'); + test.equals( + p.getPatternLink(pl), + path.join( + '00-atoms-00-button-button', + '00-atoms-00-button-button.rendered.html' + ) + ); + test.equals(p.patternGroup, 'atoms'); + test.equals(p.flatPatternPath, '00-atoms-00-button'); + test.equals(p.patternPartial, 'atoms-button'); + test.equals(p.template, ''); + test.equals(p.lineage.length, 0); + test.equals(p.lineageIndex.length, 0); + test.equals(p.lineageR.length, 0); + test.equals(p.lineageRIndex.length, 0); + test.end(); +}); + tap.test( 'test Pattern with no numbers in pattern group works as expected', function(test) { @@ -177,17 +214,17 @@ tap.test('test Pattern get dir level no separated pattern directory', function( ) { var p = new Pattern('00-atoms/00-global/00-colors-alt.mustache', { d: 123 }); console.log(p); - test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 2 }), '00-atoms'); - test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 2 }), '00-global'); - test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 2 }), ''); + test.equals(p.getDirLevel(0, { patternHasOwnDir: false }), '00-atoms'); + test.equals(p.getDirLevel(1, { patternHasOwnDir: false }), '00-global'); + test.equals(p.getDirLevel(2, { patternHasOwnDir: false }), ''); // There is no third level var p = new Pattern('00-atoms/00-colors-alt.mustache', { d: 123 }); - test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 1 }), '00-atoms'); - test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 1 }), '00-atoms'); - test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 1 }), ''); + test.equals(p.getDirLevel(0, { patternHasOwnDir: false }), '00-atoms'); + test.equals(p.getDirLevel(1, { patternHasOwnDir: false }), ''); // There is no second level + test.equals(p.getDirLevel(2, { patternHasOwnDir: false }), ''); // There is no third level var p = new Pattern('00-colors-alt.mustache', { d: 123 }); - test.equals(p.getDirLevel(0, { hasDir: false, dirLevel: 0 }), ''); - test.equals(p.getDirLevel(1, { hasDir: false, dirLevel: 0 }), ''); - test.equals(p.getDirLevel(3, { hasDir: false, dirLevel: 0 }), ''); + test.equals(p.getDirLevel(0, { patternHasOwnDir: false }), 'root'); // No first level means root + test.equals(p.getDirLevel(1, { patternHasOwnDir: false }), ''); // There is no second level + test.equals(p.getDirLevel(2, { patternHasOwnDir: false }), ''); // There is no third leveL test.end(); }); @@ -198,32 +235,79 @@ tap.test( '00-atoms/00-global/00-colors-alt/colors-alt.mustache', { d: 123 } ); - test.equals(p.getDirLevel(0, { hasDir: true, dirLevel: 3 }), '00-atoms'); - test.equals(p.getDirLevel(1, { hasDir: true, dirLevel: 3 }), '00-global'); - test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 3 }), ''); + test.equals(p.getDirLevel(0, { patternHasOwnDir: true }), '00-atoms'); + test.equals(p.getDirLevel(1, { patternHasOwnDir: true }), '00-global'); + test.equals(p.getDirLevel(2, { patternHasOwnDir: true }), ''); // There is no third level + var p = new Pattern('00-atoms/00-colors-alt/colors-alt.mustache', { d: 123, }); - test.equals(p.getDirLevel(0, { hasDir: true, dirLevel: 2 }), '00-atoms'); - test.equals( - p.getDirLevel(1, { hasDir: true, dirLevel: 2 }), - '00-colors-alt' - ); - test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 2 }), ''); + test.equals(p.getDirLevel(0, { patternHasOwnDir: true }), '00-atoms'); + test.equals(p.getDirLevel(1, { patternHasOwnDir: true }), ''); // There is no second level + test.equals(p.getDirLevel(2, { patternHasOwnDir: true }), ''); // There is no third level + var p = new Pattern('00-colors-alt/colors-alt.mustache', { d: 123 }); - test.equals( - p.getDirLevel(0, { hasDir: true, dirLevel: 1 }), - '00-colors-alt' - ); - test.equals( - p.getDirLevel(1, { hasDir: true, dirLevel: 1 }), - '00-colors-alt' + test.equals(p.getDirLevel(0, { patternHasOwnDir: true }), 'root'); // No first level means root + test.equals(p.getDirLevel(1, { patternHasOwnDir: true }), ''); // There is no second level + test.equals(p.getDirLevel(2, { patternHasOwnDir: true }), ''); // There is no third leveL + + var p = new Pattern( + '00-atoms/00-global/00-colors-alt/colors-alt~variant.mustache', + { d: 123 } ); - test.equals(p.getDirLevel(3, { hasDir: true, dirLevel: 1 }), ''); + test.equals(p.name, '00-atoms-00-global-00-colors-alt-variant'); + test.equals(p.flatPatternPath, '00-atoms-00-global'); + test.equals(p.patternBaseName, 'colors-alt-variant'); + test.end(); } ); +tap.test('test Patterns that are nested deeper without own directory', function( + test +) { + var p = new Pattern( + '00-atoms/00-global/00-random-folder/00-colors-alt.mustache', + { + d: 123, + } + ); + test.equals(p.name, '00-atoms-00-global-00-colors-alt'); + test.equals(p.flatPatternPath, '00-atoms-00-global'); + + var p = new Pattern( + '00-atoms/00-global/00-random-folder/00-another-folder/00-colors-alt.mustache', + { + d: 123, + } + ); + test.equals(p.name, '00-atoms-00-global-00-colors-alt'); + test.equals(p.flatPatternPath, '00-atoms-00-global'); + + var p = new Pattern( + '00-atoms/00-global/00-random-folder/00-another-folder/00-some-folder/00-colors-alt.mustache', + { d: 123 } + ); + test.equals(p.name, '00-atoms-00-global-00-colors-alt'); + test.equals(p.flatPatternPath, '00-atoms-00-global'); + + var p = new Pattern( + '00-atoms/00-global/00-random-folder/00-another-folder/00-colors-alt/colors-alt.mustache', + { d: 123 } + ); + test.equals(p.name, '00-atoms-00-global-00-colors-alt'); + test.equals(p.flatPatternPath, '00-atoms-00-global'); + + var p = new Pattern( + '00-atoms/00-global/00-random-folder/00-another-folder/00-some-folder/00-colors-alt~variant.mustache', + { d: 123 } + ); + test.equals(p.name, '00-atoms-00-global-00-colors-alt-variant'); + test.equals(p.flatPatternPath, '00-atoms-00-global'); + test.equals(p.patternBaseName, 'colors-alt-variant'); + test.end(); +}); + tap.test('The forms of Pattern.getPatternLink() work as expected', function( test ) { diff --git a/packages/core/test/parseAllLinks_tests.js b/packages/core/test/parseAllLinks_tests.js index 271b3ee5b..1d52c4276 100644 --- a/packages/core/test/parseAllLinks_tests.js +++ b/packages/core/test/parseAllLinks_tests.js @@ -22,15 +22,21 @@ tap.test( const patternlab = util.fakePatternLab(patterns_dir); patternlab.graph = PatternGraph.empty(); - patternlab.patterns = [ - Pattern.createEmpty({ patternPartial: 'twitter-brad' }, patternlab), - Pattern.createEmpty({ patternPartial: 'twitter-dave' }, patternlab), - Pattern.createEmpty({ patternPartial: 'twitter-brian' }, patternlab), - ]; - patternlab.data.link = {}; + addPattern(new Pattern('twitter/brad.hbs', {}, patternlab), patternlab); + addPattern(new Pattern('twitter/dave.hbs', {}, patternlab), patternlab); + addPattern(new Pattern('twitter/brian.hbs', {}, patternlab), patternlab); + addPattern( + new Pattern('twitter/people/someone.hbs', {}, patternlab), + patternlab + ); + // Test with pattern prefix + addPattern( + new Pattern('01-facebook/02-people/03-someone2.hbs', {}, patternlab), + patternlab + ); // copies essential logic from loadPattern - const navPattern = new Pattern('00-test/nav.mustache'); + const navPattern = new Pattern('00-test/nav.mustache', {}, patternlab); const patternData = dataLoader.loadDataFromFile( path.resolve( __dirname, @@ -43,21 +49,15 @@ tap.test( navPattern.jsonFileData = patternData; addPattern(navPattern, patternlab); - //for the sake of the test, also imagining I have the following pages... - patternlab.data.link['twitter-brad'] = 'https://twitter.com/brad_frost'; - patternlab.data.link['twitter-dave'] = 'https://twitter.com/dmolsen'; - patternlab.data.link['twitter-brian'] = 'https://twitter.com/bmuenzenmeyer'; - patternlab.data.brad = { url: 'link.twitter-brad' }; patternlab.data.dave = { url: 'link.twitter-dave' }; patternlab.data.brian = { url: 'link.twitter-brian' }; + patternlab.data.someone = { url: 'link.twitter-someone' }; + patternlab.data.someone2 = { url: 'link.facebook-someone2' }; - let pattern; - for (let i = 0; i < patternlab.patterns.length; i++) { - if (patternlab.patterns[i].patternPartial === 'test-nav') { - pattern = patternlab.patterns[i]; - } - } + let pattern = patternlab.patterns.find( + p => p.patternPartial === 'test-nav' + ); //assert before test.equals( @@ -75,6 +75,36 @@ tap.test( 'link.twitter-brian', 'brian pattern data should be found' ); + test.equals( + pattern.jsonFileData.someone.url, + 'link.twitter-someone', + 'brian pattern data should be found' + ); + test.equals( + pattern.jsonFileData.someone2.url, + 'link.facebook-someone2', + 'brian pattern data should be found' + ); + test.equals( + pattern.jsonFileData['viewall-twitter'].url, + 'link.viewall-twitter-all', + 'view all twitter link should be found' + ); + test.equals( + pattern.jsonFileData['viewall-twitter-people'].url, + 'link.viewall-twitter-people', + 'view all twitter people link should be found' + ); + test.equals( + pattern.jsonFileData['viewall-facebook'].url, + 'link.viewall-facebook-all', + 'view all facebook link should be found' + ); + test.equals( + pattern.jsonFileData['viewall-facebook-people'].url, + 'link.viewall-facebook-people', + 'view all facebook people link should be found' + ); //act parseAllLinks(patternlab); @@ -82,35 +112,75 @@ tap.test( //assert after test.equals( pattern.jsonFileData.brad.url, - 'https://twitter.com/brad_frost', + '/patterns/twitter-brad/twitter-brad.rendered.html', 'brad pattern data should be replaced' ); test.equals( pattern.jsonFileData.dave.url, - 'https://twitter.com/dmolsen', + '/patterns/twitter-dave/twitter-dave.rendered.html', 'dave pattern data should be replaced' ); test.equals( pattern.jsonFileData.brian.url, - 'https://twitter.com/bmuenzenmeyer', + '/patterns/twitter-brian/twitter-brian.rendered.html', 'brian pattern data should be replaced' ); + test.equals( + pattern.jsonFileData.someone.url, + '/patterns/twitter-people-someone/twitter-people-someone.rendered.html', + 'twitter people someone pattern data should be replaced' + ); + test.equals( + pattern.jsonFileData.someone2.url, + '/patterns/01-facebook-02-people-03-someone2/01-facebook-02-people-03-someone2.rendered.html', + 'facebook people someone2 pattern data should be replaced with prefix pattern' + ); + test.equals( + pattern.jsonFileData['viewall-twitter'].url, + '/patterns/twitter/index.html', + 'view all twitter link should be replaced' + ); + test.equals( + pattern.jsonFileData['viewall-twitter-people'].url, + '/patterns/twitter-people/index.html', + 'view all twitter people link should be replaced' + ); + test.equals( + pattern.jsonFileData['viewall-facebook'].url, + '/patterns/facebook/index.html', + 'view all facebook link should be replaced' + ); + test.equals( + pattern.jsonFileData['viewall-facebook-people'].url, + '/patterns/01-facebook-02-people/index.html', + 'view all facebook people link should be replaced' + ); test.equals( patternlab.data.brad.url, - 'https://twitter.com/brad_frost', + '/patterns/twitter-brad/twitter-brad.rendered.html', 'global brad data should be replaced' ); test.equals( patternlab.data.dave.url, - 'https://twitter.com/dmolsen', + '/patterns/twitter-dave/twitter-dave.rendered.html', 'global dave data should be replaced' ); test.equals( patternlab.data.brian.url, - 'https://twitter.com/bmuenzenmeyer', + '/patterns/twitter-brian/twitter-brian.rendered.html', 'global brian data should be replaced' ); + test.equals( + patternlab.data.someone.url, + '/patterns/twitter-people-someone/twitter-people-someone.rendered.html', + 'twitter people someone pattern data should be replaced' + ); + test.equals( + patternlab.data.someone2.url, + '/patterns/01-facebook-02-people-03-someone2/01-facebook-02-people-03-someone2.rendered.html', + 'facebook people someone2 pattern data should be replaced with prefix pattern' + ); test.end(); } ); diff --git a/packages/core/test/ui_builder_tests.js b/packages/core/test/ui_builder_tests.js index 607d9fd95..00fd29ec0 100644 --- a/packages/core/test/ui_builder_tests.js +++ b/packages/core/test/ui_builder_tests.js @@ -183,6 +183,7 @@ tap.test('groupPatterns - creates pattern groups correctly', function(test) { }); patternlab.patterns.push( + new Pattern('foobar.mustache'), new Pattern('00-test/bar.mustache'), new Pattern('00-test/foo.mustache'), new Pattern('patternType1/patternSubType1/blue.mustache'), @@ -222,13 +223,19 @@ tap.test('groupPatterns - creates pattern groups correctly', function(test) { 'patternType1-white' ); + // Flat patterns test.equals( patternlab.patternTypes[0].patternItems[0].patternPartial, + 'root-foobar', + 'flat pattern foobar on root' + ); + test.equals( + patternlab.patternTypes[1].patternItems[0].patternPartial, 'test-bar', 'first pattern item should be test-bar' ); test.equals( - patternlab.patternTypes[0].patternItems[1].patternPartial, + patternlab.patternTypes[1].patternItems[1].patternPartial, 'test-foo', 'second pattern item should be test-foo' ); @@ -248,13 +255,15 @@ tap.test('groupPatterns - orders patterns when provided from md', function( subtypePatterns: {}, }); + // Should be sorted by order and secondly by name patternlab.patterns.push( - new Pattern('patternType1/patternSubType1/blue.mustache'), + new Pattern('patternType1/patternSubType1/yellow.mustache'), new Pattern('patternType1/patternSubType1/red.mustache'), - new Pattern('patternType1/patternSubType1/yellow.mustache') + new Pattern('patternType1/patternSubType1/blue.mustache') ); ui.resetUIBuilderState(patternlab); + // Set order of red to 1 to sort it after the others patternlab.patterns[1].order = 1; //act @@ -270,10 +279,10 @@ tap.test('groupPatterns - orders patterns when provided from md', function( ]); var items = patternSubType.patternSubtypeItems; - //zero is viewall - test.equals(items[1].patternPartial, 'patternType1-red'); - test.equals(items[2].patternPartial, 'patternType1-blue'); - test.equals(items[3].patternPartial, 'patternType1-yellow'); + // Viewall should come last since it shows all patterns that are above + test.equals(items[0].patternPartial, 'patternType1-blue'); + test.equals(items[1].patternPartial, 'patternType1-yellow'); + test.equals(items[2].patternPartial, 'patternType1-red'); test.end(); }); @@ -310,10 +319,10 @@ tap.test( ]); var items = patternSubType.patternSubtypeItems; - //zero is viewall - test.equals(items[1].patternPartial, 'patternType1-blue'); - test.equals(items[2].patternPartial, 'patternType1-red'); - test.equals(items[3].patternPartial, 'patternType1-yellow'); + // Viewall should come last since it shows all patterns that are above + test.equals(items[0].patternPartial, 'patternType1-blue'); + test.equals(items[1].patternPartial, 'patternType1-red'); + test.equals(items[2].patternPartial, 'patternType1-yellow'); test.end(); } @@ -353,14 +362,14 @@ tap.test( ]); var items = patternSubType.patternSubtypeItems; - //zero is viewall + // Viewall should come last since it shows all patterns that are above test.equals( - items[0].patternPartial, + items[3].patternPartial, 'viewall-patternType1-patternSubType1' ); - test.equals(items[1].patternPartial, 'patternType1-blue'); - test.equals(items[2].patternPartial, 'patternType1-yellow'); - test.equals(items[3].patternPartial, 'patternType1-red'); + test.equals(items[0].patternPartial, 'patternType1-blue'); + test.equals(items[1].patternPartial, 'patternType1-yellow'); + test.equals(items[2].patternPartial, 'patternType1-red'); test.end(); } @@ -518,7 +527,75 @@ tap.test('resetUIBuilderState - reset global objects', function(test) { }); tap.test( - 'buildViewAllPages - adds viewall page for each type and subtype', + 'buildViewAllPages - adds viewall page for each type and subtype NOT! for flat patterns', + function(test) { + //arrange + const mainPageHeadHtml = ''; + const patternlab = createFakePatternLab({ + patterns: [], + patternGroups: {}, + subtypePatterns: {}, + footer: {}, + userFoot: {}, + cacheBuster: 1234, + }); + + patternlab.patterns.push( + //this flat pattern is found and causes trouble for the rest of the crew + new Pattern('00-test/foo.mustache'), + new Pattern('patternType1/patternSubType1/blue.mustache'), + new Pattern('patternType1/patternSubType1/red.mustache'), + new Pattern('patternType1/patternSubType1/yellow.mustache'), + new Pattern('patternType1/patternSubType2/black.mustache'), + new Pattern('patternType1/patternSubType2/grey.mustache'), + new Pattern('patternType1/patternSubType2/white.mustache') + ); + ui.resetUIBuilderState(patternlab); + + const styleguidePatterns = ui.groupPatterns(patternlab, uikit); + + //act + ui.buildViewAllPages( + mainPageHeadHtml, + patternlab, + styleguidePatterns, + uikit + ).then(allPatterns => { + // assert + // this was a nuanced one. buildViewAllPages() had return false; statements + // within _.forOwn(...) loops, causing premature termination of the entire loop + // when what was intended was a continue + // we expect 10 here because: + // - foo.mustache is flat and therefore does not have a viewall page + // - the colors.mustache files make 6 + // - patternSubType1 and patternSubType2 make 8 + // - the general view all page make 9 + // while most of that heavy lifting occurs inside groupPatterns and not buildViewAllPages, + // it's important to ensure that this method does not get prematurely terminated + // we choose to do that by checking it's return number of patterns + + const uniquePatterns = ui.uniqueAllPatterns(allPatterns, patternlab); + + /** + * - view-patternType1-all + * -- viewall-patternType1-patternSubType1 + * --- blue + * --- red + * --- yellow + * -- viewall-patternType1-patternSubType2 + * --- black + * --- grey + * --- white + */ + test.equals(uniquePatterns.length, 9, '3 viewall pages should be added'); + + test.end(); + }); + } +); + +tap.test( + 'buildViewAllPages - adds viewall page for each type and subtype FOR! flat patterns', function(test) { //arrange const mainPageHeadHtml = ''; @@ -531,6 +608,8 @@ tap.test( cacheBuster: 1234, }); + patternlab.config.renderFlatPatternsOnViewAllPages = true; + patternlab.patterns.push( //this flat pattern is found and causes trouble for the rest of the crew new Pattern('00-test/foo.mustache'), @@ -546,40 +625,44 @@ tap.test( const styleguidePatterns = ui.groupPatterns(patternlab, uikit); //act - ui - .buildViewAllPages( - mainPageHeadHtml, - patternlab, - styleguidePatterns, - uikit - ) - .then(allPatterns => { - //assert - //this was a nuanced one. buildViewAllPages() had return false; statements - //within _.forOwn(...) loops, causing premature termination of the entire loop - //when what was intended was a continue - //we expect 8 here because: - // - foo.mustache is flat and therefore does not have a viewall page - // - the colors.mustache files make 6 - // - patternSubType1 and patternSubType2 make 8 - //while most of that heavy lifting occurs inside groupPatterns and not buildViewAllPages, - //it's important to ensure that this method does not get prematurely terminated - //we choose to do that by checking it's return number of patterns - - //todo: this workaround matches the code at the moment - const uniquePatterns = _.uniq( - _.flatMapDeep(allPatterns, pattern => { - return pattern; - }) - ); - - test.equals( - uniquePatterns.length, - 8, - '2 viewall pages should be added' - ); - - test.end(); - }); + ui.buildViewAllPages( + mainPageHeadHtml, + patternlab, + styleguidePatterns, + uikit + ).then(allPatterns => { + // assert + // this was a nuanced one. buildViewAllPages() had return false; statements + // within _.forOwn(...) loops, causing premature termination of the entire loop + // when what was intended was a continue + // we expect 8 here because: + // - foo.mustache is flat and therefore does not have a viewall page + // - the colors.mustache files make 6 + // - patternSubType1 and patternSubType2 make 8 + // - the general view all page make 9 + // - the view-all page of test and test-foo make 11 + // while most of that heavy lifting occurs inside groupPatterns and not buildViewAllPages, + // it's important to ensure that this method does not get prematurely terminated + // we choose to do that by checking it's return number of patterns + + const uniquePatterns = ui.uniqueAllPatterns(allPatterns, patternlab); + + /** + * - viewall-test-all + * -- test-foo + * - view-patternType1-all + * -- viewall-patternType1-patternSubType1 + * --- blue + * --- red + * --- yellow + * -- viewall-patternType1-patternSubType2 + * --- black + * --- grey + * --- white + */ + test.equals(uniquePatterns.length, 11, '4 viewall pages should be added'); + + test.end(); + }); } ); diff --git a/packages/core/test/util/patternlab-config.json b/packages/core/test/util/patternlab-config.json index 904e37afd..f4f1396c5 100644 --- a/packages/core/test/util/patternlab-config.json +++ b/packages/core/test/util/patternlab-config.json @@ -58,6 +58,7 @@ "patternExportDirectory": "./pattern_exports/", "patternExtension": "mustache", "patternMergeVariantArrays": true, + "renderFlatPatternsOnViewAllPages": false, "cacheBust": true, "outputFileSuffixes": { "rendered": ".rendered", diff --git a/packages/development-edition-engine-handlebars/patternlab-config.json b/packages/development-edition-engine-handlebars/patternlab-config.json index 141f35a47..0ba471dd2 100644 --- a/packages/development-edition-engine-handlebars/patternlab-config.json +++ b/packages/development-edition-engine-handlebars/patternlab-config.json @@ -70,7 +70,8 @@ "patternExportPatternPartials": [], "patternExportPreserveDirectoryStructure": true, "patternExportRaw": false, - "patternMergeVariantArrays": true, + "patternMergeVariantArrays": false, + "renderFlatPatternsOnViewAllPages": false, "serverOptions": { "wait": 1000 }, diff --git a/packages/development-edition-engine-react/patternlab-config.json b/packages/development-edition-engine-react/patternlab-config.json index dd4efbd24..106d0f983 100644 --- a/packages/development-edition-engine-react/patternlab-config.json +++ b/packages/development-edition-engine-react/patternlab-config.json @@ -76,6 +76,7 @@ "patternExportDirectory": "./pattern_exports/", "patternExportPatternPartials": [], "patternMergeVariantArrays": true, + "renderFlatPatternsOnViewAllPages": false, "serverOptions": { "wait": 1000 }, diff --git a/packages/development-edition-engine-twig/patternlab-config.json b/packages/development-edition-engine-twig/patternlab-config.json index 55fed5c95..77377bab0 100644 --- a/packages/development-edition-engine-twig/patternlab-config.json +++ b/packages/development-edition-engine-twig/patternlab-config.json @@ -81,6 +81,7 @@ "patternExportDirectory": "pattern_exports", "patternExportPatternPartials": [], "patternMergeVariantArrays": true, + "renderFlatPatternsOnViewAllPages": false, "serverOptions": { "wait": 1000 }, diff --git a/packages/docs/src/docs/advanced-config-options.md b/packages/docs/src/docs/advanced-config-options.md index bba2d18df..026393be6 100644 --- a/packages/docs/src/docs/advanced-config-options.md +++ b/packages/docs/src/docs/advanced-config-options.md @@ -199,6 +199,19 @@ Used to override the merge behavior of pattern variants. For more information se **default**: `true` | `undefined` +### renderFlatPatternsOnViewAllPages + +Used to activate rendering flat patterns on view all pages and generate view all pages if only flat patterns are available + +- `true` will render flat patterns on view all pages +- `false` will make flat patterns available only in the menu + +```javascript +"renderFlatPatternsOnViewAllPages": true, +``` + +**default**: `false` | `undefined` + ### serverOptions Sets live-server options. See the [live-server documentation](https://github.com/pattern-lab/live-server#usage-from-node) for more details. diff --git a/packages/docs/src/docs/pattern-organization.md b/packages/docs/src/docs/pattern-organization.md index ff3cab5ad..d7839c7dd 100644 --- a/packages/docs/src/docs/pattern-organization.md +++ b/packages/docs/src/docs/pattern-organization.md @@ -50,3 +50,12 @@ Node versions support nesting of folders under `patternSubtype`. For example, yo In this example the `media-block/` directory is ignored for the purposes of generating breadcrumbs and navigation in the Pattern Lab front-end but the documentation, pattern and pseudo-patterns are still rendered. Folders can be nested under `media-block/` if desired but this is discouraged because of possible collisions when using the [shorthand partial syntax](/docs/including-patterns/). + +### Deeper Nesting Settings + +As documented in [Documenting Patterns](/docs/documenting-patterns/) there are several options to handle the patterns state or add additional information. + +The `deeplyNested` attribute is used to toggle the pattern building behavior and toggles the deeper nesting. + +- **deeplyNested not set or false** - Pattern won't be handled as a deeply nested pattern +- **deeplyNested: true** - Pattern will be handled like mentioned under [Deeper Nesting](#heading-deeper-nesting) diff --git a/packages/edition-node-gulp/patternlab-config.json b/packages/edition-node-gulp/patternlab-config.json index e24473a02..02d705683 100644 --- a/packages/edition-node-gulp/patternlab-config.json +++ b/packages/edition-node-gulp/patternlab-config.json @@ -76,6 +76,7 @@ "patternExportPreserveDirectoryStructure": true, "patternExportRaw": false, "patternMergeVariantArrays": true, + "renderFlatPatternsOnViewAllPages": false, "serverOptions": { "wait": 1000 }, diff --git a/packages/edition-node/patternlab-config.json b/packages/edition-node/patternlab-config.json index 3112f5ac0..70fe58eca 100644 --- a/packages/edition-node/patternlab-config.json +++ b/packages/edition-node/patternlab-config.json @@ -76,6 +76,7 @@ "patternExportPreserveDirectoryStructure": true, "patternExportRaw": false, "patternMergeVariantArrays": true, + "renderFlatPatternsOnViewAllPages": false, "serverOptions": { "wait": 1000 }, diff --git a/packages/edition-twig/patternlab-config.json b/packages/edition-twig/patternlab-config.json index ee6acd691..3dddbfa29 100644 --- a/packages/edition-twig/patternlab-config.json +++ b/packages/edition-twig/patternlab-config.json @@ -140,6 +140,7 @@ "patternExportPreserveDirectoryStructure": true, "patternExportRaw": false, "patternMergeVariantArrays": true, + "renderFlatPatternsOnViewAllPages": false, "serverOptions": { "wait": 1000 }, diff --git a/packages/engine-mustache/lib/engine_mustache.js b/packages/engine-mustache/lib/engine_mustache.js index dc0838cfd..59aa04501 100644 --- a/packages/engine-mustache/lib/engine_mustache.js +++ b/packages/engine-mustache/lib/engine_mustache.js @@ -29,7 +29,7 @@ const utilMustache = require('./util_mustache'); // it does, so we're cool, right? let patternLabConfig = {}; -var engine_mustache = { +const engine_mustache = { engine: Mustache, engineName: 'mustache', engineFileExtension: '.mustache', @@ -69,7 +69,7 @@ var engine_mustache = { * @returns {array|null} An array if a match is found, null if not. */ patternMatcher: function patternMatcher(pattern, regex) { - var matches; + let matches; if (typeof pattern === 'string') { matches = pattern.match(regex); } else if ( @@ -110,43 +110,35 @@ var engine_mustache = { // find and return any {{> template-name }} within pattern findPartials: function findPartials(pattern) { - var matches = this.patternMatcher(pattern, this.findPartialsRE); - return matches; + return this.patternMatcher(pattern, this.findPartialsRE); }, findPartialsWithStyleModifiers: function(pattern) { - var matches = this.patternMatcher( - pattern, - this.findPartialsWithStyleModifiersRE - ); - return matches; + return this.patternMatcher(pattern, this.findPartialsWithStyleModifiersRE); }, // returns any patterns that match {{> value(foo:"bar") }} or {{> // value:mod(foo:"bar") }} within the pattern findPartialsWithPatternParameters: function(pattern) { - var matches = this.patternMatcher( + return this.patternMatcher( pattern, this.findPartialsWithPatternParametersRE ); - return matches; }, findListItems: function(pattern) { - var matches = this.patternMatcher(pattern, this.findListItemsRE); - return matches; + return this.patternMatcher(pattern, this.findListItemsRE); }, // given a pattern, and a partial string, tease out the "pattern key" and // return it. findPartial_new: function(partialString) { - var partial = partialString.replace(this.findPartialRE, '$1'); - return partial; + return partialString.replace(this.findPartialRE, '$1'); }, // GTP: the old implementation works better. We might not need // this.findPartialRE anymore if it works in all cases! findPartial: function(partialString) { //strip out the template cruft - var foundPatternPartial = partialString + let foundPatternPartial = partialString .replace('{{> ', '') .replace(' }}', '') .replace('{{>', '') diff --git a/packages/uikit-workshop/src/sass/scss/04-components/_pattern-category.scss b/packages/uikit-workshop/src/sass/scss/04-components/_pattern-category.scss index abe9d6142..fbca575bb 100644 --- a/packages/uikit-workshop/src/sass/scss/04-components/_pattern-category.scss +++ b/packages/uikit-workshop/src/sass/scss/04-components/_pattern-category.scss @@ -10,11 +10,15 @@ */ .pl-c-category { margin-top: 6rem; - font: $pl-font !important; + font-family: $pl-font !important; &:first-of-type { margin-top: 2rem; } + + & + & { + margin-top: 2rem; + } } /** @@ -25,6 +29,10 @@ color: $pl-color-gray-87 !important; margin: 0 0 0.2rem; text-transform: capitalize; + + &:hover { + color: $pl-color-gray-70 !important; + } } /** @@ -32,6 +40,7 @@ */ .pl-c-category__title-link { transition: color $pl-animate-quick ease-out; + color: inherit; } /** diff --git a/packages/uikit-workshop/src/scripts/components/pl-nav/pl-nav.js b/packages/uikit-workshop/src/scripts/components/pl-nav/pl-nav.js index 0f4026a29..d38cd959a 100644 --- a/packages/uikit-workshop/src/scripts/components/pl-nav/pl-nav.js +++ b/packages/uikit-workshop/src/scripts/components/pl-nav/pl-nav.js @@ -269,7 +269,8 @@ class Nav extends BaseComponent { return ( {patternSubtype.patternSubtypeItems} @@ -296,19 +297,20 @@ class Nav extends BaseComponent { {(window.ishControls === undefined || window.ishControls.ishControlsHide === undefined || (window.ishControls.ishControlsHide['views-all'] !== true && - window.ishControls.ishControlsHide.all !== true)) && ( - - this.handleClick(e, 'all')} - href="styleguide/html/styleguide.html" - class="pl-c-nav__link pl-c-nav__link--pattern" - data-patternpartial="all" - tabindex="0" - > - All - - - )} + window.ishControls.ishControlsHide.all !== true)) && + !this.noViewAll && ( + + this.handleClick(e, 'all')} + href="styleguide/html/styleguide.html" + class="pl-c-nav__link pl-c-nav__link--pattern" + data-patternpartial="all" + tabindex="0" + > + All + + + )} ); } diff --git a/packages/uikit-workshop/src/scripts/components/pl-nav/src/NavLink.js b/packages/uikit-workshop/src/scripts/components/pl-nav/src/NavLink.js index 80f3b26d9..6a55dabb3 100644 --- a/packages/uikit-workshop/src/scripts/components/pl-nav/src/NavLink.js +++ b/packages/uikit-workshop/src/scripts/components/pl-nav/src/NavLink.js @@ -7,7 +7,7 @@ export const NavLink = props => { href={`patterns/${props.item.patternPath}`} className={`pl-c-nav__link pl-c-nav__link--sublink ${ - props.item.patternName === 'View All' + props.item.isDocPattern ? 'pl-c-nav__link--overview' : 'pl-c-nav__link--subsublink' } @@ -15,8 +15,8 @@ export const NavLink = props => { onClick={e => props.elem.handleClick(e, props.item.patternPartial)} data-patternpartial={props.item.patternPartial} > - {props.item.patternName === 'View All' && props.category - ? `${props.category}` + {props.item.isDocPattern && props.category + ? `${props.categoryName}` : props.item.patternName} {props.item.patternState && ( diff --git a/packages/uikit-workshop/src/scripts/components/pl-nav/src/NavList.js b/packages/uikit-workshop/src/scripts/components/pl-nav/src/NavList.js index dcd11156f..6e664e07a 100644 --- a/packages/uikit-workshop/src/scripts/components/pl-nav/src/NavList.js +++ b/packages/uikit-workshop/src/scripts/components/pl-nav/src/NavList.js @@ -5,18 +5,17 @@ import { NavItem } from './NavItem'; import { NavButton } from './NavButton'; export const NavList = props => { - const { children, category, elem } = props; + const { children, category, categoryName, elem } = props; const reorderedChildren = []; const nonViewAllItems = elem.noViewAll - ? children.filter(item => item.patternName !== 'View All') + ? children.filter(item => !item.isDocPattern) : children.filter( - item => - item.patternName !== 'View All' && !item.patternName.includes(' Docs') + item => !item.isDocPattern && !item.patternName.includes(' Docs') ); const viewAllItems = elem.noViewAll ? [] - : children.filter(item => item.patternName === 'View All'); + : children.filter(item => item.isDocPattern); reorderedChildren.push(...viewAllItems, ...nonViewAllItems); @@ -27,6 +26,7 @@ export const NavList = props => {