diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..a5ec49122 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,9 @@ +**/node_modules/** +node_modules +**/vendor/** +vendor +**/test/** +**/dist/** + +// being refactored / removed +packages/uikit-workshop/src/scripts/components/styleguide.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..826a1702e --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,86 @@ +module.exports = { + root: true, + env: { + node: true, + builtin: true, + es6: true, + browser: true, + }, + parser: 'babel-eslint', + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module', + allowImportExportEverywhere: true, + ecmaFeatures: { + jsx: true, + experimentalDecorators: true, + }, + }, + globals: {}, + plugins: ['prettier'], + extends: ['eslint-config-prettier'].map(require.resolve), + rules: { + 'prettier/prettier': 'error', + 'block-scoped-var': 0, + camelcase: 0, + 'consistent-return': 2, + curly: [2, 'all'], + 'dot-notation': [1, { allowKeywords: true }], + eqeqeq: [2, 'allow-null'], + 'global-strict': [0, 'never'], + 'guard-for-in': 2, + 'key-spacing': 0, + 'new-cap': 0, + 'no-alert': 2, + 'no-bitwise': 2, + 'no-caller': 2, + 'no-cond-assign': [2, 'except-parens'], + 'no-debugger': 2, + 'no-dupe-args': 2, + 'no-dupe-keys': 2, + 'no-empty': 2, + 'no-eval': 2, + 'no-extend-native': 2, + 'no-extra-bind': 2, + 'no-extra-parens': 0, + 'no-func-assign': 2, + 'no-implied-eval': 2, + 'no-invalid-regexp': 2, + 'no-irregular-whitespace': 1, + 'no-iterator': 2, + 'no-loop-func': 2, + 'no-mixed-requires': 0, + 'no-multi-str': 2, + 'no-native-reassign': 2, + 'no-new': 2, + 'no-param-reassign': 1, + 'no-proto': 2, + 'no-redeclare': 0, + 'no-script-url': 2, + 'no-self-assign': 2, + 'no-self-compare': 2, + 'no-sequences': 2, + 'no-shadow': 2, + 'no-undef': 2, + 'no-underscore-dangle': 0, + 'no-unreachable': 1, + 'no-unused-vars': 1, + 'no-use-before-define': 1, + 'no-useless-call': 2, + 'no-useless-concat': 2, + 'no-var': 2, + 'no-with': 2, + quotes: [0, 'single'], + radix: 2, + strict: 0, + 'valid-typeof': 2, + 'vars-on-top': 0, + 'prefer-const': [ + 'error', + { + destructuring: 'any', + ignoreReadBeforeAssign: false, + }, + ], + }, +}; diff --git a/.eslintrc b/.eslintrc.json similarity index 67% rename from .eslintrc rename to .eslintrc.json index 1a6fc01c8..335572829 100644 --- a/.eslintrc +++ b/.eslintrc.json @@ -1,29 +1,28 @@ { + "root": true, "env": { "node": true, - "builtin": true + "builtin": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 2017, + "sourceType": "module" }, "globals": {}, + "extends": ["prettier"], + "plugins": ["prettier"], "rules": { + "prettier/prettier": "error", "block-scoped-var": 0, "camelcase": 0, - "comma-spacing": [1, {"before": false, "after": true}], "consistent-return": 2, "curly": [2, "all"], "dot-notation": [1, { "allowKeywords": true }], "eqeqeq": [2, "allow-null"], "global-strict": [0, "never"], "guard-for-in": 2, - "indent": [1, 2, {"SwitchCase": 1, "VariableDeclarator": 1}], - "lines-around-comment": [1, { - "beforeBlockComment": true, - "beforeLineComment": true, - "allowBlockStart": true, - "allowObjectStart": true, - "allowArrayStart": true - }], "key-spacing": 0, - "keyword-spacing": 1, "new-cap": 0, "no-alert": 2, "no-bitwise": 2, @@ -37,7 +36,6 @@ "no-extend-native": 2, "no-extra-bind": 2, "no-extra-parens": 0, - "no-extra-semi": 2, "no-func-assign": 2, "no-implied-eval": 2, "no-invalid-regexp": 2, @@ -46,7 +44,6 @@ "no-loop-func": 2, "no-mixed-requires": 0, "no-multi-str": 2, - "no-multi-spaces": 1, "no-native-reassign": 2, "no-new": 2, "no-param-reassign": 1, @@ -64,20 +61,19 @@ "no-use-before-define": 1, "no-useless-call": 2, "no-useless-concat": 2, + "no-var": 2, "no-with": 2, "quotes": [0, "single"], "radix": 2, - "semi": [1, "always"], "strict": 0, - "space-before-blocks": 1, - "space-before-function-paren": [1, { - "anonymous": "always", - "named": "never" - }], - "space-in-parens": [1, "never"], - "space-infix-ops": 1, "valid-typeof": 2, "vars-on-top": 0, - "wrap-iife": [2, "inside"] + "prefer-const": [ + "error", + { + "destructuring": "any", + "ignoreReadBeforeAssign": false + } + ] } } diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8598cddb2..477f273af 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,15 +1,71 @@ -# Contributing to Patternlab Node +# Contributing to Pattern Lab Node + If you'd like to contribute to Pattern Lab Node, please do so! There is always a lot of ground to cover and something for your wheelhouse. -No pull request is too small. Check out any [up for grabs issues](https://github.com/pattern-lab/patternlab-node/labels/up%20for%20grabs) as a good way to get your feet wet, or add some more unit tests. +No pull request is too small. Check out any [help wanted 🆘](https://github.com/pattern-lab/patternlab-node/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted+%3Asos%3A%22) or [good first issues 🎓](https://github.com/pattern-lab/patternlab-node/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue+%3Amortar_board%3A%22)as a good way to get your feet wet, or add some more unit tests. + +## Prerequisites + +To get started, you'll need Node installed. Managing Node with [nvm](https://github.com/creationix/nvm) is recommended. Once installed, you can target the version of Node we specify within the [`.nvmrc`](https://github.com/pattern-lab/patternlab-node/blob/master/.nvmrc) file. + +```sh +nvm install <> +nvm use <> +``` + +## Developing Locally + +The best way to make changes to the Pattern Lab Node core and test them is through an edition. + +* Fork this repository on Github. +* `npm install && npm run bootstrap` +* Create a new branch in your fork and push your changes in that fork. +* `cd packages/edition-node` +* Test your changes with the edition's api + +### Cold start testing + +To ensure that developers can bootstrap the repo from a fresh clone, do this in your working copy: + +```sh +git reset --hard && git clean -dfx && npm install && npm run bootstrap +``` + +This ensures that any changes you've made will still result in a clean and functional developer experience. **Note**: be sure you've committed any outstanding work before doing this -- it will blow away whatever's still outstanding, including anything staged but not commited. ## Guidelines -1. Please keep your pull requests concise and limited to **ONE** substantive change at a time. This makes reviewing and testing so much easier. -2. _ALWAYS_ submit pull requests against the [dev branch](https://github.com/pattern-lab/patternlab-node/tree/dev). If this does not occur, I will first, try to redirect you gently, second, port over your contribution manually if time allows, and/or third, close your pull request. If you have a major feature to stabilize over time, talk to @bmuenzenmeyer about making a dedicated `feature-branch` -3. If you can, add some unit tests using the existing patterns in the `./test` directory -##Coding style -Two files combine within the project to define and maintain our coding style. +* _ALWAYS_ submit pull requests against the [dev branch](https://github.com/pattern-lab/patternlab-node/tree/dev). If this does not occur, I will first, try to redirect you gently, second, attempt to redirect the target branch myself, thirdly, port over your contribution manually if time allows, and/or lastly, close your pull request. If you have a major feature to stabilize over time, talk to @bmuenzenmeyer via an issue about making a dedicated `feature-branch` +* Keep your pull requests concise and limited to **ONE** substantive change at a time. This makes reviewing and testing so much easier. +* Commits should reference the issue you are adressing. For any Pull Request that you send, use the template provided. +* Commits are best formatted using the [conventional commits pattern](https://conventionalcommits.org/). +* If you can, add some unit tests using the existing patterns in the `.packages/core/test` directory +* Large enhancements should begin with opening an issue. This will result in a more systematic way for us to review your contribution and determine if a [specifcation discussion](https://github.com/pattern-lab/the-spec/issues) needs to occur. +* Mention the issue number in commits, so anyone can see to which issue your changes belong to. For instance: + * `fix(get): Resolve patterns correctly` + * `feat(version): Add ability to ask for version statically` + +## Coding style + +Formatting is automated via [Prettier](https://prettier.io/), setup to run on precommit. We suggest [editor integration](https://prettier.io/docs/en/editors.html) for this and for eslint. Prettier is further configured within `.prettierrc`. Eslint validates syntax and usage that Prettier doesn't handle. Configuration for both is found within the `.eslintrc.json` file. + +The `.editorconfig` controls spaces / tabs within supported editors. Check out their [site](http://editorconfig.org/). + +## Tests + +Add unit and integration tests if you can. It's always nice if our code coverage improves bit by bit (literally!). We are using [Node Tap](http://www.node-tap.org/) as test framework and [Rewire](https://github.com/jhnns/rewire) for mocking things like file system access. + +## Branching Scheme + +![branching scheme](branching-scheme.png) Currently Pattern Lab has the following branches: + +* **master** contains the latext stable, released version +* **dev**: for development. _Target pull requests against this branch._ +* **feature-branches** for larger changes. Allows merging all changes into both `dev` easily. +* **long running branches** for changes that involve major changes to the code, architecture and take a lot of time (i.e. making Pattern Lab async) + +New features are typically cut off of `dev` branch. When `dev` is stable cut releases by merging `dev` to `master` and creating a release tag. + +# Gitter.im Chat -* The `.editorconfig` controls spaces / tabs within supported editors. Check out their [site](http://editorconfig.org/). -* The `.eslintrc` defines our javascript standards. Some editors will evaluate this real-time - otherwise it's run using `grunt|gulp build` +If you have any questions or you would like to help, feel free to ask on [our Gitter.im channel](https://gitter.im/pattern-lab/node) :smiley: diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..5805c3f03 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +patreon: patternlab diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index dd3871fc4..9e5ad2464 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,12 +1,11 @@ - -I am using Pattern Lab Node `vX.X.X` on `Windows | Mac | Linux`, with Node `vX.X.X`, using the `Gulp | Grunt ` Edition. + -##### Expected Behavior +I am using Pattern Lab Node `vX.X.X` on `Windows | Mac | Linux`, with Node `vX.X.X`, using a `Gulp | Grunt | Vanilla | Custom` Edition. +##### Expected Behavior ##### Actual Behavior - ##### Steps to Reproduce diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4ddf5e12d..0ca02b82c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,5 @@ -Addresses # +Closes # Summary of changes: diff --git a/.github/branching-scheme.png b/.github/branching-scheme.png new file mode 100644 index 000000000..9d7b7628a Binary files /dev/null and b/.github/branching-scheme.png differ diff --git a/.github/gitgraph/README.md b/.github/gitgraph/README.md new file mode 100644 index 000000000..02cc14ca4 --- /dev/null +++ b/.github/gitgraph/README.md @@ -0,0 +1,8 @@ +Generating a new graph +====================== + +This folder uses http://gitgraphjs.com/ for generating the git graph model. + +1. Change `patternlab-flow.js` to your needs according to the documentation on http://gitgraphjs.com/ +2. Open branching-scheme.html in browse, right click the graph and "Save as...". +3. Overwrite `/.github/branching-scheme.png` diff --git a/.github/gitgraph/branching-scheme.html b/.github/gitgraph/branching-scheme.html new file mode 100644 index 000000000..32d2ffb80 --- /dev/null +++ b/.github/gitgraph/branching-scheme.html @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/.github/gitgraph/patternlab-flow.js b/.github/gitgraph/patternlab-flow.js new file mode 100644 index 000000000..a68910cee --- /dev/null +++ b/.github/gitgraph/patternlab-flow.js @@ -0,0 +1,213 @@ +var graphConfig = new GitGraph.Template({ + colors: [ + '#9993FF', + '#47E8D4', + '#6BDB52', + '#F85BB5', + '#FFA657', + '#FFCCAA', + '#F85BB5', + ], + branch: { + lineWidth: 3, + spacingX: 60, + mergeStyle: 'straight', + showLabel: true, // display branch names on graph + labelFont: 'normal 10pt Arial', + labelRotation: 0, + color: 'black', + }, + commit: { + spacingY: -30, + dot: { + size: 8, + strokeColor: '#000000', + strokeWidth: 4, + }, + tag: { + font: 'normal 10pt Arial', + color: 'yellow', + }, + message: { + color: 'black', + font: 'normal 12pt Arial', + displayAuthor: false, + displayBranch: false, + displayHash: false, + }, + }, + arrow: { + size: 8, + offset: 3, + }, +}); + +var config = { + template: graphConfig, + mode: 'extended', + orientation: 'horizontal', +}; + +var bugFixCommit = { + messageAuthorDisplay: false, + messageBranchDisplay: false, + messageHashDisplay: false, + message: 'Bug fix commit(s)', +}; + +var stabilizationCommit = { + messageAuthorDisplay: false, + messageBranchDisplay: false, + messageHashDisplay: false, + message: 'Release stabilization commit(s)', +}; + +// You can manually fix columns to control the display. +var i = 0; +var longRunningCol = i++; +var featureV3Col = i++; +var developV3Col = i++; +var featureCol = i++; +var developCol = i++; +var releaseCol = i++; +var masterCol = i++; + +var gitgraph = new GitGraph(config); + +var master = gitgraph.branch({ + name: 'master', + column: masterCol, +}); +master.commit('Initial commit'); + +var develop = gitgraph.branch({ + parentBranch: master, + name: 'dev', + column: developCol, +}); + +var developV3 = gitgraph.branch({ + parentBranch: master, + name: 'dev-3.0', + column: developV3Col, +}); + +var longRunning = gitgraph.branch({ + parentBranch: master, + name: 'long-running-improvement', + column: longRunningCol, +}); + +develop.commit({ + messageDisplay: false, +}); +developV3.commit({ + messageDisplay: false, +}); + +longRunning.commit({ + messageDisplay: false, +}); +longRunning.merge(developV3); + +var feature1 = gitgraph.branch({ + parentBranch: develop, + name: 'feature/1-description', + column: featureCol, +}); +feature1.commit('#1 A feature to go into v2.8.0').commit({ + messageDisplay: false, +}); +develop.merge(feature1); +feature1.commit('Small Bugfix').commit({ + messageDisplay: false, +}); +feature1.merge(develop); + +var feature3X = gitgraph.branch({ + parentBranch: developV3, + name: 'feature/42-feature-for-3-x-only', + column: featureV3Col, +}); +feature3X.commit('#42 A feature to go into v3.X').commit({ + messageDisplay: false, +}); +feature3X.merge(developV3); + +var feature2 = gitgraph.branch({ + parentBranch: develop, + name: 'feature/2-description', + column: featureCol, +}); +feature2.commit('#2 Another feature to go into v2.8.0').commit({ + messageDisplay: false, +}); +feature2.merge(develop); +feature2.merge(developV3); + +develop.merge(master, { + dotStrokeWidth: 10, + message: 'Release v2.8.1 tagged', + tag: 'v2.8.1', +}); + +develop.commit({ + messageDisplay: false, +}); + +longRunning.commit({ + messageDisplay: false, +}); + +developV3.merge(longRunning); + +longRunning.commit({ + messageDisplay: false, +}); + +var feature3 = gitgraph.branch({ + parentBranch: develop, + name: 'bugfix/3-description', + column: featureCol, +}); + +feature3.commit('A feature to go into v2.8.0').commit({ + messageDisplay: false, +}); +feature3.merge(develop); + +longRunning.merge(developV3); + +developV3.commit({ + messageDisplay: false, + dotStrokeWidth: 10, +}); + +develop.commit({ + messageDisplay: false, +}); + +develop.commit({ + messageDisplay: false, +}); + +develop.merge(master, { + dotStrokeWidth: 10, + message: 'Release v2.9.0 tagged', + tag: 'v2.9.0', +}); + +develop.commit({ + messageDisplay: false, + dotStrokeWidth: 10, +}); + +developV3.checkout(); + +/* +developV3.merge(master, { + dotStrokeWidth: 10, + message: "Release v3.0.0 tagged", + tag: "v3.0.0" +}); +*/ diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 000000000..5969a91e8 --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,17 @@ +# Number of days of inactivity before an issue becomes stale +daysUntilStale: 60 +# Number of days of inactivity before a stale issue is closed +daysUntilClose: 30 +# Issues with these labels will never be considered stale +exemptLabels: + - "staged for next release 🏁" + - "pinned 📌" + - "triage 😰" +# Label to use when marking an issue as stale +staleLabel: "needs response 🤙" +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + It's hard to keep track of everything. This issue has been automatically marked as stale because it has not had recent activity, neither from the team nor the community. It will be closed if no further activity occurs. Please consider adding additional info, volunteering to contribute a fix for this issue, or making a further case that this is important to you, the team, and the project as a whole. Thanks! +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: > + Issue closed after going stale. It can be re-opened if still relevant. diff --git a/.gitignore b/.gitignore index 74ab03195..96232f92a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,17 @@ node_modules/ +pattern_exports/ .DS_Store -latest-change.txt -patternlab.json -.sass-cache/* -/sass-cache Thumbs.db -source/css/style.css.map +.nyc_output/ +.vscode/ .idea/ -public +.env +packages/core/test/public +packages/*/public +!packages/core/test/patterns/public/.gitkeep +!packages/core/test/patterns/testDependencyGraph.json +lerna-debug.log +packages/edition-node-gulp/dependencyGraph.json +packages/uikit-workshop/dist + +yarn-error.log diff --git a/.npmignore b/.npmignore new file mode 100644 index 000000000..13dae57b5 --- /dev/null +++ b/.npmignore @@ -0,0 +1,7 @@ +test/ +.DS_Store +.travis.yml +.nyc_output/ +.vscode/ +.idea/ + diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..43c97e719 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..7f976a5ae --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +12.12.0 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..f25d454d3 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,15 @@ +**/*.json +**/README.md +**/node_modules/ +**/bower_components/ +**/dist/ +**/public/ +**/*.min.js +packages/core/scripts/api.handlebars +packages/core/scripts/events.handlebars +packages/core/test/files/annotations.js +packages/**/annotations.js +**/uikit-workshop/src/js/**/* +*.json +*.md +*.scss diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..c1a6f6671 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "singleQuote": true, + "trailingComma": "es5" +} diff --git a/.travis.yml b/.travis.yml index 844871fce..3fb4c3593 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,25 +1,29 @@ language: node_js -node_js: - - node - - 6 - - 5 - - 4 +addons: + chrome: stable before_install: - - phantomjs --version + # version lifted from `.nvmrc` + - nvm install + - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.19.0 + - export PATH="$HOME/.yarn/bin:$PATH" before_script: - - npm install -g grunt-cli - - npm install patternengine-node-underscore - - npm install patternengine-node-handlebars - - npm install patternengine-node-twig + - yarn run setup + - npx lerna add @pattern-lab/engine-mustache --scope=@pattern-lab/core + - npx lerna add @pattern-lab/engine-handlebars --scope=@pattern-lab/core + - npx lerna add @pattern-lab/engine-underscore --scope=@pattern-lab/core + - npx lerna add @pattern-lab/engine-liquid --scope=@pattern-lab/core + - npx lerna add @pattern-lab/engine-twig --scope=@pattern-lab/core + - npx lerna add @pattern-lab/engine-react --scope=@pattern-lab/core + +script: travis_wait yarn run test branches: only: - master - dev - - issue/438-runAllTestsTravis notifications: webhooks: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..ddc797b61 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,396 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [5.9.3](https://github.com/pattern-lab/patternlab-node/compare/v5.9.2...v5.9.3) (2020-05-01) + + +### Bug Fixes + +* **plugintabs:** enabling multiple file formats [#1163](https://github.com/pattern-lab/patternlab-node/issues/1163) ([bb5e817](https://github.com/pattern-lab/patternlab-node/commit/bb5e8179e6b8553a6e1af0bede26db412b6c0b68)) +* adjust UIKit Nav updates to account for the noViewAll config variation ([73eac97](https://github.com/pattern-lab/patternlab-node/commit/73eac976461f4e587b0c30668942c4895aea319f)) +* make sure the top-level Dropdown menus always open/close ([7a8b418](https://github.com/pattern-lab/patternlab-node/commit/7a8b418bfcbd200ef8b2802b1a07964a9995bf9f)) +* only allow one top level nav item to be open at a time while rendering as a dropdown menu ([409bef3](https://github.com/pattern-lab/patternlab-node/commit/409bef37165260d9b728013ac33e7aa67541c832)) +* re-try Netlify preview to debug local vs prod rendering differences ([6da41a1](https://github.com/pattern-lab/patternlab-node/commit/6da41a14feea034f891c745dfeb062fa3b196235)) +* Update dependency on twing JS engine ([cfe88c6](https://github.com/pattern-lab/patternlab-node/commit/cfe88c6cdbf2219b9955eaa0ffcfc0e4a7683511)) +* **cli:** fix test script glob ([ff18eb5](https://github.com/pattern-lab/patternlab-node/commit/ff18eb51ce24fc5423b009168e85ede366069139)) + + + + + +## [5.9.2](https://github.com/pattern-lab/patternlab-node/compare/v5.9.1...v5.9.2) (2020-04-24) + +**Note:** Version bump only for package pl-node + + + + + +## [5.9.1](https://github.com/pattern-lab/patternlab-node/compare/v5.9.0...v5.9.1) (2020-04-24) + + +### Bug Fixes + +* **cli:** ensure specified directory exists prior to scaffold ([cc3b696](https://github.com/pattern-lab/patternlab-node/commit/cc3b69624d486c94ee3b1f4b1bbb0334a514fa59)) + + + + + +# [5.9.0](https://github.com/pattern-lab/patternlab-node/compare/v5.8.0...v5.9.0) (2020-04-24) + + +### Bug Fixes + +* **cli:** set current working directory before scaffolded npm init ([6d2186d](https://github.com/pattern-lab/patternlab-node/commit/6d2186d8e8a74634198a4474ca8ae83221dd70a9)) +* **core:** do not warn about uikit-polyfills ([6bb68e7](https://github.com/pattern-lab/patternlab-node/commit/6bb68e763769969546542bf7aaf6d1f4235c6622)) +* actually exit build when Twig render fails ([5d28a24](https://github.com/pattern-lab/patternlab-node/commit/5d28a24a53011396289c1e29e0a715cd82470185)) +* Update packages/engine-twig-php/lib/engine_twig_php.js ([c67d50e](https://github.com/pattern-lab/patternlab-node/commit/c67d50ebb5d69816b7514e85f129f8ecde984ad3)) + + +### Features + +* **docs:** yarnify ([5a47dc7](https://github.com/pattern-lab/patternlab-node/commit/5a47dc7b90dc5c43c12a51143b41943dcbd8564c)) +* **README:** add netlify badges ([941df8a](https://github.com/pattern-lab/patternlab-node/commit/941df8a59b6b75bc1255646005f329e40be68106)) + + + + + +# [5.8.0](https://github.com/pattern-lab/patternlab-node/compare/v5.7.2...v5.8.0) (2020-04-03) + + +### Bug Fixes + +* the namespace notation should not be mixed with PatternLab shorthand pattern naming & name is not defined in the textarea macro ([8250fe8](https://github.com/pattern-lab/patternlab-node/commit/8250fe88231d03735424d597eae40496da2cb48c)) +* Updated the README to reflect which issues are resolved. ([d90c3c4](https://github.com/pattern-lab/patternlab-node/commit/d90c3c4605f9a5bcd1153996e3f4d1a17d58bd92)) + + +### Features + +* switch engine-twig to use twing rather than node-twig ([daca95c](https://github.com/pattern-lab/patternlab-node/commit/daca95c4ffa48916fb6c67c5184bde9b624acd76)) + + + + + +## [5.7.2](https://github.com/pattern-lab/patternlab-node/compare/v5.7.1...v5.7.2) (2020-03-24) + + +### Bug Fixes + +* update iframe resizer UI to be hidden when iframe is full width ([9797c1a](https://github.com/pattern-lab/patternlab-node/commit/9797c1a047d746d21b88a1f57b57f618a03a54df)) + + + + + +## [5.7.1](https://github.com/pattern-lab/patternlab-node/compare/v5.7.0...v5.7.1) (2020-02-24) + + +### Bug Fixes + +* update twig-renderer ([46f53b7](https://github.com/pattern-lab/patternlab-node/commit/46f53b79f8bb0bb64a9c55fd32f29459cea6e28c)) + + + + + +# [5.7.0](https://github.com/pattern-lab/patternlab-node/compare/v5.6.0...v5.7.0) (2020-02-17) + + +### Features + +* **cli:** make options more user friendly ([ad845b3](https://github.com/pattern-lab/patternlab-node/commit/ad845b394ef81f90895ebb5bc6f12cc608e5e3d4)) + + + + + +# [5.6.0](https://github.com/pattern-lab/patternlab-node/compare/v5.5.0...v5.6.0) (2020-01-18) + + +### Bug Fixes + +* a11y fix on text contrast ([6d75b22](https://github.com/pattern-lab/patternlab-node/commit/6d75b226ce27228025b2915e5d402f7080faee31)) +* a11y issue on a missing description of that complementary icon ([4f13807](https://github.com/pattern-lab/patternlab-node/commit/4f13807cb93df33435088de3a51170b9c4515889)) + + +### Features + +* pass additional configuration into twig-php engine ([dff5a78](https://github.com/pattern-lab/patternlab-node/commit/dff5a7830918fa46e2692d9f9daed4121f803461)) + + + + + + +# [5.5.0](https://github.com/pattern-lab/patternlab-node/compare/v5.4.2...v5.5.0) (2019-12-19) + + +### Features + +* upgrade Twig to use new filter, map, reduce ([4218a5a](https://github.com/pattern-lab/patternlab-node/commit/4218a5a04b06027548afd9f417486297dd25fef8)) + + + + + +## [5.4.2](https://github.com/pattern-lab/patternlab-node/compare/v5.4.1...v5.4.2) (2019-11-27) + +**Note:** Version bump only for package pl-node-pr + + + + + +## [5.4.1](https://github.com/pattern-lab/patternlab-node/compare/v5.4.0...v5.4.1) (2019-11-26) + + +### Bug Fixes + +* temp workaround to address instance where the latest version of Edge supports ES modules but NOT Custom Elements ([ada3d82](https://github.com/pattern-lab/patternlab-node/commit/ada3d829019345fd33ed949f306972efdcb4fa57)) + + + + + +# [5.4.0](https://github.com/pattern-lab/patternlab-node/compare/v5.3.3...v5.4.0) (2019-11-26) + + +### Bug Fixes + +* re-add popstate listener ([6dbbd6a](https://github.com/pattern-lab/patternlab-node/commit/6dbbd6aae3709cc17544c12dd10588120eb9e71a)) +* **script:** remove quotes around starterkit ([e4897fb](https://github.com/pattern-lab/patternlab-node/commit/e4897fb6e4d4cd0985ab72397abd03ff04be514b)) +* add a new method to check if PL is currently compiling + add new method to get the config PL is using ([26e886c](https://github.com/pattern-lab/patternlab-node/commit/26e886c93db5d135c91de648724f7278c4d5b3e9)) +* check if dependency graph file exists before trying to remove ([f9af6a9](https://github.com/pattern-lab/patternlab-node/commit/f9af6a95025a22041e7ff8a4bfb19e4727385e98)) +* comment out example config to disable viewAll links ([ddb3fad](https://github.com/pattern-lab/patternlab-node/commit/ddb3fad5770d1d66432c4b583ae9af09a3a47d48)) + + +### Features + +* add the ability to disable Pattern Lab viewall links in the navigation ([156e609](https://github.com/pattern-lab/patternlab-node/commit/156e609a92e7f7e7ebd8f4f5cd77b5d695db8bad)) +* major improvements to local UIKit workflow ([4dc9173](https://github.com/pattern-lab/patternlab-node/commit/4dc9173a5a44b422e9677824de3728048b7c4f05)) +* test adding cross-env to Twig Edition test ([3f8bb01](https://github.com/pattern-lab/patternlab-node/commit/3f8bb01bc4e96a0aba61c213ea1619c02593defc)) + + + + + +## [5.3.3](https://github.com/pattern-lab/patternlab-node/compare/v5.3.2...v5.3.3) (2019-11-22) + + +### Bug Fixes + +* simplify overflow fix ([378cf42](https://github.com/pattern-lab/patternlab-node/commit/378cf4282a3e5b4f597287eb538270e3358c8c69)) +* testing potential FF fix for https://github.com/pattern-lab/patternlab-node/issues/1100 ([613bba1](https://github.com/pattern-lab/patternlab-node/commit/613bba104f2082be507938db78f1db7a07f6b8be)) + + + + + + +## [5.3.2](https://github.com/pattern-lab/patternlab-node/compare/v5.3.1...v5.3.2) (2019-11-14) + +**Note:** Version bump only for package pl-node + + + + + +## [5.3.1](https://github.com/pattern-lab/patternlab-node/compare/v5.3.0...v5.3.1) (2019-11-13) + + +### Bug Fixes + +* CSS fix to properly highlight the correct active page / link in the Nav; improve dropdown open / close animation ([ec4ab84](https://github.com/pattern-lab/patternlab-node/commit/ec4ab84ddc8007796c9012a3f493822d76f039a7)) +* small UI fixes for the sticky Tabs header on smaller screens + drawer content collapsing on smaller screens + better handling of Nav link cleanup when changing pages ([347e2fe](https://github.com/pattern-lab/patternlab-node/commit/347e2fe29a78a1d168005a07c656b4f9f1124c7f)) +* tweak header and drawer padding when viewing on a device with curved edges ([98e9baf](https://github.com/pattern-lab/patternlab-node/commit/98e9baf649eceb9124218a924b6b08097b910e86)) +* uikit fixes and minor CSS updates intended for the v5.3.0 release ([26c4ced](https://github.com/pattern-lab/patternlab-node/commit/26c4ceddaae09fa4fa4873f092c924274498c5da)) + + + + + +# [5.3.0](https://github.com/pattern-lab/patternlab-node/compare/v5.2.0...v5.3.0) (2019-11-13) + + +### Bug Fixes + +* add PluginTab workaround for Safari ([2fa9367](https://github.com/pattern-lab/patternlab-node/commit/2fa936769be65484af52f242dca2536a3382462c)) +* **core:** re-add cleanPublic fix ([c100bbc](https://github.com/pattern-lab/patternlab-node/commit/c100bbca3f339e9132acb9c482e98c1c8a66b8b5)) +* **plugin-tab:** defensively call addPanels ([b82bd12](https://github.com/pattern-lab/patternlab-node/commit/b82bd129fdbe48de95b62d75fb7fe95cea896b7e)) +* port over missing UIKit Sass that wasn't added in the original PR ([f7659e6](https://github.com/pattern-lab/patternlab-node/commit/f7659e64d0eee13be20921dd5afc48ac20ae93e6)) + + +### Features + +* port latest UIKit updates + fixes upstream ([d07952c](https://github.com/pattern-lab/patternlab-node/commit/d07952cb07e3792b995dda2e589262ecf4153fdc)) + + + + + +# [5.2.0](https://github.com/pattern-lab/patternlab-node/compare/v5.1.0...v5.2.0) (2019-11-12) + + +### Bug Fixes + +* **deploy:** add setup command ([7c1d8d1](https://github.com/pattern-lab/patternlab-node/commit/7c1d8d14842a467bb301e2ede2ec83074ff35ae2)) +* add missing $ ([c95a06e](https://github.com/pattern-lab/patternlab-node/commit/c95a06ece78631b068f8721666caf33452e57a7a)) +* address bug causing viewport width to progressively decrease in size when resizing your screen / refreshing on certain devices ([41b11af](https://github.com/pattern-lab/patternlab-node/commit/41b11af8aaaf066fcf99abd2513eae8706122d32)) +* configure the Logo's `altText` config option when used as an HTML attribute ([ade34a2](https://github.com/pattern-lab/patternlab-node/commit/ade34a29435f5112f0449ad020bee7e9dc2c81e1)) +* fix classname typo ([da3c5f1](https://github.com/pattern-lab/patternlab-node/commit/da3c5f144d22b1ac3ad99680a264433d4438ebb2)) +* temp workaround to fix content exceeding the height of drawer container ([435243c](https://github.com/pattern-lab/patternlab-node/commit/435243cbfbd000a7d96a0e9fa7beff1a988ede64)) +* update drawer UI to not collapse content on smaller screen sizes ([7147085](https://github.com/pattern-lab/patternlab-node/commit/71470856b8b389421348366afd247a599d1e9c86)) +* update package.json description in `@pattern-lab/uikit-polyfills` ([22fc44a](https://github.com/pattern-lab/patternlab-node/commit/22fc44a4b3683753a469a98abfcdad8f1234f28a)) +* **engine_twig_php:** Allow additional flexibility with twig namespaces. ([07bfaa3](https://github.com/pattern-lab/patternlab-node/commit/07bfaa35a00ff62fd2016cc9f34e09cf5af36559)) + + +### Features + +* add lit-element, basic Typescript support to Webpack ([611f705](https://github.com/pattern-lab/patternlab-node/commit/611f705be85eea8a31091169750d64e988798cee)) +* add local copy of new Slotify library till published to NPM ([63b9d83](https://github.com/pattern-lab/patternlab-node/commit/63b9d833908151ce5cb5aa5184c72254125c7ed1)) +* add new component to make Button-like styles more reusable ([5e7b014](https://github.com/pattern-lab/patternlab-node/commit/5e7b0140622eb89154c38925769a6def6d669fb3)) +* add new component ([e8ce2a9](https://github.com/pattern-lab/patternlab-node/commit/e8ce2a927365b8d5316a7d8229c979ff31b04907)) +* add support for auto-closing Nav when clicking inside of the rendered iframe ([9d602fe](https://github.com/pattern-lab/patternlab-node/commit/9d602fe335a5d3b5bca5cac258c2465934d9a46a)) +* add support for optional chaining syntax via Babel plugin ([c8886b6](https://github.com/pattern-lab/patternlab-node/commit/c8886b6d9d91fea246fa3ab7947f289509dc26d5)) +* major refactoring + UI updates to address cross browser support; UI cleanup and conversion of the majority of the remaining components over to lit-element ([2ff8e1c](https://github.com/pattern-lab/patternlab-node/commit/2ff8e1c98cdd02e8077064c48eca5f7754a3db02)) +* refactor + convert pl-toggle-info to lit-element ([85cd9c5](https://github.com/pattern-lab/patternlab-node/commit/85cd9c50ca814066bf999badf2071d84964f00cc)) +* refactor + convert pl-toggle-layout to lit-element ([46009d9](https://github.com/pattern-lab/patternlab-node/commit/46009d91b1cb9ed613baa5a7626cba4f42883465)) +* refactor + convert pl-toggle-theme to lit-element ([95a3b21](https://github.com/pattern-lab/patternlab-node/commit/95a3b21a89dacd2d5b4df8c134ce438d4efdbd04)) +* refactor Drawer to render via lit-element + massively improve rendering performance ([28d47eb](https://github.com/pattern-lab/patternlab-node/commit/28d47eb3cbbce038204203e786e5188b4cefe64f)) +* remove mixin that was causing outlines to be removed from default UI styles ([622ed76](https://github.com/pattern-lab/patternlab-node/commit/622ed76d435b3b2e31e412266c3090506f98051b)) +* temp add unsafe-svg directive till upstream PR merged ([34de61c](https://github.com/pattern-lab/patternlab-node/commit/34de61ccd9c7bb3b48ca5ef386a87efc8e84babc)) +* update the Nav design to not bold the active item in order to not shift the layout ([0eda431](https://github.com/pattern-lab/patternlab-node/commit/0eda4312ba9f4c61afa6322c3ff45f9cda0efc9e)) +* update Webpack config to use the latest Style Loader + new SVG icon system ([2ed70e7](https://github.com/pattern-lab/patternlab-node/commit/2ed70e79d8656c7314d8b3109aa1c34160ad24f9)) + + + + + + +# [5.1.0](https://github.com/pattern-lab/patternlab-node/compare/v5.0.2...v5.1.0) (2019-10-29) + + +### Features + +* **config:** add new default pattern export options ([a7487a0](https://github.com/pattern-lab/patternlab-node/commit/a7487a0681cb11e6f3c5c8eaefd62e5648ad5ea3)) + + + + + +## [5.0.2](https://github.com/pattern-lab/patternlab-node/compare/v5.0.1...v5.0.2) (2019-10-28) + + +### Bug Fixes + +* **uikit-workshop:** add template files to published bundle ([9005fce](https://github.com/pattern-lab/patternlab-node/commit/9005fcee9e129fb41d509f706195e1437bddc710)) +* **uikit-workshop:** add webpack config to published bundle ([060a573](https://github.com/pattern-lab/patternlab-node/commit/060a573cbddce9ee3d270d39337d0c8cac8372fa)) + + + + + +## [5.0.1](https://github.com/pattern-lab/patternlab-node/compare/v5.0.0...v5.0.1) (2019-10-28) + + +### Bug Fixes + +* add missing “dist” folder to array of files / folders published to NPM ([8829429](https://github.com/pattern-lab/patternlab-node/commit/88294296c438352570befd2eb6b9e1ca2ae3b750)) + + + + + +# [5.0.0](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25) + + +### Bug Fixes + +* **1049:** Treat folders like patterns only if they're subfolders of pattern groupings ([4eb79ab](https://github.com/pattern-lab/patternlab-node/commit/4eb79ab48b335a35b2e5ed3b7053974b8e8bb6b6)) +* **cli:** add custom install logic to edition-node ([f04fd26](https://github.com/pattern-lab/patternlab-node/commit/f04fd266429cd806987dab747e6d69bff9b926a4)) +* **cli:** allow any package to be installed as a starterkit ([d2aa1be](https://github.com/pattern-lab/patternlab-node/commit/d2aa1be810a0a7473dcc52391a2263dacfdda0b8)), closes [#1067](https://github.com/pattern-lab/patternlab-node/issues/1067) +* **cli:** merge config arrays via overwrite instead of concatenate ([42e5f7b](https://github.com/pattern-lab/patternlab-node/commit/42e5f7b42a26b4fc1f262c68ee4b474b546f2eac)) +* **cli:** proper path resolution to helpers ([a18fe5e](https://github.com/pattern-lab/patternlab-node/commit/a18fe5ef4d1c074a5eba8bfa255ebbee2261bf74)) +* **cli:** re-order and clarify engines ([e39e301](https://github.com/pattern-lab/patternlab-node/commit/e39e301a33306c6615fabf64262f1893ca682b97)) +* **core:** allow plugin resolution to follow normal algorithm ([3f6b83b](https://github.com/pattern-lab/patternlab-node/commit/3f6b83be080c88aec1d8b73bececb76f0f57a79d)) +* **core:** find plugins from config only and with simpler args ([fe7351c](https://github.com/pattern-lab/patternlab-node/commit/fe7351cba346425512cbb2ef3a1b7728ab06ae60)) +* **deploy:** add setup command ([74dd314](https://github.com/pattern-lab/patternlab-node/commit/74dd3142bf48873a9f1ec4e8dccb8aa2fef9001d)) +* **engine_twig_php:** Pseudo patterns Twig PHP ([226aa8b](https://github.com/pattern-lab/patternlab-node/commit/226aa8bbaaf5e418530ccf54a28f6c5657ee6dea)), closes [#1045](https://github.com/pattern-lab/patternlab-node/issues/1045) +* **engine_twig_php:** Twig incremental rebuilds ([1ade945](https://github.com/pattern-lab/patternlab-node/commit/1ade9451840b2645706a0b01129e2b697dc22d4b)), closes [#1015](https://github.com/pattern-lab/patternlab-node/issues/1015) +* **engine_twig_php:** Twig incremental rebuilds ([5d33f24](https://github.com/pattern-lab/patternlab-node/commit/5d33f24f156ebe50900701513a855de7de608dcf)), closes [#1015](https://github.com/pattern-lab/patternlab-node/issues/1015) +* **lerna:** typo in config ([525a47b](https://github.com/pattern-lab/patternlab-node/commit/525a47b51fba91c1bf5b7439735f48eb7dfa073e)) +* **lint:** Use const instead of var ([ad1e782](https://github.com/pattern-lab/patternlab-node/commit/ad1e782ef71295eb610f56d019eaa35499fb3f85)) +* **plugin:** correct spelling error and function locations ([d4abd88](https://github.com/pattern-lab/patternlab-node/commit/d4abd88cb017550002407241b5045a2ad1adb1dc)) +* **plugin-tab:** bump lodash from 4.17.5 to 4.17.15 in /packages/plugin-tab ([#1081](https://github.com/pattern-lab/patternlab-node/issues/1081)) ([3f89dda](https://github.com/pattern-lab/patternlab-node/commit/3f89dda1685874e251f9777f969c0943e0080881)) +* **plugin-tab:** handle params correctly ([d248993](https://github.com/pattern-lab/patternlab-node/commit/d2489939bb0db1a1d67b0e7f47dfb1838b88b0a0)) +* **starterkit:** add css output and build command ([ccb2d35](https://github.com/pattern-lab/patternlab-node/commit/ccb2d3569b741220324a3fa738ab3d4d2eb97ffe)) +* add better pre-rendering support ([8ecd615](https://github.com/pattern-lab/patternlab-node/commit/8ecd6159a89232f42e0a9dc3c688b6e21de8fc30)) +* add eslint fixes ([00d7bbe](https://github.com/pattern-lab/patternlab-node/commit/00d7bbe319ea77a6ee8cc9cd0348856feaaf13ad)) +* add missing @babel/runtime package to address silent error getting thrown on Travis ([1918d04](https://github.com/pattern-lab/patternlab-node/commit/1918d042d7e90cc8aaa2fdfcd8649961c0a5dd50)) +* add missing preact-render-to-string library ([881296a](https://github.com/pattern-lab/patternlab-node/commit/881296a2c256424beac28bd560c5b1a5e1fed005)) +* add repo info to root package.json so Auto knows what repo to configure for ([85142e8](https://github.com/pattern-lab/patternlab-node/commit/85142e8e94549edd7980459e5975d0639c34864d)) +* address unrelated eslint errors from PL core ([6ada00d](https://github.com/pattern-lab/patternlab-node/commit/6ada00d396eb436837f7453664bfa50522a2ec10)) +* correct typo in build logging ([96d989f](https://github.com/pattern-lab/patternlab-node/commit/96d989f8869630ba9f59705bfca66755f20e35ab)) +* fall back to seeing the current pattern's query string to `all` or the defaultPattern value if undefined when the iframe page initially loads ([a368459](https://github.com/pattern-lab/patternlab-node/commit/a3684590fca02cf96b99421b87a0ad0a711893ad)) +* fix incorrect Webpack version in package.json ([9788e89](https://github.com/pattern-lab/patternlab-node/commit/9788e8977921e31fe43f2a1ec19d4684dd4709c5)) +* fix issue with viewport height exceeding the space available ([95cd1cf](https://github.com/pattern-lab/patternlab-node/commit/95cd1cfa57f086ecb84ac2e996ecda81f0c6a1a6)) +* fix Prism.js typo so languages not found / supported don't throw a JS error ([a8c19f9](https://github.com/pattern-lab/patternlab-node/commit/a8c19f9f9b11d4abbdcd9e573fb0cb418d665660)) +* fix Twig Edition examples by adding missing Twig namespaces to config ([b4c20ef](https://github.com/pattern-lab/patternlab-node/commit/b4c20ef88ee0d3010760584c6f05ff7f92b711a6)) +* minor CSS fixes + fresh prod build ([8ac2c1f](https://github.com/pattern-lab/patternlab-node/commit/8ac2c1fa1c7558ed2ac50755f599a438d682ee2a)) +* re-enable displaying the top level `All` link if PL isn't configured to hide this specific link in the ishControlsHide config option. Addresses [#1048](https://github.com/pattern-lab/patternlab-node/issues/1048) ([6bb4e1a](https://github.com/pattern-lab/patternlab-node/commit/6bb4e1ac6f38b47f93030c8c5bca62d5db2132e4)) +* re-enable using the defaultPattern config for the initial iframe page load if defined ([d645ea1](https://github.com/pattern-lab/patternlab-node/commit/d645ea15150061d7ad13741d2dc37b12b9786411)) +* regenerate fresh UIKit build after fixing main JS issues ([9ea34d2](https://github.com/pattern-lab/patternlab-node/commit/9ea34d2efe43cafacb3729ac113121ba51126344)) +* Rename Handlebars and Nunjucks extension setting to "extend" ([74e5af2](https://github.com/pattern-lab/patternlab-node/commit/74e5af28c4e714fdfc1db535b94c52f3dc14a3a4)) +* squashing minor UI bugs ([a8a606c](https://github.com/pattern-lab/patternlab-node/commit/a8a606cfb224f7041f53ff5026a84e13fa17914c)) +* temporarily disable Random and Disco viewport controls until the full JS logic for these is re-enabled ([14b9a19](https://github.com/pattern-lab/patternlab-node/commit/14b9a19e4dee9462f3784eae28066893cc893624)) +* temporarily downgrade Preact version so tooltip used for displaying viewport sizes renders correctly ([52dcf85](https://github.com/pattern-lab/patternlab-node/commit/52dcf85e756ee171ca993288d98f5b5ef9a0a24b)) +* update autoprefixer browserslist config to address warning messages ([5e52f2b](https://github.com/pattern-lab/patternlab-node/commit/5e52f2b0ed02e2002ca867368636c3c0dc79ff0a)) +* update initial PL iframe path default ([a26fbb9](https://github.com/pattern-lab/patternlab-node/commit/a26fbb956e13901d1751c435b76de65637191ca4)) +* update Javascript to address merge conflict issue with previous PR merge / recent release ([cf2ecc1](https://github.com/pattern-lab/patternlab-node/commit/cf2ecc154383c3e8abd56dc88484370bc58ac30b)) +* update styles for pattern state dots ([7728acc](https://github.com/pattern-lab/patternlab-node/commit/7728accc9a6e5cd83be451f7d74e522dfe721cad)) +* update the default pattern that displays in the Handlebars demo ([ff1d85f](https://github.com/pattern-lab/patternlab-node/commit/ff1d85f2852fc4f210841e8e0aaf14b55165ce58)) +* **starterkit:** remove config file ([f90e38a](https://github.com/pattern-lab/patternlab-node/commit/f90e38aa873dcff0dd08fe4dabc3b71bf95080b6)) +* **starterkit:** use handlebars meta files ([d8f5e12](https://github.com/pattern-lab/patternlab-node/commit/d8f5e12471bd783bd3755626701ecc17669fc761)) +* updates to address eslint / prettier issues ([d945acc](https://github.com/pattern-lab/patternlab-node/commit/d945acc13b8e4e36f3815b017fbc12266c323d1f)) +* updates to fix eslint / prettier issues; update packages/core to reuse root .eslintrc.js file ([5b7a057](https://github.com/pattern-lab/patternlab-node/commit/5b7a057d46ccd16b5832af1441030c7b76f237a8)) +* use 100% of the screen available when JS is disabled / the first time the iframe loads up ([c0c5bff](https://github.com/pattern-lab/patternlab-node/commit/c0c5bff7a63b157d5b81dc2bcecee9e732ecfd4e)) +* **uikit:** clear out "404" responses when loading tabs ([73874b1](https://github.com/pattern-lab/patternlab-node/commit/73874b1b0b66ca6425c2b74331d417efdb529e2e)) +* **uikit-workshop:** fix merge problem ([d245b3b](https://github.com/pattern-lab/patternlab-node/commit/d245b3bca044c29f281052bf2feb95eeffafcf6b)) + + +### Features + +* **core:** invoke registered plugin hooks ([a54d775](https://github.com/pattern-lab/patternlab-node/commit/a54d7753b6939fe6a58da543f4fb34f64dd8901a)) +* **edition-node:** switch to engine-handlebars ([b481e22](https://github.com/pattern-lab/patternlab-node/commit/b481e22dc1f41ddd4da709621640a15190fba257)) +* **engine-handlebars:** Default location for helpers, like engine-nunjucks ([11c4180](https://github.com/pattern-lab/patternlab-node/commit/11c41805e0c3dbebb7109719c4f3c780d32feab5)) +* **engine-handlebars:** Demonstration of custom Handlebars helper ([f330b5b](https://github.com/pattern-lab/patternlab-node/commit/f330b5bca72f2f34bfafe5c2c64e6b0b8823eb1c)) +* **engine-handlebars:** Document the Helpers feature ([a01e040](https://github.com/pattern-lab/patternlab-node/commit/a01e040429a7f77dfeb28d67c690e835b97881de)) +* **engine-handlebars:** Load Handlebars helpers specified in the config ([a12df36](https://github.com/pattern-lab/patternlab-node/commit/a12df36d2a644dfac8ded1dfd94b987e99c29d79)) +* **engine-nunjucks:** Configurable extension locations; Use usePatternlabConfig() ([e54e3b3](https://github.com/pattern-lab/patternlab-node/commit/e54e3b3d48f934d3a4d44b9f4ff262f742a4aaf9)) +* **engine-react:** set package to private ([3aea881](https://github.com/pattern-lab/patternlab-node/commit/3aea8815f19df5b527cdda0b75cf99a9a8c3bc1e)) +* **plugin-tab:** pivot to using hook functions ([d4b2598](https://github.com/pattern-lab/patternlab-node/commit/d4b25984fc2a2646cc1876a5c635f57593c35f09)) +* **plugin-tab, core:** initial plugin hook exploration ([2f3d39a](https://github.com/pattern-lab/patternlab-node/commit/2f3d39ac6b125ad4c6b872e27ee224ce2ea33a12)) +* **starterkits:** add starterkit-handlebars-demo ([384d2cf](https://github.com/pattern-lab/patternlab-node/commit/384d2cfa3440c1e6f456d39f56ca6381f82f7689)) +* **uikit-workshop:** add plugin-loader ([fc966d6](https://github.com/pattern-lab/patternlab-node/commit/fc966d6b151e24055bc2f4146d6a90b5fb392765)) +* introduce netlify preview ([6c5d332](https://github.com/pattern-lab/patternlab-node/commit/6c5d332479fb6836bd8bd5530a074d13440f8ae4)) +* remove pre-built uikit dist folder and switch to auto-building when bootstrapping OR when publishing to NPM ([b5dd553](https://github.com/pattern-lab/patternlab-node/commit/b5dd5538ee00ddf1da321851865fa1c223cedb43)) +* switch to Yarn + Yarn workspaces ([f4c4ec3](https://github.com/pattern-lab/patternlab-node/commit/f4c4ec33cd30d372c87ffa904fbe7d5b819ee14e)) +* update Node to v12 ([fcbb970](https://github.com/pattern-lab/patternlab-node/commit/fcbb970648cdd775c9a88078f14c1f24c5b62d73)) + + +### Reverts + +* don't flatten folders containing only one item inside ([77f1f46](https://github.com/pattern-lab/patternlab-node/commit/77f1f46595328bd96fba46347b532295c65802d1)) + + +### BREAKING CHANGES + +* **core:** plugins now use async functions instead of events +* **plugin-tab:** event based listeners replaced with functions +* **cli:** previously, we concatenated arrays, which is unlikely to be intended +* **edition-node:** use handlebars over mustache diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..75ecbe287 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,17 @@ +# This is a comment. +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @global-owner1 and @global-owner2 will be requested for +# review when someone opens a pull request. +@pattern-lab/trusted-committers + +# CLI owner +/packages/cli @raphaelokon + +# uikit-workshop owner +/packages/uikit-workshop @sghoweri + +# engine-nunjucks owner +/packages/engine-nunjucks @danwhite85 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..9f536bcc4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at brian.muenzenmeyer@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/Gruntfile.js b/Gruntfile.js deleted file mode 100644 index 2d74d0d19..000000000 --- a/Gruntfile.js +++ /dev/null @@ -1,41 +0,0 @@ -module.exports = function (grunt) { - - /****************************** - * Project configuration. - * Should only be needed if you are developing against core, running tests, linting and want to run tests or increment package numbers - *****************************/ - grunt.initConfig({ - pkg: grunt.file.readJSON('package.json'), - concat: { - options: { - stripBanners: true, - banner: '/* \n * <%= pkg.name %> - v<%= pkg.version %> - <%= grunt.template.today("yyyy") %> \n * \n * <%= pkg.author.name %>, <%= pkg.contributors[0].name %>, and the web community.\n * Licensed under the <%= pkg.license %> license. \n * \n * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. \n *\n */\n\n', - }, - patternlab: { - src: './core/lib/patternlab.js', - dest: './core/lib/patternlab.js' - } - }, - nodeunit: { - all: ['test/*_tests.js'] - }, - eslint: { - options: { - configFile: './.eslintrc' - }, - target: ['./core/lib/*'] - } - }); - - // load all grunt tasks - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-eslint'); - grunt.loadNpmTasks('grunt-contrib-nodeunit'); - - //travis CI task - grunt.registerTask('travis', ['nodeunit', 'eslint']); - - //to be run prior to releasing a version - grunt.registerTask('build', ['nodeunit', 'eslint', 'concat']); - -}; diff --git a/LICENSE b/LICENSE index 44cbb3e41..c9b8c1daa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2013 Brad Frost, http://bradfrostweb.com & Dave Olsen, http://dmolsen.com & Brian Muenzenmeyer, http://brianmuenzenmeyer.com +Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in @@ -17,4 +17,4 @@ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index fa94173db..cd621433a 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,74 @@ -[![Build Status](https://travis-ci.org/pattern-lab/patternlab-node.png?branch=master)](https://travis-ci.org/pattern-lab/patternlab-node) ![current release](https://img.shields.io/github/release/pattern-lab/patternlab-node.svg) ![license](https://img.shields.io/github/license/pattern-lab/patternlab-node.svg) [![Join the chat at Gitter](https://badges.gitter.im/pattern-lab/node.svg)](https://gitter.im/pattern-lab/node) +

+ Pattern Lab Logo +

-# Pattern Lab Node Core +# Pattern Lab -This repository contains the core functionality for Pattern Lab Node. Pattern Lab Core is designed to be included as a dependency within [Node Editions](https://github.com/pattern-lab?utf8=%E2%9C%93&query=edition-node). -If this looks **REALLY DIFFERENT** from what you expected, check out the [ChangeLog](https://github.com/pattern-lab/patternlab-node/wiki/ChangeLog). +This monorepo contains the core of Pattern Lab / Node and all related engines, UI kits, plugins and utilities. Pattern Lab helps you and your team build thoughtful, pattern-driven user interfaces using atomic design principles. -* [Pattern Lab/Node: Gulp Edition](https://github.com/pattern-lab/edition-node-gulp) contains info how to get started within a Gulp task running environment. -* [Pattern Lab/Node: Grunt Edition](https://github.com/pattern-lab/edition-node-grunt) contains info how to get started within a Grunt task running environment. +If you'd like to see what a front-end project built with Pattern Lab looks like, check out this [online demo of Pattern Lab output](http://demo.patternlab.io/). -## Core Team +[![Build Status](https://travis-ci.org/pattern-lab/patternlab-node.svg?branch=master)](https://travis-ci.org/pattern-lab/patternlab-node) +![current release](https://img.shields.io/npm/v/@pattern-lab/core.svg) +![license](https://img.shields.io/github/license/pattern-lab/patternlab-node.svg) +[![Coverage Status](https://coveralls.io/repos/github/pattern-lab/patternlab-node/badge.svg?branch=master)](https://coveralls.io/github/pattern-lab/patternlab-node?branch=master) +[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) +[![node (scoped)](https://img.shields.io/node/v/@pattern-lab/patternlab-node.svg)]() +[![Join the chat at Gitter](https://badges.gitter.im/pattern-lab/node.svg)](https://gitter.im/pattern-lab/node) -* [@bmuenzenmeyer](https://github.com/bmuenzenmeyer) - Lead Maintainer -* [@geoffp](https://github.com/geoffp) - Core Contributor +Docs @ [![Netlify Status](https://api.netlify.com/api/v1/badges/d454dbde-02c5-4bd4-8393-4ab75e862b03/deploy-status)](https://app.netlify.com/sites/patternlab-docs-preview/deploys) -## Upgrading +Pattern Lab Preview @ [![Netlify Status](https://api.netlify.com/api/v1/badges/a6db1666-cb4f-4d26-82d4-9d88d875f286/deploy-status)](https://app.netlify.com/sites/patternlab-handlebars-preview/deploys) -If you find yourself here and are looking to upgrade, check out how to upgrade from version to version of Pattern Lab Node here: [https://github.com/pattern-lab/patternlab-node/wiki/Upgrading](https://github.com/pattern-lab/patternlab-node/wiki/Upgrading) +## Using Pattern Lab -## Command Line Interface +Refer to the [core usage guidelines](https://github.com/pattern-lab/patternlab-node/blob/master/packages/core/README.md#usage) -The [command line interface](https://github.com/pattern-lab/patternlab-node/wiki/Command-Line-Interface) is documented in the wiki, and already implemented for you within [Node Editions](https://github.com/pattern-lab?utf8=%E2%9C%93&query=edition-node). +### Installation -## Contributing +As of Pattern Lab Node 3.0.0, installation of [Editions](http://patternlab.io/docs/advanced-ecosystem-overview.html) is accomplished via the command line interface. + +The below assumes a new directory and project is required. This is likely what you want to do if starting from scratch. You could also run this within an existing project. The CLI will ask you for the installation location. + +1. Open a terminal window and following along below: + ```bash + mkdir new-project + cd new-project + npm create pattern-lab + ``` + > If you get an error stating that `npx` is not installed, ensure you are on `npm 5.2.0` or later by running `npm -v` or install it globally with `npm install -g npx`. [Learn more about npx.](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b) +1. Follow the on-screen prompts to choose your Edition and a Starterkit should you want one. + - If you chose `edition-node`, new commands in the "scripts" will be added in your `package.json`. + - If you chose `edition-node-gulp`, a `gulpfile.js` will be added to your project. + + > Notice that `@pattern-lab/cli` was installed as a depdendency. Learn how to further [use the cli in your own project](https://github.com/pattern-lab/patternlab-node/blob/dev/packages/cli/readme.md#configuring-your-project-to-use-the-cli). + + +## Ecosystem -If you'd like to contribute to Pattern Lab Node, please do so! There is always a lot of ground to cover and something for your wheelhouse. +![Pattern Lab Ecosystem](http://patternlab.io/assets/pattern-lab-2-image_18-large-opt.png) -No pull request is too small. Check out any [up for grabs issues](https://github.com/pattern-lab/patternlab-node/labels/up%20for%20grabs) as a good way to get your feet wet, or add some more unit tests. +Core, and Editions, are part of the [Pattern Lab Ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). With this architecture, we encourage people to write and maintain their own Editions, Starterkits, and even PatternEngines. -## Guidelines -1. Please keep your pull requests concise and limited to **ONE** substantive change at a time. This makes reviewing and testing so much easier. -2. _ALWAYS_ submit pull requests against the [dev branch](https://github.com/pattern-lab/patternlab-node/tree/dev). If this does not occur, I will first, try to redirect you gently, second, port over your contribution manually if time allows, and/or third, close your pull request. If you have a major feature to stabilize over time, talk to @bmuenzenmeyer about making a dedicated `feature-branch` -3. If you can, add some unit tests using the existing patterns in the `./test` directory -4. To help hack on core from an edition, read [this wiki page](https://github.com/pattern-lab/patternlab-node/wiki/Running-an-Edition-Against-Local-Core) +## Changelog -## Coding style -Two files combine within the project to define and maintain our coding style. +[Each package within this monorepo](https://github.com/pattern-lab/patternlab-node/tree/master/packages) has its own changelog. Below are the main ones to watch: -* The `.editorconfig` controls spaces / tabs within supported editors. Check out their [site](http://editorconfig.org/). -* The `.eslintrc` defines our javascript standards. Some editors will evaluate this real-time - otherwise it's run using `grunt|gulp build` +* [@pattern-lab/core changelog ](https://github.com/pattern-lab/patternlab-node/blob/master/packages/core/CHANGELOG.md) +* [@pattern-lab/cli changelog ](https://github.com/pattern-lab/patternlab-node/blob/master/packages/cli/CHANGELOG.md) -## Gitter +## Support for Pattern Lab + +Pattern Lab / Node wouldn't be what it is today without the support of the community. It will always be free and open source. Continued development is made possible in part from the support of [these wonderful project supporters](https://github.com/pattern-lab/patternlab-node/wiki/Thanks). If you want to learn more about supporting the project, visit the [Pattern Lab / Node Patreon page](https://www.patreon.com/patternlab). + +**:100: Thanks for support from the following:** + +* **[Brad Frost](http://bradfrost.com/)** +* Jan Ditze +* [Marcos Peebles](https://twitter.com/marcospeebles) +* [Maximilian Franzke](https://twitter.com/maedmaex) +* [Susan Simkins](https://twitter.com/susanmsimkins) + +## Contributing -The Pattern Lab Node team uses [our gitter.im channel, pattern-lab/node](https://gitter.im/pattern-lab/node) to keep in sync, share updates, and talk shop. Please stop by to say hello or as a first place to turn if stuck. Other channels in the Pattern Lab organization can be found on gitter too. +Refer to the [contribution guidelines](https://github.com/pattern-lab/patternlab-node/blob/master/.github/CONTRIBUTING.md). diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 000000000..6960ccf61 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,27 @@ +module.exports = { + presets: [ + [ + '@babel/preset-env', + { + targets: { + node: 'current', + }, + }, + ], + ], + plugins: [ + /** + * 1. Helps with our Web Component Preact renderer + */ + '@babel/plugin-syntax-jsx' /* [1] */, + [ + '@babel/plugin-transform-react-jsx' /* [1] */, + { + pragma: 'h', + pragmaFrag: '"span"', + throwIfNamespace: false, + useBuiltIns: false, + }, + ], + ], +}; diff --git a/core/lib/annotation_exporter.js b/core/lib/annotation_exporter.js deleted file mode 100644 index d12054145..000000000 --- a/core/lib/annotation_exporter.js +++ /dev/null @@ -1,101 +0,0 @@ -"use strict"; - -var path = require('path'), - glob = require('glob'), - fs = require('fs-extra'), - JSON5 = require('json5'), - _ = require('lodash'), - mp = require('./markdown_parser'); - -var annotations_exporter = function (pl) { - - var paths = pl.config.paths; - - /* - Returns the array of comments that used to be wrapped in raw JS. - */ - function parseAnnotationsJS() { - //attempt to read the file - try { - var oldAnnotations = fs.readFileSync(path.resolve(paths.source.annotations, 'annotations.js'), 'utf8'); - } catch (ex) { - if (pl.config.debug) { - console.log('annotations.js file missing from ' + paths.source.annotations + '. This may be expected.'); - } - return []; - } - - //parse as JSON by removing the old wrapping js syntax. comments and the trailing semi-colon - oldAnnotations = oldAnnotations.replace('var comments = ', ''); - oldAnnotations = oldAnnotations.replace('};', '}'); - - try { - var oldAnnotationsJSON = JSON5.parse(oldAnnotations); - } catch (ex) { - console.log('There was an error parsing JSON for ' + paths.source.annotations + 'annotations.js'); - console.log(ex); - return []; - } - return oldAnnotationsJSON.comments; - } - - function buildAnnotationMD(annotationsYAML, markdown_parser) { - var annotation = {}; - var markdownObj = markdown_parser.parse(annotationsYAML); - - annotation.el = markdownObj.el || markdownObj.selector; - annotation.title = markdownObj.title; - annotation.comment = markdownObj.markdown; - return annotation; - } - - function parseMDFile(annotations, parser) { - var annotations = annotations; - var markdown_parser = parser; - - return function (filePath) { - var annotationsMD = fs.readFileSync(path.resolve(filePath), 'utf8'); - - //take the annotation snippets and split them on our custom delimiter - var annotationsYAML = annotationsMD.split('~*~'); - for (var i = 0; i < annotationsYAML.length; i++) { - var annotation = buildAnnotationMD(annotationsYAML[i], markdown_parser); - annotations.push(annotation); - } - return false; - }; - } - - /* - Converts the *.md file yaml list into an array of annotations - */ - function parseAnnotationsMD() { - var markdown_parser = new mp(); - var annotations = []; - var mdFiles = glob.sync(paths.source.annotations + '/*.md'); - - mdFiles.forEach(parseMDFile(annotations, markdown_parser)); - return annotations; - } - - function gatherAnnotations() { - var annotationsJS = parseAnnotationsJS(); - var annotationsMD = parseAnnotationsMD(); - return _.unionBy(annotationsJS, annotationsMD, 'el'); - } - - return { - gather: function () { - return gatherAnnotations(); - }, - gatherJS: function () { - return parseAnnotationsJS(); - }, - gatherMD: function () { - return parseAnnotationsMD(); - } - }; - -}; - -module.exports = annotations_exporter; diff --git a/core/lib/lineage_hunter.js b/core/lib/lineage_hunter.js deleted file mode 100644 index e74e4a6c9..000000000 --- a/core/lib/lineage_hunter.js +++ /dev/null @@ -1,131 +0,0 @@ -"use strict"; - -var lineage_hunter = function () { - - var pa = require('./pattern_assembler'); - - function findlineage(pattern, patternlab) { - - var pattern_assembler = new pa(); - - //find the {{> template-name }} within patterns - var matches = pattern.findPartials(); - if (matches !== null) { - matches.forEach(function (match) { - //get the ancestorPattern - var ancestorPattern = pattern_assembler.getPartial(pattern.findPartial(match), patternlab); - - if (ancestorPattern && pattern.lineageIndex.indexOf(ancestorPattern.patternPartial) === -1) { - //add it since it didnt exist - pattern.lineageIndex.push(ancestorPattern.patternPartial); - - //create the more complex patternLineage object too - var l = { - "lineagePattern": ancestorPattern.patternPartial, - "lineagePath": "../../patterns/" + ancestorPattern.patternLink - }; - if (ancestorPattern.patternState) { - l.lineageState = ancestorPattern.patternState; - } - - pattern.lineage.push(l); - - //also, add the lineageR entry if it doesn't exist - if (ancestorPattern.lineageRIndex.indexOf(pattern.patternPartial) === -1) { - ancestorPattern.lineageRIndex.push(pattern.patternPartial); - - //create the more complex patternLineage object in reverse - var lr = { - "lineagePattern": pattern.patternPartial, - "lineagePath": "../../patterns/" + pattern.patternLink - }; - if (pattern.patternState) { - lr.lineageState = pattern.patternState; - } - - ancestorPattern.lineageR.push(lr); - } - } - }); - } - } - - function setPatternState(direction, pattern, targetPattern) { - // if the request came from the past, apply target pattern state to current pattern lineage - if (direction === 'fromPast') { - for (var i = 0; i < pattern.lineageIndex.length; i++) { - if (pattern.lineageIndex[i] === targetPattern.patternPartial) { - pattern.lineage[i].lineageState = targetPattern.patternState; - } - } - } else { - //the request came from the future, apply target pattern state to current pattern reverse lineage - for (var i = 0; i < pattern.lineageRIndex.length; i++) { - if (pattern.lineageRIndex[i] === targetPattern.patternPartial) { - pattern.lineageR[i].lineageState = targetPattern.patternState; - } - } - } - } - - - function cascadePatternStates(patternlab) { - - var pattern_assembler = new pa(); - - for (var i = 0; i < patternlab.patterns.length; i++) { - var pattern = patternlab.patterns[i]; - - //for each pattern with a defined state - if (pattern.patternState) { - - if (pattern.lineageIndex && pattern.lineageIndex.length > 0) { - - //find all lineage - patterns being consumed by this one - for (var h = 0; h < pattern.lineageIndex.length; h++) { - var lineagePattern = pattern_assembler.getPartial(pattern.lineageIndex[h], patternlab); - setPatternState('fromFuture', lineagePattern, pattern); - } - } - - if (pattern.lineageRIndex && pattern.lineageRIndex.length > 0) { - - //find all reverse lineage - that is, patterns consuming this one - for (var j = 0; j < pattern.lineageRIndex.length; j++) { - - var lineageRPattern = pattern_assembler.getPartial(pattern.lineageRIndex[j], patternlab); - - //only set patternState if pattern.patternState "is less than" the lineageRPattern.patternstate - //or if lineageRPattern.patternstate (the consuming pattern) does not have a state - //this makes patternlab apply the lowest common ancestor denominator - if (lineageRPattern.patternState === '' || (patternlab.config.patternStateCascade.indexOf(pattern.patternState) - < patternlab.config.patternStateCascade.indexOf(lineageRPattern.patternState))) { - - if (patternlab.config.debug) { - console.log('Found a lower common denominator pattern state: ' + pattern.patternState + ' on ' + pattern.patternPartial + '. Setting reverse lineage pattern ' + lineageRPattern.patternPartial + ' from ' + (lineageRPattern.patternState === '' ? '<>' : lineageRPattern.patternState)); - } - - lineageRPattern.patternState = pattern.patternState; - - //take this opportunity to overwrite the lineageRPattern's lineage state too - setPatternState('fromPast', lineageRPattern, pattern); - } else { - setPatternState('fromPast', pattern, lineageRPattern); - } - } - } - } - } - } - - return { - find_lineage: function (pattern, patternlab) { - findlineage(pattern, patternlab); - }, - cascade_pattern_states : function (patternlab) { - cascadePatternStates(patternlab); - } - }; -}; - -module.exports = lineage_hunter; diff --git a/core/lib/list_item_hunter.js b/core/lib/list_item_hunter.js deleted file mode 100644 index be55f35c5..000000000 --- a/core/lib/list_item_hunter.js +++ /dev/null @@ -1,138 +0,0 @@ -"use strict"; - -var list_item_hunter = function () { - - var extend = require('util')._extend, - JSON5 = require('json5'), - pa = require('./pattern_assembler'), - smh = require('./style_modifier_hunter'), - plutils = require('./utilities'), - Pattern = require('./object_factory').Pattern; - - var pattern_assembler = new pa(), - style_modifier_hunter = new smh(), - items = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen', 'sixteen', 'seventeen', 'eighteen', 'nineteen', 'twenty']; - - function processListItemPartials(pattern, patternlab) { - //find any listitem blocks - var matches = pattern.findListItems(); - - if (matches !== null) { - matches.forEach(function (liMatch) { - - if (patternlab.config.debug) { - console.log('found listItem of size ' + liMatch + ' inside ' + pattern.patternPartial); - } - - //find the boundaries of the block - var loopNumberString = liMatch.split('.')[1].split('}')[0].trim(); - var end = liMatch.replace('#', '/'); - var patternBlock = pattern.template.substring(pattern.template.indexOf(liMatch) + liMatch.length, pattern.template.indexOf(end)).trim(); - - //build arrays that repeat the block, however large we need to - var repeatedBlockTemplate = []; - var repeatedBlockHtml = ''; - for (var i = 0; i < items.indexOf(loopNumberString); i++) { - if (patternlab.config.debug) { - console.log('list item(s) in pattern', pattern.patternPartial, 'adding', patternBlock, 'to repeatedBlockTemplate'); - } - repeatedBlockTemplate.push(patternBlock); - } - - //check for a local listitems.json file - var listData; - try { - listData = JSON5.parse(JSON5.stringify(patternlab.listitems)); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.relPath); - console.log(err); - } - - listData = plutils.mergeData(listData, pattern.listitems); - listData = pattern_assembler.parse_data_links_specific(patternlab, listData, 'listitems.json + any pattern listitems.json'); - - //iterate over each copied block, rendering its contents along with pattenlab.listitems[i] - for (var i = 0; i < repeatedBlockTemplate.length; i++) { - - var thisBlockTemplate = repeatedBlockTemplate[i]; - var thisBlockHTML = ""; - - //combine listItem data with pattern data with global data - var itemData = listData['' + items.indexOf(loopNumberString)]; //this is a property like "2" - var globalData; - var localData; - try { - globalData = JSON5.parse(JSON5.stringify(patternlab.data)); - localData = JSON5.parse(JSON5.stringify(pattern.jsonFileData)); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.relPath); - console.log(err); - } - - var allData = plutils.mergeData(globalData, localData); - allData = plutils.mergeData(allData, itemData !== undefined ? itemData[i] : {}); //itemData could be undefined if the listblock contains no partial, just markup - allData.link = extend({}, patternlab.data.link); - - //check for partials within the repeated block - var foundPartials = Pattern.createEmpty({'template': thisBlockTemplate}).findPartials(); - - if (foundPartials && foundPartials.length > 0) { - - for (var j = 0; j < foundPartials.length; j++) { - - //get the partial - var partialName = foundPartials[j].match(/([\w\-\.\/~]+)/g)[0]; - var partialPattern = pattern_assembler.getPartial(partialName, patternlab); - - //create a copy of the partial so as to not pollute it after the get_pattern_by_key call. - var cleanPartialPattern; - try { - cleanPartialPattern = JSON5.parse(JSON5.stringify(partialPattern)); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.relPath); - console.log(err); - } - - //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #356 - cleanPartialPattern.extendedTemplate = cleanPartialPattern.template; - - //if partial has style modifier data, replace the styleModifier value - if (foundPartials[j].indexOf(':') > -1) { - style_modifier_hunter.consume_style_modifier(cleanPartialPattern, foundPartials[j], patternlab); - } - - //replace its reference within the block with the extended template - thisBlockTemplate = thisBlockTemplate.replace(foundPartials[j], cleanPartialPattern.extendedTemplate); - } - - //render with data - thisBlockHTML = pattern_assembler.renderPattern(thisBlockTemplate, allData, patternlab.partials); - - } else { - //just render with mergedData - thisBlockHTML = pattern_assembler.renderPattern(thisBlockTemplate, allData, patternlab.partials); - } - - //add the rendered HTML to our string - repeatedBlockHtml = repeatedBlockHtml + thisBlockHTML; - } - - //replace the block with our generated HTML - var repeatingBlock = pattern.extendedTemplate.substring(pattern.extendedTemplate.indexOf(liMatch), pattern.extendedTemplate.indexOf(end) + end.length); - pattern.extendedTemplate = pattern.extendedTemplate.replace(repeatingBlock, repeatedBlockHtml); - - //update the extendedTemplate in the partials object in case this pattern is consumed later - patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate; - - }); - } - } - - return { - process_list_item_partials: function (pattern, patternlab) { - processListItemPartials(pattern, patternlab); - } - }; -}; - -module.exports = list_item_hunter; diff --git a/core/lib/object_factory.js b/core/lib/object_factory.js deleted file mode 100644 index a03e2b400..000000000 --- a/core/lib/object_factory.js +++ /dev/null @@ -1,148 +0,0 @@ -"use strict"; - -var patternEngines = require('./pattern_engines'); -var path = require('path'); -var extend = require('util')._extend; - -// Pattern properties - -var Pattern = function (relPath, data, patternlab) { - // 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. - var pathObj = path.parse(path.normalize(relPath)); - 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' - this.fileExtension = pathObj.ext; // '.mustache' - - // this is the unique name, subDir + fileName (sans extension) - this.name = this.subdir.replace(path.sep, '-') + '-' + this.fileName.replace('~', '-'); // '00-atoms-00-global-00-colors' - - // the JSON used to render values in the pattern - this.jsonFileData = data || {}; - - // strip leading "00-" from the file name and flip tildes to dashes - this.patternBaseName = this.fileName.replace(/^\d*\-/, '').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 - - // the top-level pattern group this pattern belongs to. 'atoms' - this.patternGroup = this.subdir.split(path.sep)[0].replace(/^\d*-/, ''); - - //00-atoms if needed - this.patternType = this.subdir.split(path.sep)[0]; - - // the sub-group this pattern belongs to. - this.patternSubGroup = path.basename(this.subdir).replace(/^\d*-/, ''); // 'global' - - //00-colors if needed - this.patternSubType = path.basename(this.subdir); - - // the joined pattern group and subgroup directory - this.flatPatternPath = this.subdir.replace(/[\/\\]/g, '-'); // '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 = patternlab ? this.getPatternLink(patternlab, 'rendered') : null; - - // The canonical "key" by which this pattern is known. This is the callable - // name of the pattern. UPDATE: this.key is now known as this.patternPartial - this.patternPartial = this.patternGroup + '-' + this.patternBaseName; - - this.isPattern = true; - this.isFlatPattern = this.patternGroup === this.patternSubGroup; - this.patternState = ''; - this.template = ''; - this.patternPartialCode = ''; - this.lineage = []; - this.lineageIndex = []; - this.lineageR = []; - this.lineageRIndex = []; - this.isPseudoPattern = false; - this.engine = patternEngines.getEngineForPattern(this); -}; - -// Pattern methods - -Pattern.prototype = { - - // render method on oPatterns; this acts as a proxy for the PatternEngine's - // render function - render: function (data, partials) { - if (this.engine) { - return this.engine.renderPattern(this, data || this.jsonFileData, partials); - } - return null; - }, - - registerPartial: function () { - if (this.engine && typeof this.engine.registerPartial === 'function') { - this.engine.registerPartial(this); - } - }, - - // 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' - getPatternLink: function (patternlab, suffixType, customfileExtension) { - // if no suffixType is provided, we default to rendered - var suffixConfig = patternlab.config.outputFileSuffixes; - var suffix = suffixType ? suffixConfig[suffixType] : suffixConfig.rendered; - - if (suffixType === 'rawTemplate') { - return this.name + path.sep + this.name + suffix + this.fileExtension; - } - - if (suffixType === 'custom') { - return this.name + path.sep + this.name + customfileExtension; - } - - return this.name + path.sep + this.name + suffix + '.html'; - }, - - // the finders all delegate to the PatternEngine, which also encapsulates all - // appropriate regexes - findPartials: function () { - return this.engine.findPartials(this); - }, - - findPartialsWithStyleModifiers: function () { - return this.engine.findPartialsWithStyleModifiers(this); - }, - - findPartialsWithPatternParameters: function () { - return this.engine.findPartialsWithPatternParameters(this); - }, - - findListItems: function () { - return this.engine.findListItems(this); - }, - - findPartial: function (partialString) { - return this.engine.findPartial(partialString); - } -}; - -// Pattern static methods - -// factory: creates an empty Pattern for miscellaneous internal use, such as -// by list_item_hunter -Pattern.createEmpty = function (customProps, patternlab) { - var pattern = new Pattern('', null, patternlab); - return extend(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. -Pattern.create = function (relPath, data, customProps, patternlab) { - var newPattern = new Pattern(relPath || '', data || null, patternlab); - return extend(newPattern, customProps); -}; - -module.exports = { - Pattern: Pattern -}; diff --git a/core/lib/pattern_assembler.js b/core/lib/pattern_assembler.js deleted file mode 100644 index 69308215e..000000000 --- a/core/lib/pattern_assembler.js +++ /dev/null @@ -1,563 +0,0 @@ -"use strict"; - -var path = require('path'), - fs = require('fs-extra'), - Pattern = require('./object_factory').Pattern, - pph = require('./pseudopattern_hunter'), - mp = require('./markdown_parser'), - plutils = require('./utilities'), - patternEngines = require('./pattern_engines'), - lh = require('./lineage_hunter'), - lih = require('./list_item_hunter'), - smh = require('./style_modifier_hunter'), - ph = require('./parameter_hunter'), - JSON5 = require('json5'); - -var markdown_parser = new mp(); - -var pattern_assembler = function () { - // HELPER FUNCTIONS - - function getPartial(partialName, patternlab) { - //look for exact partial matches - for (var i = 0; i < patternlab.patterns.length; i++) { - if (patternlab.patterns[i].patternPartial === partialName) { - return patternlab.patterns[i]; - } - } - - //else look by verbose syntax - for (var i = 0; i < patternlab.patterns.length; i++) { - switch (partialName) { - case patternlab.patterns[i].relPath: - case patternlab.patterns[i].subdir + '/' + patternlab.patterns[i].fileName: - return patternlab.patterns[i]; - } - } - - //return the fuzzy match if all else fails - for (var i = 0; i < patternlab.patterns.length; i++) { - var partialParts = partialName.split('-'), - partialType = partialParts[0], - partialNameEnd = partialParts.slice(1).join('-'); - - if (patternlab.patterns[i].patternPartial.split('-')[0] === partialType && patternlab.patterns[i].patternPartial.indexOf(partialNameEnd) > -1) { - return patternlab.patterns[i]; - } - } - if (patternlab.config.debug) { - console.error('Could not find pattern with partial ' + partialName); - } - return undefined; - } - - function buildListItems(container) { - //combine all list items into one structure - var list = []; - for (var item in container.listitems) { - if (container.listitems.hasOwnProperty(item)) { - list.push(container.listitems[item]); - } - } - container.listItemArray = plutils.shuffle(list); - - for (var i = 1; i <= container.listItemArray.length; i++) { - var tempItems = []; - if (i === 1) { - tempItems.push(container.listItemArray[0]); - container.listitems['' + i ] = tempItems; - } else { - for (var c = 1; c <= i; c++) { - tempItems.push(container.listItemArray[c - 1]); - container.listitems['' + i ] = tempItems; - } - } - } - } - - /* - * Deprecated in favor of .md 'status' frontmatter inside a pattern. Still used for unit tests at this time. - * Will be removed in future versions - */ - function setState(pattern, patternlab, displayDeprecatedWarning) { - if (patternlab.config.patternStates && patternlab.config.patternStates[pattern.patternPartial]) { - - if (displayDeprecatedWarning) { - plutils.logRed("Deprecation Warning: Using patternlab-config.json patternStates object will be deprecated in favor of the state frontmatter key associated with individual pattern markdown files."); - console.log("This feature will still work in it's current form this release (but still be overridden by the new parsing method), and will be removed in the future."); - } - - pattern.patternState = patternlab.config.patternStates[pattern.patternPartial]; - } - } - - function addPattern(pattern, patternlab) { - - //add the link to the global object - patternlab.data.link[pattern.patternPartial] = '/patterns/' + pattern.patternLink; - - //only push to array if the array doesn't contain this pattern - var isNew = true; - for (var i = 0; i < patternlab.patterns.length; i++) { - //so we need the identifier to be unique, which patterns[i].relPath is - if (pattern.relPath === patternlab.patterns[i].relPath) { - //if relPath already exists, overwrite that element - patternlab.patterns[i] = pattern; - patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate || pattern.template; - isNew = false; - break; - } - } - - // if the pattern is new, we must register it with various data structures! - if (isNew) { - - if (patternlab.config.debug) { - console.log('found new pattern ' + pattern.patternPartial); - } - - // do global registration - if (pattern.isPattern) { - patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate || pattern.template; - - // do plugin-specific registration - pattern.registerPartial(); - } else { - patternlab.partials[pattern.patternPartial] = pattern.patternDesc; - } - - patternlab.patterns.push(pattern); - - } - } - - function addSubtypePattern(subtypePattern, patternlab) { - patternlab.subtypePatterns[subtypePattern.patternPartial] = subtypePattern; - } - - // Render a pattern on request. Long-term, this should probably go away. - function renderPattern(pattern, data, partials) { - // if we've been passed a full Pattern, it knows what kind of template it - // is, and how to render itself, so we just call its render method - if (pattern instanceof Pattern) { - return pattern.render(data, partials); - } else { - // otherwise, assume it's a plain mustache template string, and we - // therefore just need to create a dummpy pattern to be able to render - // it - var dummyPattern = Pattern.createEmpty({extendedTemplate: pattern}); - return patternEngines.mustache.renderPattern(dummyPattern, data, partials); - } - } - - function parsePatternMarkdown(currentPattern, patternlab) { - - try { - var markdownFileName = path.resolve(patternlab.config.paths.source.patterns, currentPattern.subdir, currentPattern.fileName + ".md"); - var markdownFileContents = fs.readFileSync(markdownFileName, 'utf8'); - - var markdownObject = markdown_parser.parse(markdownFileContents); - if (!plutils.isObjectEmpty(markdownObject)) { - //set keys and markdown itself - currentPattern.patternDescExists = true; - currentPattern.patternDesc = markdownObject.markdown; - - //consider looping through all keys eventually. would need to blacklist some properties and whitelist others - if (markdownObject.state) { - currentPattern.patternState = markdownObject.state; - } - if (markdownObject.order) { - currentPattern.order = markdownObject.order; - } - if (markdownObject.hidden) { - currentPattern.hidden = markdownObject.hidden; - } - if (markdownObject.excludeFromStyleguide) { - currentPattern.excludeFromStyleguide = markdownObject.excludeFromStyleguide; - } - if (markdownObject.tags) { - currentPattern.tags = markdownObject.tags; - } - if (markdownObject.links) { - currentPattern.links = markdownObject.links; - } - } else { - if (patternlab.config.debug) { - console.log('error processing markdown for ' + currentPattern.patternPartial); - } - } - - if (patternlab.config.debug) { - console.log('found pattern-specific markdown for ' + currentPattern.patternPartial); - } - } - catch (err) { - // do nothing when file not found - if (err.code !== 'ENOENT') { - console.log('there was an error setting pattern keys after markdown parsing of the companion file for pattern ' + currentPattern.patternPartial); - console.log(err); - } - } - } - - /** - * A helper that unravels a pattern looking for partials or listitems to unravel. - * The goal is really to convert pattern.template into pattern.extendedTemplate - * @param pattern - the pattern to decompose - * @param patternlab - global data store - * @param ignoreLineage - whether or not to hunt for lineage for this pattern - */ - function decomposePattern(pattern, patternlab, ignoreLineage) { - - var lineage_hunter = new lh(), - list_item_hunter = new lih(); - - pattern.extendedTemplate = pattern.template; - - //find how many partials there may be for the given pattern - var foundPatternPartials = pattern.findPartials(); - - //find any listItem blocks that within the pattern, even if there are no partials - list_item_hunter.process_list_item_partials(pattern, patternlab); - - // expand any partials present in this pattern; that is, drill down into - // the template and replace their calls in this template with rendered - // results - - if (pattern.engine.expandPartials && (foundPatternPartials !== null && foundPatternPartials.length > 0)) { - // eslint-disable-next-line - expandPartials(foundPatternPartials, list_item_hunter, patternlab, pattern); - - // update the extendedTemplate in the partials object in case this - // pattern is consumed later - patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate; - } - - //find pattern lineage - if (!ignoreLineage) { - lineage_hunter.find_lineage(pattern, patternlab); - } - - //add to patternlab object so we can look these up later. - addPattern(pattern, patternlab); - } - - function processPatternIterative(relPath, patternlab) { - - var relativeDepth = relPath.match(/\w(?=\\)|\w(?=\/)/g || []).length; - if (relativeDepth > 2) { - console.log(''); - plutils.logOrange('Warning:'); - plutils.logOrange('A pattern file: ' + relPath + ' was found greater than 2 levels deep from ' + patternlab.config.paths.source.patterns + '.'); - plutils.logOrange('It\'s strongly suggested to not deviate from the following structure under _patterns/'); - plutils.logOrange('[patternType]/[patternSubtype]/[patternName].[patternExtension]'); - console.log(''); - plutils.logOrange('While Pattern Lab may still function, assets may 404 and frontend links may break. Consider yourself warned. '); - plutils.logOrange('Read More: http://patternlab.io/docs/pattern-organization.html'); - console.log(''); - } - - //check if the found file is a top-level markdown file - var fileObject = path.parse(relPath); - if (fileObject.ext === '.md') { - try { - var proposedDirectory = path.resolve(patternlab.config.paths.source.patterns, fileObject.dir, fileObject.name); - var proposedDirectoryStats = fs.statSync(proposedDirectory); - if (proposedDirectoryStats.isDirectory()) { - var subTypeMarkdownFileContents = fs.readFileSync(proposedDirectory + '.md', 'utf8'); - var subTypeMarkdown = markdown_parser.parse(subTypeMarkdownFileContents); - var subTypePattern = new Pattern(relPath, null, patternlab); - subTypePattern.patternSectionSubtype = true; - subTypePattern.patternLink = subTypePattern.name + '/index.html'; - subTypePattern.patternDesc = subTypeMarkdown.markdown; - subTypePattern.flatPatternPath = subTypePattern.flatPatternPath + '-' + subTypePattern.fileName; - subTypePattern.isPattern = false; - subTypePattern.engine = null; - - addSubtypePattern(subTypePattern, patternlab); - return subTypePattern; - } - } catch (err) { - // no file exists, meaning it's a pattern markdown file - if (err.code !== 'ENOENT') { - console.log(err); - } - } - - } - - var pseudopattern_hunter = new pph(); - - //extract some information - var filename = fileObject.base; - var ext = fileObject.ext; - var patternsPath = patternlab.config.paths.source.patterns; - - // skip non-pattern files - if (!patternEngines.isPatternFile(filename, patternlab)) { return null; } - - //make a new Pattern Object - var currentPattern = new Pattern(relPath, null, patternlab); - - //if file is named in the syntax for variants - if (patternEngines.isPseudoPatternJSON(filename)) { - return currentPattern; - } - - //can ignore all non-supported files at this point - if (patternEngines.isFileExtensionSupported(ext) === false) { - return currentPattern; - } - - //see if this file has a state - setState(currentPattern, patternlab, true); - - //look for a json file for this template - try { - var jsonFilename = path.resolve(patternsPath, currentPattern.subdir, currentPattern.fileName + ".json"); - try { - var jsonFilenameStats = fs.statSync(jsonFilename); - } catch (err) { - //not a file - } - if (jsonFilenameStats && jsonFilenameStats.isFile()) { - currentPattern.jsonFileData = fs.readJSONSync(jsonFilename); - if (patternlab.config.debug) { - console.log('processPatternIterative: found pattern-specific data.json for ' + currentPattern.patternPartial); - } - } - } - catch (err) { - console.log('There was an error parsing sibling JSON for ' + currentPattern.relPath); - console.log(err); - } - - //look for a listitems.json file for this template - try { - var listJsonFileName = path.resolve(patternsPath, currentPattern.subdir, currentPattern.fileName + ".listitems.json"); - try { - var listJsonFileStats = fs.statSync(listJsonFileName); - } catch (err) { - //not a file - } - if (listJsonFileStats && listJsonFileStats.isFile()) { - currentPattern.listitems = fs.readJSONSync(listJsonFileName); - buildListItems(currentPattern); - if (patternlab.config.debug) { - console.log('found pattern-specific listitems.json for ' + currentPattern.patternPartial); - } - } - } - catch (err) { - console.log('There was an error parsing sibling listitem JSON for ' + currentPattern.relPath); - console.log(err); - } - - //look for a markdown file for this template - parsePatternMarkdown(currentPattern, patternlab); - - //add the raw template to memory - currentPattern.template = fs.readFileSync(path.resolve(patternsPath, relPath), 'utf8'); - - //find any stylemodifiers that may be in the current pattern - currentPattern.stylePartials = currentPattern.findPartialsWithStyleModifiers(); - - //find any pattern parameters that may be in the current pattern - currentPattern.parameteredPartials = currentPattern.findPartialsWithPatternParameters(); - - //add currentPattern to patternlab.patterns array - addPattern(currentPattern, patternlab); - - //look for a pseudo pattern by checking if there is a file containing same name, with ~ in it, ending in .json - pseudopattern_hunter.find_pseudopatterns(currentPattern, patternlab); - - return currentPattern; - } - - function processPatternRecursive(file, patternlab) { - - //find current pattern in patternlab object using var file as a partial - var currentPattern, i; - - for (i = 0; i < patternlab.patterns.length; i++) { - if (patternlab.patterns[i].relPath === file) { - currentPattern = patternlab.patterns[i]; - } - } - - //return if processing an ignored file - if (typeof currentPattern === 'undefined') { return; } - - //we are processing a markdown only pattern - if (currentPattern.engine === null) { return; } - - //call our helper method to actually unravel the pattern with any partials - decomposePattern(currentPattern, patternlab); - } - - function expandPartials(foundPatternPartials, list_item_hunter, patternlab, currentPattern) { - - var style_modifier_hunter = new smh(), - parameter_hunter = new ph(); - - if (patternlab.config.debug) { - console.log('found partials for ' + currentPattern.patternPartial); - } - - // determine if the template contains any pattern parameters. if so they - // must be immediately consumed - parameter_hunter.find_parameters(currentPattern, patternlab); - - //do something with the regular old partials - for (var i = 0; i < foundPatternPartials.length; i++) { - var partial = currentPattern.findPartial(foundPatternPartials[i]); - var partialPath; - - //identify which pattern this partial corresponds to - for (var j = 0; j < patternlab.patterns.length; j++) { - if (patternlab.patterns[j].patternPartial === partial || - patternlab.patterns[j].relPath.indexOf(partial) > -1) - { - partialPath = patternlab.patterns[j].relPath; - } - } - - //recurse through nested partials to fill out this extended template. - processPatternRecursive(partialPath, patternlab); - - //complete assembly of extended template - //create a copy of the partial so as to not pollute it after the getPartial call. - var partialPattern = getPartial(partial, patternlab); - var cleanPartialPattern = JSON5.parse(JSON5.stringify(partialPattern)); - - //if partial has style modifier data, replace the styleModifier value - if (currentPattern.stylePartials && currentPattern.stylePartials.length > 0) { - style_modifier_hunter.consume_style_modifier(cleanPartialPattern, foundPatternPartials[i], patternlab); - } - - currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace(foundPatternPartials[i], cleanPartialPattern.extendedTemplate); - } - } - - function parseDataLinksHelper(patternlab, obj, key) { - var linkRE, dataObjAsString, linkMatches; - - //check for link.patternPartial - linkRE = /link\.[A-z0-9-_]+/g; - - //stringify the passed in object - dataObjAsString = JSON5.stringify(obj); - if (!dataObjAsString) { return obj; } - - //find matches - linkMatches = dataObjAsString.match(linkRE); - - if (linkMatches) { - for (var i = 0; i < linkMatches.length; i++) { - var dataLink = linkMatches[i]; - if (dataLink && dataLink.split('.').length >= 2) { - - //get the partial the link refers to - var linkPatternPartial = dataLink.split('.')[1]; - var pattern = getPartial(linkPatternPartial, patternlab); - if (pattern !== undefined) { - - //get the full built link and replace it - var fullLink = patternlab.data.link[linkPatternPartial]; - if (fullLink) { - fullLink = path.normalize(fullLink).replace(/\\/g, '/'); - if (patternlab.config.debug) { - console.log('expanded data link from ' + dataLink + ' to ' + fullLink + ' inside ' + key); - } - - //also make sure our global replace didn't mess up a protocol - fullLink = fullLink.replace(/:\//g, '://'); - dataObjAsString = dataObjAsString.replace(dataLink, fullLink); - } - } else { - if (patternlab.config.debug) { - console.log('pattern not found for', dataLink, 'inside', key); - } - } - } - } - } - - var dataObj; - try { - dataObj = JSON5.parse(dataObjAsString); - } catch (err) { - console.log('There was an error parsing JSON for ' + key); - console.log(err); - } - - return dataObj; - } - - //look for pattern links included in data files. - //these will be in the form of link.* WITHOUT {{}}, which would still be there from direct pattern inclusion - function parseDataLinks(patternlab) { - //look for link.* such as link.pages-blog as a value - - patternlab.data = parseDataLinksHelper(patternlab, patternlab.data, 'data.json'); - - //loop through all patterns - for (var i = 0; i < patternlab.patterns.length; i++) { - patternlab.patterns[i].jsonFileData = parseDataLinksHelper(patternlab, patternlab.patterns[i].jsonFileData, patternlab.patterns[i].patternPartial); - } - } - - return { - find_pattern_partials: function (pattern) { - return pattern.findPartials(); - }, - find_pattern_partials_with_style_modifiers: function (pattern) { - return pattern.findPartialsWithStyleModifiers(); - }, - find_pattern_partials_with_parameters: function (pattern) { - return pattern.findPartialsWithPatternParameters(); - }, - find_list_items: function (pattern) { - return pattern.findListItems(); - }, - setPatternState: function (pattern, patternlab, displayDeprecatedWarning) { - setState(pattern, patternlab, displayDeprecatedWarning); - }, - addPattern: function (pattern, patternlab) { - addPattern(pattern, patternlab); - }, - addSubtypePattern: function (subtypePattern, patternlab) { - addSubtypePattern(subtypePattern, patternlab); - }, - decomposePattern: function (pattern, patternlab, ignoreLineage) { - decomposePattern(pattern, patternlab, ignoreLineage); - }, - renderPattern: function (template, data, partials) { - return renderPattern(template, data, partials); - }, - process_pattern_iterative: function (file, patternlab) { - return processPatternIterative(file, patternlab); - }, - process_pattern_recursive: function (file, patternlab, additionalData) { - processPatternRecursive(file, patternlab, additionalData); - }, - getPartial: function (partial, patternlab) { - return getPartial(partial, patternlab); - }, - combine_listItems: function (patternlab) { - buildListItems(patternlab); - }, - parse_data_links: function (patternlab) { - parseDataLinks(patternlab); - }, - parse_data_links_specific: function (patternlab, data, label) { - return parseDataLinksHelper(patternlab, data, label); - }, - parse_pattern_markdown: function (pattern, patternlab) { - parsePatternMarkdown(pattern, patternlab); - } - }; - -}; - -module.exports = pattern_assembler; diff --git a/core/lib/pattern_engines.js b/core/lib/pattern_engines.js deleted file mode 100644 index 451f8d4fe..000000000 --- a/core/lib/pattern_engines.js +++ /dev/null @@ -1,188 +0,0 @@ -// special shoutout to Geoffrey Pursell for single-handedly making Pattern Lab Node Pattern Engines possible! -'use strict'; - -var path = require('path'); -var diveSync = require('diveSync'); -var engineMatcher = /^patternengine-node-(.*)$/; -var enginesDirectories = [ - { - displayName: 'the core', - path: path.resolve(__dirname, '..', '..', 'node_modules') - }, - { - displayName: 'the edition or test directory', - path: path.join(process.cwd(), 'node_modules') - } -]; -var PatternEngines; // the main export object -var engineNameForExtension; // generated mapping of extension to engine name - - -// free "private" functions, for internal setup only - -// given a path: return the engine name if the path points to a valid engine -// module directory, or false if it doesn't -function isEngineModule(filePath) { - var baseName = path.basename(filePath); - var engineMatch = baseName.match(engineMatcher); - - if (engineMatch) { return engineMatch[1]; } - return false; -} - -function findEngineModulesInDirectory(dir) { - var foundEngines = []; - - diveSync(dir, { - recursive: false, - directories: true - }, function (err, filePath) { - if (err) { throw err; } - var foundEngineName = isEngineModule(filePath); - if (foundEngineName) { - foundEngines.push({ - name: foundEngineName, - modulePath: filePath - }); - } - }); - - return foundEngines; -} - -// Try to load engines! We scan for engines at each path specified above. This -// function is kind of a big deal. -function loadAllEngines(enginesObject) { - console.log('\nLoading engines...'); - - enginesDirectories.forEach(function (engineDirectory) { - var enginesInThisDir = findEngineModulesInDirectory(engineDirectory.path); - console.log("...scanning for engines in", engineDirectory.displayName + "..."); - - // find all engine-named things in this directory and try to load them, - // unless it's already been loaded. - enginesInThisDir.forEach(function (engineDiscovery) { - var errorMessage; - var successMessage = "good to go"; - - try { - // give it a try! load 'er up. But not if we already have, of course. - if (enginesObject[engineDiscovery.name]) { - throw new Error("already loaded, skipping."); - } - enginesObject[engineDiscovery.name] = require(engineDiscovery.modulePath); - } catch (err) { - errorMessage = err.message; - } finally { - // report on the status of the engine, one way or another! - console.log('-', engineDiscovery.name, 'engine:', errorMessage ? errorMessage : successMessage); - } - }); - }); - - // Complain if for some reason we haven't loaded any engines. - if (Object.keys(enginesObject).length === 0) { - throw new Error('No engines loaded! Something is seriously wrong.'); - } - console.log('...done loading engines.\n'); -} - - -// produce a mapping between file extension and engine name for each of the -// loaded engines -function createFileExtensionToEngineNameMap(enginesObject) { - var mapping = {}; - - Object.keys(enginesObject).forEach(function (engineName) { - var extensionForEngine = enginesObject[engineName].engineFileExtension; - mapping[extensionForEngine] = engineName; - }); - - return mapping; -} - - -// -// PatternEngines: the main export of this module -// -// It's an Object/hash of all loaded pattern engines, empty at first. My -// intention here is to make this return an object that can be used to obtain -// any loaded PatternEngine by addressing them like this: -// -// var PatternEngines = require('./pattern_engines/pattern_engines'); -// var Mustache = PatternEngines['mustache']; -// -// Object.create lets us create an object with a specified prototype. We want -// this here because we would like the object's "own properties" to include -// only the engine names so we can easily iterate over them; all the handy -// methods and properites below should therefore be on its prototype. - -PatternEngines = Object.create({ - getEngineNameForPattern: function (pattern) { - // avoid circular dependency by putting this in here. TODO: is this slow? - var of = require('./object_factory'); - - if (pattern instanceof of.Pattern && typeof pattern.fileExtension === 'string' && pattern.fileExtension) { - return engineNameForExtension[pattern.fileExtension]; - } - - // otherwise, assume it's a plain mustache template string and act - // accordingly - return 'mustache'; - }, - - getEngineForPattern: function (pattern) { - if (pattern.isPseudoPattern) { - return this.getEngineForPattern(pattern.basePattern); - } else { - var engineName = this.getEngineNameForPattern(pattern); - return this[engineName]; - } - }, - - getSupportedFileExtensions: function () { - var engineNames = Object.keys(PatternEngines); - return engineNames.map(function (engineName) { - return PatternEngines[engineName].engineFileExtension; - }); - }, - - isFileExtensionSupported: function (fileExtension) { - var supportedExtensions = PatternEngines.getSupportedFileExtensions(); - return (supportedExtensions.lastIndexOf(fileExtension) !== -1); - }, - - // given a filename, return a boolean: whether or not the filename indicates - // that the file is pseudopattern JSON - isPseudoPatternJSON: function (filename) { - var extension = path.extname(filename); - return (extension === '.json' && filename.indexOf('~') > -1); - }, - - // takes a filename string, not a full path; a basename (plus extension) - // ignore _underscored patterns, dotfiles, and anything not recognized by a - // loaded pattern engine. Pseudo-pattern .json files ARE considered to be - // pattern files! - isPatternFile: function (filename) { - // skip hidden patterns/files without a second thought - var extension = path.extname(filename); - if (filename.charAt(0) === '.' || - (extension === '.json' && !PatternEngines.isPseudoPatternJSON(filename))) { - return false; - } - - // not a hidden pattern, let's dig deeper - var supportedPatternFileExtensions = PatternEngines.getSupportedFileExtensions(); - return (supportedPatternFileExtensions.lastIndexOf(extension) !== -1 || - PatternEngines.isPseudoPatternJSON(filename)); - } -}); - - -// load up the engines we found -loadAllEngines(PatternEngines); - -// mapping of file extensions to engine names, for lookup use -engineNameForExtension = createFileExtensionToEngineNameMap(PatternEngines); - -module.exports = PatternEngines; diff --git a/core/lib/pattern_exporter.js b/core/lib/pattern_exporter.js deleted file mode 100644 index c53768420..000000000 --- a/core/lib/pattern_exporter.js +++ /dev/null @@ -1,30 +0,0 @@ -"use strict"; - -var fs = require('fs-extra'); - -var pattern_exporter = function () { - - function exportPatterns(patternlab) { - //read the config export options - var exportPartials = patternlab.config.patternExportPatternPartials; - - //find the chosen patterns to export - for (var i = 0; i < exportPartials.length; i++) { - for (var j = 0; j < patternlab.patterns.length; j++) { - if (exportPartials[i] === patternlab.patterns[j].patternPartial) { - //write matches to the desired location - fs.outputFileSync(patternlab.config.patternExportDirectory + patternlab.patterns[j].patternPartial + '.html', patternlab.patterns[j].patternPartialCode); - } - } - } - } - - return { - export_patterns: function (patternlab) { - exportPatterns(patternlab); - } - }; - -}; - -module.exports = pattern_exporter; diff --git a/core/lib/patternlab.js b/core/lib/patternlab.js deleted file mode 100644 index 15da30ce8..000000000 --- a/core/lib/patternlab.js +++ /dev/null @@ -1,499 +0,0 @@ -/* - * patternlab-node - v2.6.0-alpha - 2016 - * - * Brian Muenzenmeyer, Geoff Pursell, and the web community. - * Licensed under the MIT license. - * - * Many thanks to Brad Frost and Dave Olsen for inspiration, encouragement, and advice. - * - */ - -"use strict"; - -var diveSync = require('diveSync'), - glob = require('glob'), - _ = require('lodash'), - path = require('path'), - cleanHtml = require('js-beautify').html, - inherits = require('util').inherits, - pm = require('./plugin_manager'), - plutils = require('./utilities'); - -var EventEmitter = require('events').EventEmitter; - -function buildPatternData(dataFilesPath, fs) { - var dataFiles = glob.sync(dataFilesPath + '*.json', {"ignore" : [dataFilesPath + 'listitems.json']}); - var mergeObject = {}; - dataFiles.forEach(function (filePath) { - var jsonData = fs.readJSONSync(path.resolve(filePath), 'utf8'); - mergeObject = _.merge(mergeObject, jsonData); - }); - return mergeObject; -} - -// GTP: these two diveSync pattern processors factored out so they can be reused -// from unit tests to reduce code dupe! -function processAllPatternsIterative(pattern_assembler, patterns_dir, patternlab) { - diveSync( - patterns_dir, - function (err, file) { - //log any errors - if (err) { - console.log(err); - return; - } - pattern_assembler.process_pattern_iterative(path.relative(patterns_dir, file), patternlab); - } - ); -} - -function processAllPatternsRecursive(pattern_assembler, patterns_dir, patternlab) { - diveSync( - patterns_dir, - function (err, file) { - //log any errors - if (err) { - console.log(err); - return; - } - pattern_assembler.process_pattern_recursive(path.relative(patterns_dir, file), patternlab); - } - ); -} - -function checkConfiguration(patternlab) { - //default the output suffixes if not present - var outputFileSuffixes = { - rendered: '.rendered', - rawTemplate: '', - markupOnly: '.markup-only' - }; - - if (!patternlab.config.outputFileSuffixes) { - plutils.logOrange('Configuration Object "outputFileSuffixes" not found, and defaulted to the following:'); - console.log(outputFileSuffixes); - plutils.logOrange('Since Pattern Lab Core 2.3.0 this configuration option is required. Suggest you add it to your patternlab-config.json file.'); - console.log(); - } - patternlab.config.outputFileSuffixes = _.extend(outputFileSuffixes, patternlab.config.outputFileSuffixes); -} - -/** - * Finds and calls the main method of any found plugins. - * @param patternlab - global data store - */ -function initializePlugins(patternlab) { - var plugin_manager = new pm(patternlab.config, path.resolve(__dirname, '../../patternlab-config.json')); - var foundPlugins = plugin_manager.detect_plugins(); - - if (foundPlugins && foundPlugins.length > 0) { - - for (var i = 0; i < foundPlugins.length; i++) { - var plugin = plugin_manager.load_plugin(foundPlugins[i]); - plugin(patternlab); - } - } -} - -function PatternLabEventEmitter() { - EventEmitter.call(this); -} -inherits(PatternLabEventEmitter, EventEmitter); - -var patternlab_engine = function (config) { - 'use strict'; - - var JSON5 = require('json5'), - fs = require('fs-extra'), - pa = require('./pattern_assembler'), - pe = require('./pattern_exporter'), - lh = require('./lineage_hunter'), - ui = require('./ui_builder'), - sm = require('./starterkit_manager'), - Pattern = require('./object_factory').Pattern, - patternlab = {}; - - var pattern_assembler = new pa(), - pattern_exporter = new pe(), - lineage_hunter = new lh(); - - patternlab.package = fs.readJSONSync(path.resolve(__dirname, '../../package.json')); - patternlab.config = config || fs.readJSONSync(path.resolve(__dirname, '../../patternlab-config.json')); - patternlab.events = new PatternLabEventEmitter(); - - checkConfiguration(patternlab); - - //todo: determine if this is the best place to wire up plugins - initializePlugins(patternlab); - - var paths = patternlab.config.paths; - - function getVersion() { - console.log(patternlab.package.version); - } - - function help() { - - console.log(''); - - console.log('|=======================================|'); - plutils.logGreen(' Pattern Lab Node Help v' + patternlab.package.version); - console.log('|=======================================|'); - - console.log(''); - console.log('Command Line Interface - usually consumed by an edition'); - console.log(''); - - plutils.logGreen(' patternlab:build'); - console.log(' > Compiles the patterns and frontend, outputting to config.paths.public'); - console.log(''); - - plutils.logGreen(' patternlab:patternsonly'); - console.log(' > Compiles the patterns only, outputting to config.paths.public'); - console.log(''); - - plutils.logGreen(' patternlab:version'); - console.log(' > Return the version of patternlab-node you have installed'); - console.log(''); - - plutils.logGreen(' patternlab:help'); - console.log(' > Get more information about patternlab-node, pattern lab in general, and where to report issues.'); - console.log(''); - - plutils.logGreen(' patternlab:liststarterkits'); - console.log(' > Returns a url with the list of available starterkits hosted on the Pattern Lab organization Github account'); - console.log(''); - - plutils.logGreen(' patternlab:loadstarterkit'); - console.log(' > Load a starterkit into config.paths.source/*'); - console.log(' > NOTE: Overwrites existing content, and only cleans out existing directory if --clean=true argument is passed.'); - console.log(' > NOTE: In most cases, `npm install starterkit-name` will precede this call.'); - console.log(' > arguments:'); - console.log(' -- kit '); - console.log(' > the name of the starter kit to load'); - console.log(' -- clean '); - console.log(' > removes all files from config.paths.source/ prior to load'); - console.log(' > example (gulp):'); - console.log(' `gulp patternlab:loadstarterkit --kit=starterkit-mustache-demo`'); - console.log(''); - - console.log('==============================='); - console.log(''); - console.log('Visit http://patternlab.io/ for more info about Pattern Lab'); - console.log('Visit https://github.com/pattern-lab/patternlab-node/issues to open an issue.'); - console.log('Visit https://github.com/pattern-lab/patternlab-node/wiki to view the changelog, roadmap, and other info.'); - console.log(''); - console.log('==============================='); - } - - function printDebug() { - // A replacer function to pass to stringify below; this is here to prevent - // the debug output from blowing up into a massive fireball of circular - // references. This happens specifically with the Handlebars engine. Remove - // if you like 180MB log files. - function propertyStringReplacer(key, value) { - if (key === 'engine' && value && value.engineName) { - return '{' + value.engineName + ' engine object}'; - } - return value; - } - - //debug file can be written by setting flag on patternlab-config.json - if (patternlab.config.debug) { - console.log('writing patternlab debug file to ./patternlab.json'); - fs.outputFileSync('./patternlab.json', JSON.stringify(patternlab, propertyStringReplacer, 3)); - } - } - - function setCacheBust() { - if (patternlab.config.cacheBust) { - if (patternlab.config.debug) { - console.log('setting cacheBuster value for frontend assets.'); - } - patternlab.cacheBuster = new Date().getTime(); - } else { - patternlab.cacheBuster = 0; - } - } - - function listStarterkits() { - var starterkit_manager = new sm(patternlab.config); - return starterkit_manager.list_starterkits(); - } - - function loadStarterKit(starterkitName, clean) { - var starterkit_manager = new sm(patternlab.config); - starterkit_manager.load_starterkit(starterkitName, clean); - } - - /** - * Process the user-defined pattern head and prepare it for rendering - */ - function processHeadPattern() { - try { - var headPath = path.resolve(paths.source.meta, '_00-head.mustache'); - var headPattern = new Pattern(headPath, null, patternlab); - headPattern.template = fs.readFileSync(headPath, 'utf8'); - headPattern.isPattern = false; - headPattern.isMetaPattern = true; - pattern_assembler.decomposePattern(headPattern, patternlab, true); - patternlab.userHead = headPattern.extendedTemplate; - } - catch (ex) { - plutils.logRed('\nWARNING: Could not find the user-editable header template, currently configured to be at ' + path.join(config.paths.source.meta, '_00-head.mustache') + '. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n'); - if (patternlab.config.debug) { console.log(ex); } - process.exit(1); - } - } - - /** - * Process the user-defined pattern footer and prepare it for rendering - */ - function processFootPattern() { - try { - var footPath = path.resolve(paths.source.meta, '_01-foot.mustache'); - var footPattern = new Pattern(footPath, null, patternlab); - footPattern.template = fs.readFileSync(footPath, 'utf8'); - footPattern.isPattern = false; - footPattern.isMetaPattern = true; - pattern_assembler.decomposePattern(footPattern, patternlab, true); - patternlab.userFoot = footPattern.extendedTemplate; - } - catch (ex) { - plutils.logRed('\nWARNING: Could not find the user-editable footer template, currently configured to be at ' + path.join(config.paths.source.meta, '_01-foot.mustache') + '. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.\n'); - if (patternlab.config.debug) { console.log(ex); } - process.exit(1); - } - } - - function buildPatterns(deletePatternDir) { - - patternlab.events.emit('patternlab-build-pattern-start', patternlab); - - try { - patternlab.data = buildPatternData(paths.source.data, fs); - } catch (ex) { - plutils.logRed('missing or malformed' + paths.source.data + 'data.json Pattern Lab may not work without this file.'); - patternlab.data = {}; - } - try { - patternlab.listitems = fs.readJSONSync(path.resolve(paths.source.data, 'listitems.json')); - } catch (ex) { - plutils.logOrange('WARNING: missing or malformed ' + paths.source.data + 'listitems.json file. Pattern Lab may not work without this file.'); - patternlab.listitems = {}; - } - try { - patternlab.header = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials', 'general-header.mustache'), 'utf8'); - patternlab.footer = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials', 'general-footer.mustache'), 'utf8'); - patternlab.patternSection = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials', 'patternSection.mustache'), 'utf8'); - patternlab.patternSectionSubType = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'partials', 'patternSectionSubtype.mustache'), 'utf8'); - patternlab.viewAll = fs.readFileSync(path.resolve(paths.source.patternlabFiles, 'viewall.mustache'), 'utf8'); - } catch (ex) { - console.log(ex); - plutils.logRed('\nERROR: missing an essential file from ' + paths.source.patternlabFiles + '. Pattern Lab won\'t work without this file.\n'); - process.exit(1); - } - patternlab.patterns = []; - patternlab.subtypePatterns = {}; - patternlab.partials = {}; - patternlab.data.link = {}; - - setCacheBust(); - - pattern_assembler.combine_listItems(patternlab); - - patternlab.events.emit('patternlab-build-global-data-end', patternlab); - - // diveSync once to perform iterative populating of patternlab object - processAllPatternsIterative(pattern_assembler, paths.source.patterns, patternlab); - - patternlab.events.emit('patternlab-pattern-iteration-end', patternlab); - - //diveSync again to recursively include partials, filling out the - //extendedTemplate property of the patternlab.patterns elements - processAllPatternsRecursive(pattern_assembler, paths.source.patterns, patternlab); - - //take the user defined head and foot and process any data and patterns that apply - processHeadPattern(); - processFootPattern(); - - //now that all the main patterns are known, look for any links that might be within data and expand them - //we need to do this before expanding patterns & partials into extendedTemplates, otherwise we could lose the data -> partial reference - pattern_assembler.parse_data_links(patternlab); - - //cascade any patternStates - lineage_hunter.cascade_pattern_states(patternlab); - - //delete the contents of config.patterns.public before writing - if (deletePatternDir) { - fs.removeSync(paths.public.patterns); - fs.emptyDirSync(paths.public.patterns); - } - - //set pattern-specific header if necessary - var head; - if (patternlab.userHead) { - head = patternlab.userHead; - } else { - head = patternlab.header; - } - - //set the pattern-specific header by compiling the general-header with data, and then adding it to the meta header - patternlab.data.patternLabHead = pattern_assembler.renderPattern(patternlab.header, { - cacheBuster: patternlab.cacheBuster - }); - - //render all patterns last, so lineageR works - patternlab.patterns.forEach(function (pattern) { - - if (!pattern.isPattern) { - return false; - } - - //todo move this into lineage_hunter - pattern.patternLineages = pattern.lineage; - pattern.patternLineageExists = pattern.lineage.length > 0; - pattern.patternLineagesR = pattern.lineageR; - pattern.patternLineageRExists = pattern.lineageR.length > 0; - pattern.patternLineageEExists = pattern.patternLineageExists || pattern.patternLineageRExists; - - //render the pattern, but first consolidate any data we may have - var allData; - try { - allData = JSON5.parse(JSON5.stringify(patternlab.data)); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.relPath); - console.log(err); - } - allData = plutils.mergeData(allData, pattern.jsonFileData); - allData.cacheBuster = patternlab.cacheBuster; - - //re-rendering the headHTML each time allows pattern-specific data to influence the head of the pattern - pattern.header = head; - var headHTML = pattern_assembler.renderPattern(pattern.header, allData); - - //render the extendedTemplate with all data - pattern.patternPartialCode = pattern_assembler.renderPattern(pattern, allData); - - // stringify this data for individual pattern rendering and use on the styleguide - // see if patternData really needs these other duped values - pattern.patternData = JSON.stringify({ - cssEnabled: false, - patternLineageExists: pattern.patternLineageExists, - patternLineages: pattern.patternLineages, - lineage: pattern.patternLineages, - patternLineageRExists: pattern.patternLineageRExists, - patternLineagesR: pattern.patternLineagesR, - lineageR: pattern.patternLineagesR, - patternLineageEExists: pattern.patternLineageExists || pattern.patternLineageRExists, - patternDesc: pattern.patternDescExists ? pattern.patternDesc : '', - patternBreadcrumb: - pattern.patternGroup === pattern.patternSubGroup ? - { - patternType: pattern.patternGroup - } : { - patternType: pattern.patternGroup, - patternSubtype: pattern.patternSubGroup - }, - patternExtension: pattern.fileExtension.substr(1), //remove the dot because styleguide asset default adds it for us - patternName: pattern.patternName, - patternPartial: pattern.patternPartial, - patternState: pattern.patternState, - patternEngineName: pattern.engine.engineName, - extraOutput: {} - }); - - //set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer - var footerPartial = pattern_assembler.renderPattern(patternlab.footer, { - isPattern: pattern.isPattern, - patternData: pattern.patternData, - cacheBuster: patternlab.cacheBuster - }); - - var allFooterData; - try { - allFooterData = JSON5.parse(JSON5.stringify(patternlab.data)); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.relPath); - console.log(err); - } - allFooterData = plutils.mergeData(allFooterData, pattern.jsonFileData); - allFooterData.patternLabFoot = footerPartial; - - var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, allFooterData); - - patternlab.events.emit('patternlab-pattern-write-begin', patternlab, pattern); - - //write the compiled template to the public patterns directory - var patternPage = headHTML + pattern.patternPartialCode + footerHTML; - - //beautify the output if configured to do so - var cleanedPatternPage = config.cleanOutputHtml ? cleanHtml(patternPage, {indent_size: 2}) : patternPage; - var cleanedPatternPartialCode = config.cleanOutputHtml ? cleanHtml(pattern.patternPartialCode, {indent_size: 2}) : pattern.patternPartialCode; - var cleanedPatternTemplateCode = config.cleanOutputHtml ? cleanHtml(pattern.template, {indent_size: 2}) : pattern.template; - - //write the compiled template to the public patterns directory - fs.outputFileSync(paths.public.patterns + pattern.getPatternLink(patternlab, 'rendered'), cleanedPatternPage); - - //write the mustache file too - fs.outputFileSync(paths.public.patterns + pattern.getPatternLink(patternlab, 'rawTemplate'), cleanedPatternTemplateCode); - - //write the encoded version too - fs.outputFileSync(paths.public.patterns + pattern.getPatternLink(patternlab, 'markupOnly'), cleanedPatternPartialCode); - - patternlab.events.emit('patternlab-pattern-write-end', patternlab, pattern); - - return true; - }); - - //export patterns if necessary - pattern_exporter.export_patterns(patternlab); - } - - return { - version: function () { - return getVersion(); - }, - build: function (callback, deletePatternDir) { - if (patternlab && patternlab.isBusy) { - console.log('Pattern Lab is busy building a previous run - returning early.'); - return; - } - patternlab.isBusy = true; - buildPatterns(deletePatternDir); - new ui().buildFrontend(patternlab); - printDebug(); - patternlab.isBusy = false; - callback(); - }, - help: function () { - help(); - }, - patternsonly: function (callback, deletePatternDir) { - if (patternlab && patternlab.isBusy) { - console.log('Pattern Lab is busy building a previous run - returning early.'); - return; - } - patternlab.isBusy = true; - buildPatterns(deletePatternDir); - printDebug(); - patternlab.isBusy = false; - callback(); - }, - liststarterkits: function () { - return listStarterkits(); - }, - loadstarterkit: function (starterkitName, clean) { - loadStarterKit(starterkitName, clean); - } - }; -}; - -// export these free functions so they're available without calling the exported -// function, for use in reducing code dupe in unit tests. At least, until we -// have a better way to do this -patternlab_engine.build_pattern_data = buildPatternData; -patternlab_engine.process_all_patterns_iterative = processAllPatternsIterative; -patternlab_engine.process_all_patterns_recursive = processAllPatternsRecursive; - -module.exports = patternlab_engine; diff --git a/core/lib/plugin_manager.js b/core/lib/plugin_manager.js deleted file mode 100644 index fdb331a5b..000000000 --- a/core/lib/plugin_manager.js +++ /dev/null @@ -1,79 +0,0 @@ -"use strict"; - -var plugin_manager = function (config, configPath) { - var path = require('path'), - fs = require('fs-extra'), - util = require('./utilities'); - - function loadPlugin(pluginName) { - return require(path.join(process.cwd(), 'node_modules', pluginName)); - } - - function installPlugin(pluginName) { - try { - var pluginPath = path.resolve( - path.join(process.cwd(), 'node_modules', pluginName) - ); - console.log('Attempting to load plugin from', pluginPath); - try { - var pluginDirStats = fs.statSync(pluginPath); - } catch (ex) { - util.logRed(pluginName + ' not found, please use npm to install it first.'); - util.logRed(pluginName + ' not loaded.'); - return; - } - var pluginPathDirExists = pluginDirStats.isDirectory(); - if (pluginPathDirExists) { - - //write config entry back - var diskConfig = fs.readJSONSync(path.resolve(configPath), 'utf8'); - diskConfig[pluginName] = false; - fs.outputFileSync(path.resolve(configPath), JSON.stringify(diskConfig, null, 2)); - - util.logGreen('Plugin ' + pluginName + ' installed.'); - - //todo, tell them how to uninstall or disable - - } - } catch (ex) { - console.log(ex); - } - } - - function detectPlugins() { - var node_modules_path = path.join(process.cwd(), 'node_modules'); - return fs.readdirSync(node_modules_path).filter(function (dir) { - var module_path = path.join(process.cwd(), 'node_modules', dir); - return fs.statSync(module_path).isDirectory() && dir.indexOf('plugin-node-') === 0; - }); - } - - function disablePlugin(pluginName) { - console.log('disablePlugin not implemented yet. No change made to state of plugin', pluginName); - } - - function enablePlugin(pluginName) { - console.log('enablePlugin not implemented yet. No change made to state of plugin', pluginName); - } - - return { - install_plugin: function (pluginName) { - installPlugin(pluginName); - }, - load_plugin: function (pluginName) { - return loadPlugin(pluginName); - }, - detect_plugins: function () { - return detectPlugins(); - }, - disable_plugin: function (pluginName) { - disablePlugin(pluginName); - }, - enable_plugin: function (pluginName) { - enablePlugin(pluginName); - } - }; - -}; - -module.exports = plugin_manager; diff --git a/core/lib/pseudopattern_hunter.js b/core/lib/pseudopattern_hunter.js deleted file mode 100644 index d695b4f0e..000000000 --- a/core/lib/pseudopattern_hunter.js +++ /dev/null @@ -1,81 +0,0 @@ -"use strict"; - -var pseudopattern_hunter = function () { - - function findpseudopatterns(currentPattern, patternlab) { - var glob = require('glob'), - fs = require('fs-extra'), - pa = require('./pattern_assembler'), - lh = require('./lineage_hunter'), - Pattern = require('./object_factory').Pattern, - plutils = require('./utilities'), - path = require('path'); - - - var pattern_assembler = new pa(); - var lineage_hunter = new lh(); - var paths = patternlab.config.paths; - - //look for a pseudo pattern by checking if there is a file containing same - //name, with ~ in it, ending in .json - var needle = currentPattern.subdir + '/' + currentPattern.fileName + '~*.json'; - var pseudoPatterns = glob.sync(needle, { - cwd: paths.source.patterns, - debug: false, - nodir: true - }); - - if (pseudoPatterns.length > 0) { - for (var i = 0; i < pseudoPatterns.length; i++) { - if (patternlab.config.debug) { - console.log('found pseudoPattern variant of ' + currentPattern.patternPartial); - } - - //we want to do everything we normally would here, except instead read the pseudoPattern data - try { - var variantFileData = fs.readJSONSync(path.resolve(paths.source.patterns, pseudoPatterns[i])); - } catch (err) { - console.log('There was an error parsing pseudopattern JSON for ' + currentPattern.relPath); - console.log(err); - } - - //extend any existing data with variant data - variantFileData = plutils.mergeData(currentPattern.jsonFileData, variantFileData); - - var variantName = pseudoPatterns[i].substring(pseudoPatterns[i].indexOf('~') + 1).split('.')[0]; - var variantFilePath = path.join(currentPattern.subdir, currentPattern.fileName + '~' + variantName + '.json'); - var patternVariant = Pattern.create(variantFilePath, variantFileData, { - //use the same template as the non-variant - template: currentPattern.template, - fileExtension: currentPattern.fileExtension, - extendedTemplate: currentPattern.extendedTemplate, - isPseudoPattern: true, - basePattern: currentPattern, - stylePartials: currentPattern.stylePartials, - parameteredPartials: currentPattern.parameteredPartials, - - // use the same template engine as the non-variant - engine: currentPattern.engine - }, patternlab); - - //process the companion markdown file if it exists - pattern_assembler.parse_pattern_markdown(patternVariant, patternlab); - - //find pattern lineage - lineage_hunter.find_lineage(patternVariant, patternlab); - - //add to patternlab object so we can look these up later. - pattern_assembler.addPattern(patternVariant, patternlab); - } - } - } - - return { - find_pseudopatterns: function (pattern, patternlab) { - findpseudopatterns(pattern, patternlab); - } - }; - -}; - -module.exports = pseudopattern_hunter; diff --git a/core/lib/starterkit_manager.js b/core/lib/starterkit_manager.js deleted file mode 100644 index 89ef77ecf..000000000 --- a/core/lib/starterkit_manager.js +++ /dev/null @@ -1,106 +0,0 @@ -"use strict"; - -var starterkit_manager = function (config) { - var path = require('path'), - fetch = require('node-fetch'), - fs = require('fs-extra'), - util = require('./utilities'), - paths = config.paths; - - function loadStarterKit(starterkitName, clean) { - try { - var kitPath = path.resolve( - path.join(process.cwd(), 'node_modules', starterkitName, config.starterkitSubDir) - ); - console.log('Attempting to load starterkit from', kitPath); - try { - var kitDirStats = fs.statSync(kitPath); - } catch (ex) { - util.logRed(starterkitName + ' not found, please use npm to install it first.'); - util.logRed(starterkitName + ' not loaded.'); - return; - } - var kitPathDirExists = kitDirStats.isDirectory(); - if (kitPathDirExists) { - - if (clean) { - console.log('Deleting contents of', paths.source.root, 'prior to starterkit load.'); - util.emptyDirectory(paths.source.root); - } else { - console.log('Overwriting contents of', paths.source.root, 'during starterkit load.'); - } - - fs.copy(kitPath, paths.source.root, function (ex) { - if (ex) { - console.error(ex); - } - util.logGreen('starterkit ' + starterkitName + ' loaded successfully.'); - }); - } - } catch (ex) { - console.log(ex); - } - } - - /** - * @func listStarterkits - * @desc Fetches starterkit repos from GH API that contain 'starterkit' in their name for the user 'pattern-lab' - * @returns {Promise} Returns an Array<{name,url}> for the starterkit repos - */ - function listStarterkits() { - return fetch('https://api.github.com/search/repositories?q=starterkit+in:name+user:pattern-lab&sort=stars&order=desc', { - method: 'GET', - headers: { - 'Accept': 'application/json' - } - }).then(function (res) { - var contentType = res.headers.get('content-type'); - if (contentType && contentType.indexOf('application/json') === -1) { - throw new TypeError("StarterkitManager->listStarterkits: Not valid JSON"); - } - return res.json(); - }).then(function (json) { - if (!json.items || !Array.isArray(json.items)) { - return false; - } - return json.items - .map(function (repo) { - return {name: repo.name, url: repo.html_url}; - }); - }).catch(function (err) { - console.error(err); - return false; - }); - } - - function packStarterkit() { - - } - - function detectStarterKits() { - var node_modules_path = path.join(process.cwd(), 'node_modules'); - var npm_modules = fs.readdirSync(node_modules_path).filter(function (dir) { - var module_path = path.join(process.cwd(), 'node_modules', dir); - return fs.statSync(module_path).isDirectory() && dir.indexOf('starterkit-') === 0; - }); - return npm_modules; - } - - return { - load_starterkit: function (starterkitName, clean) { - loadStarterKit(starterkitName, clean); - }, - list_starterkits: function () { - return listStarterkits(); - }, - pack_starterkit: function () { - packStarterkit(); - }, - detect_starterkits: function () { - return detectStarterKits(); - } - }; - -}; - -module.exports = starterkit_manager; diff --git a/core/lib/ui_builder.js b/core/lib/ui_builder.js deleted file mode 100644 index 4a0e87f0a..000000000 --- a/core/lib/ui_builder.js +++ /dev/null @@ -1,663 +0,0 @@ -"use strict"; - -var path = require('path'); -var JSON5 = require('json5'); -var fs = require('fs-extra'); -var ae = require('./annotation_exporter'); -var of = require('./object_factory'); -var Pattern = of.Pattern; -var pa = require('./pattern_assembler'); -var pattern_assembler = new pa(); -var plutils = require('./utilities'); -var eol = require('os').EOL; -var _ = require('lodash'); - -var ui_builder = function () { - - /** - * Registers the pattern to the patternPaths object for the appropriate patternGroup and basename - * patternGroup + patternBaseName are what comprise the patternPartial (atoms-colors) - * @param patternlab - global data store - * @param pattern - the pattern to add - */ - function addToPatternPaths(patternlab, pattern) { - if (!patternlab.patternPaths[pattern.patternGroup]) { - patternlab.patternPaths[pattern.patternGroup] = {}; - } - - //only add real patterns - if (pattern.isPattern && !pattern.isDocPattern) { - patternlab.patternPaths[pattern.patternGroup][pattern.patternBaseName] = pattern.name; - } - } - - /** - * Registers the pattern with the viewAllPaths object for the appropriate patternGroup and patternSubGroup - * @param patternlab - global data store - * @param pattern - the pattern to add - */ - function addToViewAllPaths(patternlab, pattern) { - if (!patternlab.viewAllPaths[pattern.patternGroup]) { - patternlab.viewAllPaths[pattern.patternGroup] = {}; - } - - if (!patternlab.viewAllPaths[pattern.patternGroup][pattern.patternSubGroup]) { - patternlab.viewAllPaths[pattern.patternGroup][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; - - //add all if it does not exist yet - if (!patternlab.viewAllPaths[pattern.patternGroup].all) { - patternlab.viewAllPaths[pattern.patternGroup].all = pattern.patternType; - } - } - - /** - * Writes a file to disk, with an optional callback - * @param filePath - the path to write to with filename - * @param data - the file contents - * @param callback - an optional callback - */ - function writeFile(filePath, data, callback) { - if (callback) { - fs.outputFileSync(filePath, data, callback); - } else { - fs.outputFileSync(filePath, data); - } - } - - /** - * Returns whether or not the pattern should be excluded from direct rendering or navigation on the front end - * @param pattern - the pattern to test for inclusion/exclusion - * @param patternlab - global data store - * @returns boolean - whether or not the pattern is excluded - */ - function isPatternExcluded(pattern, patternlab) { - var isOmitted; - - // skip underscore-prefixed files - isOmitted = pattern.isPattern && pattern.fileName.charAt(0) === '_'; - if (isOmitted) { - if (patternlab.config.debug) { - console.log('Omitting ' + pattern.patternPartial + " from styleguide patterns because it has an underscore suffix."); - } - return true; - } - - //this is meant to be a homepage that is not present anywhere else - isOmitted = pattern.patternPartial === patternlab.config.defaultPattern; - if (isOmitted) { - if (patternlab.config.debug) { - console.log('Omitting ' + pattern.patternPartial + ' from styleguide patterns because it is defined as a defaultPattern.'); - } - patternlab.defaultPattern = pattern; - return true; - } - - //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('/_') > -1; - if (isOmitted) { - if (patternlab.config.debug) { - console.log('Omitting ' + pattern.patternPartial + ' from styleguide patterns because its contained within an underscored directory.'); - } - return true; - } - - //this pattern is a head or foot pattern - isOmitted = pattern.isMetaPattern; - if (isOmitted) { - if (patternlab.config.debug) { - console.log('Omitting ' + pattern.patternPartial + ' from styleguide patterns because its a meta pattern.'); - } - return true; - } - - //yay, let's include this on the front end - return isOmitted; - } - - /** - * For the given pattern, find or construct the view-all pattern block for the group - * @param pattern - the pattern to derive our documentation pattern from - * @param patternlab - global data store - * @param isSubtypePattern - whether or not this is a subtypePattern or a typePattern (typePatterns not supported yet) - * @returns the found or created pattern object - */ - function injectDocumentationBlock(pattern, patternlab, isSubtypePattern) { - //first see if pattern_assembler processed one already - var docPattern = patternlab.subtypePatterns[pattern.patternGroup + (isSubtypePattern ? '-' + pattern.patternSubGroup : '')]; - if (docPattern) { - docPattern.isDocPattern = true; - return docPattern; - } - - //if not, create one now - docPattern = new Pattern.createEmpty( - { - name: pattern.flatPatternPath, - patternName: isSubtypePattern ? pattern.patternSubGroup : pattern.patternGroup, - patternDesc: '', - patternPartial: 'viewall-' + pattern.patternGroup + (isSubtypePattern ? '-' + pattern.patternSubGroup : ''), - patternSectionSubtype : isSubtypePattern, - patternLink: pattern.flatPatternPath + path.sep + 'index.html', - isPattern: false, - engine: null, - flatPatternPath: pattern.flatPatternPath, - isDocPattern: true - }, - patternlab - ); - return docPattern; - } - - /** - * Registers flat patterns with the patternTypes object - * This is a new menu group like atoms - * @param patternlab - global data store - * @param pattern - the pattern to register - */ - function addPatternType(patternlab, pattern) { - patternlab.patternTypes.push( - { - patternTypeLC: pattern.patternGroup.toLowerCase(), - patternTypeUC: pattern.patternGroup.charAt(0).toUpperCase() + pattern.patternGroup.slice(1), - patternType: pattern.patternType, - patternTypeDash: pattern.patternGroup, //todo verify - patternTypeItems: [] - } - ); - } - - /** - * Return the patternType object for the given pattern. Exits application if not found. - * @param patternlab - global data store - * @param pattern - the pattern to derive the pattern Type from - * @returns the found pattern type object - */ - function getPatternType(patternlab, pattern) { - var patternType = _.find(patternlab.patternTypes, ['patternType', pattern.patternType]); - - if (!patternType) { - plutils.logRed('Could not find patternType' + pattern.patternType + '. This is a critical error.'); - console.trace(); - process.exit(1); - } - - return patternType; - } - - /** - * Return the patternSubType object for the given pattern. Exits application if not found. - * @param patternlab - global data store - * @param pattern - the pattern to derive the pattern subType from - * @returns the found patternSubType object - */ - function getPatternSubType(patternlab, pattern) { - var patternType = getPatternType(patternlab, pattern); - var patternSubType = _.find(patternType.patternTypeItems, ['patternSubtype', pattern.patternSubType]); - - if (!patternSubType) { - plutils.logRed('Could not find patternType ' + pattern.patternType + '-' + pattern.patternType + '. This is a critical error.'); - console.trace(); - process.exit(1); - } - - return patternSubType; - } - - /** - * Registers the pattern with the appropriate patternType.patternTypeItems object - * This is a new menu group like atoms/global - * @param patternlab - global data store - * @param pattern - the pattern to register - */ - function addPatternSubType(patternlab, pattern) { - var patternType = getPatternType(patternlab, pattern); - patternType.patternTypeItems.push( - { - patternSubtypeLC: pattern.patternSubGroup.toLowerCase(), - patternSubtypeUC: pattern.patternSubGroup.charAt(0).toUpperCase() + pattern.patternSubGroup.slice(1), - patternSubtype: pattern.patternSubType, - patternSubtypeDash: pattern.patternSubGroup, //todo verify - patternSubtypeItems: [] - } - ); - } - - /** - * 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}} - */ - function createPatternSubTypeItem(pattern) { - var 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 - }; - } - - /** - * Registers the pattern with the appropriate patternType.patternSubType.patternSubtypeItems array - * These are the actual menu items you click on - * @param patternlab - global data store - * @param pattern - the pattern to derive the subtypeitem from - * @param createViewAllVariant - whether or not to create the special view all item - */ - function addPatternSubTypeItem(patternlab, pattern, createSubtypeViewAllVarient) { - var patternSubType = getPatternSubType(patternlab, pattern); - if (createSubtypeViewAllVarient) { - patternSubType.patternSubtypeItems.push( - { - patternPartial: 'viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup, - patternName: 'View All', - patternPath: encodeURI(pattern.flatPatternPath + '/index.html'), - patternType: pattern.patternType, - patternSubtype: pattern.patternSubtype - } - ); - } - else { - patternSubType.patternSubtypeItems.push( - createPatternSubTypeItem(pattern) - ); - } - } - - /** - * Registers flat patterns to the appropriate type - * @param patternlab - global data store - * @param pattern - the pattern to add - */ - function addPatternItem(patternlab, pattern, isViewAllVariant) { - var patternType = getPatternType(patternlab, pattern); - if (!patternType) { - plutils.logRed('Could not find patternType' + pattern.patternType + '. This is a critical error.'); - console.trace(); - process.exit(1); - } - - if (!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') - }); - } - - } else { - patternType.patternItems.push(createPatternSubTypeItem(pattern)); - } - } - - // function getPatternItems(patternlab, patternType) { - // var patternType = _.find(patternlab.patternTypes, ['patternTypeLC', patternType]); - // if (patternType) { - // return patternType.patternItems; - // } - // return []; - // } - - /** - * Sorts patterns based on name. - * Will be expanded to use explicit order in the near future - * @param patternsArray - patterns to sort - * @returns sorted patterns - */ - function sortPatterns(patternsArray) { - return patternsArray.sort(function (a, b) { - - if (a.name > b.name) { - return 1; - } - if (a.name < b.name) { - return -1; - } - return 0; - }); - } - - /** - * Returns an object representing how the front end styleguide and navigation is structured - * @param patternlab - global data store - * @returns ptterns grouped by type -> subtype like atoms -> global -> pattern, pattern, pattern - */ - function groupPatterns(patternlab) { - var groupedPatterns = { - patternGroups: {} - }; - - _.forEach(sortPatterns(patternlab.patterns), function (pattern) { - - //ignore patterns we can omit from rendering directly - pattern.omitFromStyleguide = isPatternExcluded(pattern, patternlab); - if (pattern.omitFromStyleguide) { return; } - - if (!groupedPatterns.patternGroups[pattern.patternGroup]) { - - 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); - } - - //continue building navigation for nested patterns - if (pattern.patternGroup !== pattern.patternSubGroup) { - - if (!groupedPatterns.patternGroups[pattern.patternGroup][pattern.patternSubGroup]) { - - addPatternSubType(patternlab, pattern); - - pattern.isSubtypePattern = !pattern.isPattern; - groupedPatterns.patternGroups[pattern.patternGroup][pattern.patternSubGroup] = {}; - groupedPatterns.patternGroups[pattern.patternGroup][pattern.patternSubGroup]['viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup] = injectDocumentationBlock(pattern, patternlab, true); - - addToViewAllPaths(patternlab, pattern); - addPatternSubTypeItem(patternlab, pattern, true); - - } - - groupedPatterns.patternGroups[pattern.patternGroup][pattern.patternSubGroup][pattern.patternBaseName] = pattern; - - addToPatternPaths(patternlab, pattern); - addPatternSubTypeItem(patternlab, pattern); - } else { - addPatternItem(patternlab, pattern); - addToPatternPaths(patternlab, pattern); - } - - }); - - return groupedPatterns; - } - - /** - * Builds footer HTML from the general footer and user-defined footer - * @param patternlab - global data store - * @param patternPartial - the partial key to build this for, either viewall-patternPartial or a viewall-patternType-all - * @returns HTML - */ - function buildFooterHTML(patternlab, patternPartial) { - //first render the general footer - var footerPartial = pattern_assembler.renderPattern(patternlab.footer, { - patternData: JSON.stringify({ - patternPartial: patternPartial, - }), - cacheBuster: patternlab.cacheBuster - }); - - var allFooterData; - try { - allFooterData = JSON5.parse(JSON5.stringify(patternlab.data)); - } catch (err) { - console.log('There was an error parsing JSON for patternlab.data'); - console.log(err); - } - allFooterData.patternLabFoot = footerPartial; - - //then add it to the user footer - var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, allFooterData); - return footerHTML; - } - - /** - * Takes a set of patterns and builds a viewall HTML page for them - * Used by the type and subtype viewall sets - * @param patternlab - global data store - * @param patterns - the set of patterns to build the viewall page for - * @param patternPartial - a key used to identify the viewall page - * @returns HTML - */ - function buildViewAllHTML(patternlab, patterns, patternPartial) { - var viewAllHTML = pattern_assembler.renderPattern(patternlab.viewAll, - { - partials: patterns, - patternPartial: 'viewall-' + patternPartial, - cacheBuster: patternlab.cacheBuster - }, { - patternSection: patternlab.patternSection, - patternSectionSubtype: patternlab.patternSectionSubType - }); - return viewAllHTML; - } - - /** - * Constructs viewall pages for each set of grouped patterns - * @param mainPageHeadHtml - the already built main page HTML - * @param patternlab - global data store - * @param styleguidePatterns - the grouped set of patterns - * @returns every built pattern and set of viewall patterns, so the styleguide can use it - */ - function buildViewAllPages(mainPageHeadHtml, patternlab, styleguidePatterns) { - var paths = patternlab.config.paths; - var patterns = []; - var writeViewAllFile = true; - - //loop through the grouped styleguide patterns, building at each level - _.forEach(styleguidePatterns.patternGroups, function (patternTypeObj, patternType) { - - var p; - var typePatterns = []; - var styleGuideExcludes = patternlab.config.styleGuideExcludes; - - _.forOwn(patternTypeObj, function (patternSubtypes, patternSubtype) { - - var patternPartial = patternType + '-' + patternSubtype; - - //do not create a viewall page for flat patterns - if (patternType === patternSubtype) { - writeViewAllFile = false; - return false; - } - - //render the footer needed for the viewall template - var footerHTML = buildFooterHTML(patternlab, 'viewall-' + patternPartial); - - //render the viewall template - var subtypePatterns = _.values(patternSubtypes); - - //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; - }); - - typePatterns = typePatterns.concat(subtypePatterns); - - var viewAllHTML = buildViewAllHTML(patternlab, subtypePatterns, patternPartial); - writeFile(paths.public.patterns + p.flatPatternPath + '/index.html', mainPageHeadHtml + viewAllHTML + footerHTML); - return true; //stop yelling at us eslint we know we know - }); - - //do not create a viewall page for flat patterns - if (!writeViewAllFile || !p) { - return false; - } - - //render the footer needed for the viewall template - var footerHTML = buildFooterHTML(patternlab, 'viewall-' + patternType + '-all'); - - //add any flat patterns - //todo this isn't quite working yet - //typePatterns = typePatterns.concat(getPatternItems(patternlab, patternType)); - - //get the appropriate patternType - var anyPatternOfType = _.find(typePatterns, function (pat) { - return pat.patternType && pat.patternType !== '';}); - - //render the viewall template for the type - var viewAllHTML = buildViewAllHTML(patternlab, typePatterns, patternType); - writeFile(paths.public.patterns + anyPatternOfType.patternType + '/index.html', mainPageHeadHtml + viewAllHTML + footerHTML); - - //determine if we should omit this patterntype completely from the viewall page - var omitPatternType = styleGuideExcludes && styleGuideExcludes.length - && _.some(styleGuideExcludes, function (exclude) { - return exclude === patternType; - }); - if (omitPatternType) { - if (patternlab.config.debug) { - console.log('Omitting ' + patternType + ' from building a viewall page because its patternGroup is specified in styleguideExcludes.'); - } - } else { - patterns = patterns.concat(typePatterns); - } - - return true; //stop yelling at us eslint we know we know - }); - return patterns; - } - - /** - * Write out our pattern information for use by the front end - * @param patternlab - global data store - */ - function exportData(patternlab) { - var annotation_exporter = new ae(patternlab); - var paths = patternlab.config.paths; - - //write out the data - var output = ''; - - //config - output += 'var config = ' + JSON.stringify(patternlab.config) + ';\n'; - - //ishControls - output += 'var ishControls = {"ishControlsHide":' + JSON.stringify(patternlab.config.ishControlsHide) + '};' + eol; - - //navItems - output += 'var navItems = {"patternTypes": ' + JSON.stringify(patternlab.patternTypes) + '};' + eol; - - //patternPaths - output += 'var patternPaths = ' + JSON.stringify(patternlab.patternPaths) + ';' + eol; - - //viewAllPaths - output += 'var viewAllPaths = ' + JSON.stringify(patternlab.viewAllPaths) + ';' + eol; - - //plugins - output += 'var plugins = ' + JSON.stringify(patternlab.plugins) + ';' + eol; - - //smaller config elements - output += 'var defaultShowPatternInfo = ' + (patternlab.config.defaultShowPatternInfo ? patternlab.config.defaultShowPatternInfo : 'false') + ';' + eol; - output += 'var defaultPattern = "' + (patternlab.config.defaultPattern ? patternlab.config.defaultPattern : 'all') + '";' + eol; - - //write all output to patternlab-data - writeFile(path.resolve(paths.public.data, 'patternlab-data.js'), output); - - //annotations - var annotationsJSON = annotation_exporter.gather(); - var annotations = 'var comments = { "comments" : ' + JSON.stringify(annotationsJSON) + '};'; - writeFile(path.resolve(paths.public.annotations, 'annotations.js'), annotations); - } - - /** - * Reset any global data we use between builds to guard against double adding things - */ - function resetUIBuilderState(patternlab) { - patternlab.patternPaths = {}; - patternlab.viewAllPaths = {}; - patternlab.patternTypes = []; - } - - /** - * The main entry point for ui_builder - * @param patternlab - global data store - */ - function buildFrontend(patternlab) { - - resetUIBuilderState(patternlab); - - var paths = patternlab.config.paths; - - //determine which patterns should be included in the front-end rendering - var styleguidePatterns = groupPatterns(patternlab); - - //set the pattern-specific header by compiling the general-header with data, and then adding it to the meta header - var headerPartial = pattern_assembler.renderPattern(patternlab.header, { - cacheBuster: patternlab.cacheBuster - }); - - var headFootData = patternlab.data; - headFootData.patternLabHead = headerPartial; - headFootData.cacheBuster = patternlab.cacheBuster; - var headerHTML = pattern_assembler.renderPattern(patternlab.userHead, headFootData); - - //set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer - var footerPartial = pattern_assembler.renderPattern(patternlab.footer, { - patternData: '{}', - cacheBuster: patternlab.cacheBuster - }); - headFootData.patternLabFoot = footerPartial; - var footerHTML = pattern_assembler.renderPattern(patternlab.userFoot, headFootData); - - //build the viewall pages - var allPatterns = buildViewAllPages(headerHTML, patternlab, styleguidePatterns); - - //add the defaultPattern if we found one - if (patternlab.defaultPattern) { - allPatterns.push(patternlab.defaultPattern); - addToPatternPaths(patternlab, patternlab.defaultPattern); - } - - //build the main styleguide page - var styleguideHtml = pattern_assembler.renderPattern(patternlab.viewAll, - { - partials: allPatterns - }, { - patternSection: patternlab.patternSection, - patternSectionSubtype: patternlab.patternSectionSubType - }); - writeFile(path.resolve(paths.public.styleguide, 'html/styleguide.html'), headerHTML + styleguideHtml + footerHTML); - - //move the index file from its asset location into public root - var patternlabSiteHtml; - try { - patternlabSiteHtml = fs.readFileSync(path.resolve(paths.source.styleguide, 'index.html'), 'utf8'); - } catch (error) { - console.log(error); - console.log("\nERROR: Could not load one or more styleguidekit assets from", paths.source.styleguide, '\n'); - process.exit(1); - } - writeFile(path.resolve(paths.public.root, 'index.html'), patternlabSiteHtml); - - //write out patternlab.data object to be read by the client - exportData(patternlab); - } - - return { - buildFrontend: function (patternlab) { - buildFrontend(patternlab); - }, - isPatternExcluded: function (pattern, patternlab) { - return isPatternExcluded(pattern, patternlab); - }, - groupPatterns: function (patternlab) { - return groupPatterns(patternlab); - }, - resetUIBuilderState: function (patternlab) { - resetUIBuilderState(patternlab); - } - }; - -}; - -module.exports = ui_builder; diff --git a/core/lib/utilities.js b/core/lib/utilities.js deleted file mode 100644 index d05eec876..000000000 --- a/core/lib/utilities.js +++ /dev/null @@ -1,93 +0,0 @@ -"use strict"; - -var fs = require('fs-extra'), - path = require('path'); - -var util = { - // http://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array-in-javascript - shuffle: function (o) { - /*eslint-disable curly*/ - for (var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); - return o; - }, - - logGreen: function (message) { - console.log('\x1b[32m', message, '\x1b[0m'); - }, - - logOrange: function (message) { - console.log('\x1b[33m', message, '\x1b[0m'); - }, - - logRed: function (message) { - console.log('\x1b[41m', message, '\x1b[0m'); - }, - - /** - * Recursively merge properties of two objects. - * - * @param {Object} obj1 If obj1 has properties obj2 doesn't, add to obj2. - * @param {Object} obj2 This object's properties have priority over obj1. - * @returns {Object} obj2 - */ - mergeData: function (obj1, obj2) { - /*eslint-disable no-param-reassign, guard-for-in*/ - if (typeof obj2 === 'undefined') { - obj2 = {}; - } - for (var p in obj1) { - try { - // Only recurse if obj1[p] is an object. - if (obj1[p].constructor === Object) { - // Requires 2 objects as params; create obj2[p] if undefined. - if (typeof obj2[p] === 'undefined') { - obj2[p] = {}; - } - obj2[p] = util.mergeData(obj1[p], obj2[p]); - - // Pop when recursion meets a non-object. If obj1[p] is a non-object, - // only copy to undefined obj2[p]. This way, obj2 maintains priority. - } else if (typeof obj2[p] === 'undefined') { - obj2[p] = obj1[p]; - } - } catch (e) { - // Property in destination object not set; create it and set its value. - if (typeof obj2[p] === 'undefined') { - obj2[p] = obj1[p]; - } - } - } - return obj2; - }, - - isObjectEmpty: function (obj) { - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) { return false; } - } - return true; - }, - - // recursively delete the contents of directory - // adapted from https://gist.github.com/tkihira/2367067 - emptyDirectory: function (dir, cleanDir) { - var list = fs.readdirSync(dir); - for (var i = 0; i < list.length; i++) { - var filename = path.join(dir, list[i]); - var stat = fs.statSync(filename); - - if (filename === "." || filename === "..") { - // pass these files - } else if (stat.isDirectory()) { - this.emptyDirectory(filename); - } else { - // rm fiilename - fs.unlinkSync(filename); - } - } - if (cleanDir) { - fs.rmdirSync(dir); - } - } -}; - -module.exports = util; diff --git a/core/scripts/postinstall.js b/core/scripts/postinstall.js deleted file mode 100644 index 53636e1d0..000000000 --- a/core/scripts/postinstall.js +++ /dev/null @@ -1,47 +0,0 @@ -"use strict"; -try { - console.log('Beginning Pattern Lab postinstall...'); - - var path = require('path'); - var fs = require('fs-extra'); - var smPath = path.resolve(__dirname, '..', 'lib/starterkit_manager.js'); - var pmPath = path.resolve(__dirname, '..', 'lib/plugin_manager.js'); - var uPath = path.resolve(__dirname, '..', 'lib/utilities.js'); - var sm = require(smPath); - var pm = require(pmPath); - var u = require(uPath); - - //get the config - var configPath = path.resolve(process.cwd(), 'patternlab-config.json'); - var config = fs.readJSONSync(path.resolve(configPath), 'utf8'); - - //determine if any starterkits are already installed - var starterkit_manager = new sm(config); - var foundStarterkits = starterkit_manager.detect_starterkits(); - - //todo - enhance to support multiple kits with prompt for each or all - if (foundStarterkits && foundStarterkits.length > 0) { - starterkit_manager.load_starterkit(foundStarterkits[0], true); - } else { - console.log('No starterkits found to automatically load.'); - } - - //determine if any plugins are already installed - var plugin_manager = new pm(config, configPath); - var foundPlugins = plugin_manager.detect_plugins(); - - if (foundPlugins && foundPlugins.length > 0) { - - for (var i = 0; i < foundPlugins.length; i++) { - console.log('Found plugin', foundPlugins[i]); - plugin_manager.install_plugin(foundPlugins[i]); - } - } - - u.logGreen('Pattern Lab postinstall complete.'); - -} catch (ex) { - console.log(ex); - u.logOrange('An error occurred during Pattern Lab Node postinstall.'); - u.logOrange('Pattern Lab postinstall completed with errors.'); -} diff --git a/lerna.json b/lerna.json new file mode 100644 index 000000000..84951f7f0 --- /dev/null +++ b/lerna.json @@ -0,0 +1,38 @@ +{ + "lerna": "3.11.0", + "version": "5.9.3", + "packages": [ + "packages/*" + ], + "command": { + "init": { + "exact": true + }, + "publish": { + "allowBranch": [ + "master", + "dev" + ], + "conventionalCommits": true, + "gitReset": true, + "includeMergedTags": true, + "noCommitHooks": true, + "verifyAccess": true + }, + "changed": { + "includeMergedTags": true + } + }, + "ignoreChanges": [ + "**/__fixtures__/**", + "**/__tests__/**", + "**/*.md", + "**/__snapshots__/**" + ], + "npmClient": "yarn", + "npmClientArgs": [ + "--ignore-optional", + "--registry=https://registry.npmjs.org/" + ], + "useWorkspaces": true +} diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 000000000..94337a4da --- /dev/null +++ b/netlify.toml @@ -0,0 +1,2 @@ +[context.deploy-preview] + command = "yarn setup && yarn preview:hbs && yarn preview:docs" diff --git a/package.json b/package.json index ab66fa4a4..d2c271c91 100644 --- a/package.json +++ b/package.json @@ -1,52 +1,53 @@ { - "name": "patternlab-node", - "description": "Pattern Lab is a collection of tools to help you create atomic design systems. This is the node command line interface (CLI).", - "version": "2.6.0-alpha", - "main": "./core/lib/patternlab.js", - "dependencies": { - "diveSync": "^0.3.0", - "fs-extra": "^0.30.0", - "glob": "^7.0.0", - "js-beautify": "^1.6.3", - "js-yaml": "^3.6.1", - "json5": "^0.5.0", - "lodash": "~4.13.1", - "markdown-it": "^6.0.1", - "node-fetch": "^1.6.0", - "patternengine-node-mustache": "^1.0.0" + "workspaces": { + "packages": [ + "packages/*" + ], + "nohoist": [ + "@pattern-lab/engine-*", + "**/@pattern-lab/engine-*", + "**/@pattern-lab/uikit-workshop" + ] }, - "devDependencies": { - "grunt": "~1.0.1", - "grunt-contrib-concat": "^1.0.1", - "grunt-contrib-nodeunit": "^1.0.0", - "grunt-eslint": "^18.0.0" + "dependencies": { + "@babel/plugin-proposal-decorators": "^7.4.4", + "@babel/plugin-syntax-jsx": "^7.2.0", + "babel-eslint": "^10.0.2", + "eslint": "^6.1.0", + "eslint-config-airbnb-base": "^14.0.0", + "eslint-config-prettier": "^6.0.0", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-prettier": "^3.1.0", + "prettier": "^1.14.3", + "lerna": "3.17.0", + "pretty-quick": "^1.11.1", + "auto": "^7.8.0" }, - "keywords": [ - "Pattern Lab", - "Atomic Web Design", - "Node", - "Grunt", - "Gulp", - "Javascript" - ], "repository": { "type": "git", - "url": "git://github.com/pattern-lab/patternlab-node.git" - }, - "bugs": "https://github.com/pattern-lab/patternlab-node/issues", - "author": { - "name": "Brian Muenzenmeyer" + "url": "git+https://github.com/pattern-lab/patternlab-node.git" }, - "contributors": [ - { - "name": "Geoff Pursell" - } - ], - "license": "MIT", + "private": true, "scripts": { - "test": "grunt travis --verbose" + "postinstall": "lerna run postbootstrap", + "setup": "yarn", + "build:uikit": "cd packages/uikit-workshop && npm run build", + "precommit": "pretty-quick --staged", + "lint:fix": "npm run lint -- --fix", + "lint": "eslint --max-warnings 0 './packages/{core,cli,uikit-workshop}/**/*.js ' --ignore-path .eslintignore", + "test": "lerna run test", + "clean": "git clean -dfx", + "publish": "npx lerna publish -m \"[skip travis] chore(release): publish %s\"", + "postpublish": "auto release", + "preview:docs": "cd packages/docs && yarn production", + "preview:hbs": "cd packages/development-edition-engine-handlebars && npx patternlab add --starterkits @pattern-lab/starterkit-handlebars-vanilla && npm run pl:build" }, - "engines": { - "node": ">=4.0" + "nyc": { + "exclude": [ + "**/*_tests.js", + "packages/cli", + "packages/core/test", + "packages/live-server" + ] } } diff --git a/packages/cli/.gitattributes b/packages/cli/.gitattributes new file mode 100644 index 000000000..141034634 --- /dev/null +++ b/packages/cli/.gitattributes @@ -0,0 +1,3 @@ +* text=auto +bin/patternlab.js lf +readme.md merge=union diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore new file mode 100644 index 000000000..55a89459b --- /dev/null +++ b/packages/cli/.gitignore @@ -0,0 +1,81 @@ +# Logs +logs +*.log +npm-debug.log* + +# Runtime data +pids +*.pid +*.seed + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# macOS specific +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Pattern Lab CLI specific + +.DS_Store +latest-change.txt +patternlab.json +.sass-cache/* +/sass-cache +Thumbs.db +source/css/style.css.map +.idea/ +public +tmp diff --git a/packages/cli/.npmrc b/packages/cli/.npmrc new file mode 100644 index 000000000..ce73f58bf --- /dev/null +++ b/packages/cli/.npmrc @@ -0,0 +1,2 @@ +package-lock=false +save-exact=true diff --git a/packages/cli/.nvmrc b/packages/cli/.nvmrc new file mode 100644 index 000000000..a13e7b9c8 --- /dev/null +++ b/packages/cli/.nvmrc @@ -0,0 +1 @@ +10.0.0 diff --git a/packages/cli/.travis.yml b/packages/cli/.travis.yml new file mode 100644 index 000000000..1f0e15052 --- /dev/null +++ b/packages/cli/.travis.yml @@ -0,0 +1,11 @@ +language: node_js +node_js: + - node + - 8 +before_script: + - npm install edition-node + - npm install starterkit-mustache-base + - npm install eslint +branches: + only: + - master diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md new file mode 100644 index 000000000..3f84d02ec --- /dev/null +++ b/packages/cli/CHANGELOG.md @@ -0,0 +1,293 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [5.9.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.9.2...v5.9.3) (2020-05-01) + + +### Bug Fixes + +* **cli:** fix test script glob ([ff18eb5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/ff18eb51ce24fc5423b009168e85ede366069139)) + + + + + + +## [5.9.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.9.0...v5.9.1) (2020-04-24) + + +### Bug Fixes + +* **cli:** ensure specified directory exists prior to scaffold ([cc3b696](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/cc3b69624d486c94ee3b1f4b1bbb0334a514fa59)) + + + + + +# [5.9.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.8.0...v5.9.0) (2020-04-24) + + +### Bug Fixes + +* **cli:** set current working directory before scaffolded npm init ([6d2186d](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/6d2186d8e8a74634198a4474ca8ae83221dd70a9)) + + + + + +# [5.7.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.6.0...v5.7.0) (2020-02-17) + + +### Features + +* **cli:** make options more user friendly ([ad845b3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/ad845b394ef81f90895ebb5bc6f12cc608e5e3d4)) + + + + + + +# [5.4.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.3.3...v5.4.0) (2019-11-26) + +**Note:** Version bump only for package @pattern-lab/cli + + + + + +# [5.3.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.2.0...v5.3.0) (2019-11-13) + +**Note:** Version bump only for package @pattern-lab/cli + + + + + +# [5.1.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v5.0.2...v5.1.0) (2019-10-29) + +**Note:** Version bump only for package @pattern-lab/cli + + + + + +# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25) + + +### Bug Fixes + +* updates to fix eslint / prettier issues; update packages/core to reuse root .eslintrc.js file ([5b7a057](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/5b7a057d46ccd16b5832af1441030c7b76f237a8)) +* **cli:** add custom install logic to edition-node ([f04fd26](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/f04fd266429cd806987dab747e6d69bff9b926a4)) +* **cli:** allow any package to be installed as a starterkit ([d2aa1be](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/d2aa1be810a0a7473dcc52391a2263dacfdda0b8)), closes [#1067](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/issues/1067) +* **cli:** merge config arrays via overwrite instead of concatenate ([42e5f7b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/42e5f7b42a26b4fc1f262c68ee4b474b546f2eac)) +* **cli:** proper path resolution to helpers ([a18fe5e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/a18fe5ef4d1c074a5eba8bfa255ebbee2261bf74)) +* **cli:** re-order and clarify engines ([e39e301](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/e39e301a33306c6615fabf64262f1893ca682b97)) +* **plugin:** correct spelling error and function locations ([d4abd88](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/d4abd88cb017550002407241b5045a2ad1adb1dc)) + + +### Features + +* **starterkits:** add starterkit-handlebars-demo ([384d2cf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/384d2cfa3440c1e6f456d39f56ca6381f82f7689)) + + +### BREAKING CHANGES + +* **cli:** previously, we concatenated arrays, which is unlikely to be intended + + + + + + +## [1.0.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@1.0.2...@pattern-lab/cli@1.0.3) (2019-10-14) + +**Note:** Version bump only for package @pattern-lab/cli + + + + + + +# [1.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.1.0...@pattern-lab/cli@1.0.0) (2019-08-23) + + +### Bug Fixes + +* **cli:** merge config arrays via overwrite instead of concatenate ([42e5f7b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/42e5f7b)) +* **cli:** proper path resolution to helpers ([a18fe5e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/a18fe5e)) + + +### BREAKING CHANGES + +* **cli:** previously, we concatenated arrays, which is unlikely to be intended + + + + + +# [0.1.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.3...@pattern-lab/cli@0.1.0) (2019-08-23) + + +### Bug Fixes + +* updates to fix eslint / prettier issues; update packages/core to reuse root .eslintrc.js file ([5b7a057](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/5b7a057)) +* **cli:** add custom install logic to edition-node ([f04fd26](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/f04fd26)) +* **cli:** re-order and clarify engines ([e39e301](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/e39e301)) + + +### Features + +* **starterkits:** add starterkit-handlebars-demo ([384d2cf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/384d2cf)) + + + + + + +## [0.0.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.3-alpha.0...@pattern-lab/cli@0.0.3) (2019-05-16) + +**Note:** Version bump only for package @pattern-lab/cli + + + + + +## [0.0.1-beta.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-beta.0...@pattern-lab/cli@0.0.1-beta.2) (2019-02-09) + + +### Bug Fixes + +* **cli:** do not call build before serve ([663d8e1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/663d8e1)), closes [#917](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/issues/917) +* **cli:** pass watch options cleanly to core ([8bf186b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/8bf186b)) +* **cli:** remove copy-source-files ([64311a1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/64311a1)), closes [#833](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/issues/833) +* **nvmrc:** bump Node version ([36a917f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/36a917f)) +* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/98dfadf)) + + +### Features + +* **README:** simplify README and add CLI configuration instructions ([ceec673](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/ceec673)) + + + + + + +## [0.0.1-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.23...@pattern-lab/cli@0.0.1-beta.0) (2018-09-07) + + +### Bug Fixes + +* **cli:** set initialized to false during plugin installation ([88cce3f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/88cce3f)) +* **cli:** support scoped plugins ([4ae13ce](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/4ae13ce)) +* **package:** update tap dependency ([2b70ff4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/2b70ff4)) + + + + + + + +## [0.0.1-alpha.23](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.22...@pattern-lab/cli@0.0.1-alpha.23) (2018-07-09) + +### Bug Fixes + +* **install:** copy dependencies ([1acef87](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/1acef87)) + + + +## [0.0.1-alpha.22](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.21...@pattern-lab/cli@0.0.1-alpha.22) (2018-07-06) + +**Note:** Version bump only for package @pattern-lab/cli + + + +## [0.0.1-alpha.21](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.20...@pattern-lab/cli@0.0.1-alpha.21) (2018-07-06) + +### Bug Fixes + +* **install:** add break statements to install edition command ([3b1813c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/3b1813c)) +* **install:** use process to find package.json ([200c7cb](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/200c7cb)) + +### Features + +* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/55f5bc2)) +* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/415698e)) +* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/5ab3995)) + + + +## [0.0.1-alpha.20](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.19...@pattern-lab/cli@0.0.1-alpha.20) (2018-07-05) + +### Bug Fixes + +* **cli:** change whitespace to spaces per standard ([4556fc7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/4556fc7)) +* **tests:** change test command name similar to live-server until this passes CI ([5c39be1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/5c39be1)) + +### Features + +* **serve:** change calling method ([3b86a0d](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/3b86a0d)) +* **tests:** use lerna run test at the monorepo level ([38a01b1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/38a01b1)) + + + +## [0.0.1-alpha.19](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.18...@pattern-lab/cli@0.0.1-alpha.19) (2018-05-19) + +### Bug Fixes + +* **cli:** change line-endings of cli entrypoint ([3fc86c2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/3fc86c2)) +* **wording:** reconcile Pattern Lab vs PatternLab ([f3d1e0d](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/f3d1e0d)) + + + +## [0.0.1-alpha.18](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.17...@pattern-lab/cli@0.0.1-alpha.18) (2018-05-04) + +### Bug Fixes + +* **version:** use static core method getVersion ([f9dcd4d](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/f9dcd4d)) + + + +## [0.0.1-alpha.17](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.16...@pattern-lab/cli@0.0.1-alpha.17) (2018-05-04) + +### Bug Fixes + +* **package:** update publish config and installation target ([27d2c8f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/27d2c8f)) + + + +## [0.0.1-alpha.16](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.15...@pattern-lab/cli@0.0.1-alpha.16) (2018-05-04) + +### Features + +* **API:** standardize v() and version() into a single call ([6309e69](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/6309e69)) + +### BREAKING CHANGES + +* **API:** change `version()` to return a string representation of the version, removing `v()` + + + +## [0.0.1-alpha.15](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.14...@pattern-lab/cli@0.0.1-alpha.15) (2018-03-21) + +### Features + +* **package:** standardize and hoist common devDependencies ([7f4ce6f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/7f4ce6f)) + + + +## [0.0.1-alpha.14](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/compare/@pattern-lab/cli@0.0.1-alpha.13...@pattern-lab/cli@0.0.1-alpha.14) (2018-03-05) + +### Bug Fixes + +* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/1473cd5)) + + + +## 0.0.1-alpha.13 (2018-03-02) + +### Features + +* **cli:** Rename package ([9ea40d4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli/commit/9ea40d4)) diff --git a/packages/cli/bin/archive.js b/packages/cli/bin/archive.js new file mode 100644 index 000000000..9d7a6989c --- /dev/null +++ b/packages/cli/bin/archive.js @@ -0,0 +1,53 @@ +'use strict'; +const fs = require('fs'); +const path = require('path'); +const Archiver = require('archiver'); +const isValidConfig = require('./validate-config'); +const debug = require('./utils').debug; + +/** + * @func exportPatterns + * @desc Exports the patterns into the patternExportDirectory. + * @param {object} config - The passed Pattern Lab config. + */ +function exportPatterns(config) { + if (!isValidConfig) { + throw new TypeError( + 'export: Expects config not to be empty OR of type object if not empty.' + ); + } + + const archive = new Archiver('zip', {}); + const exportsPath = path.resolve( + './', + config.patternExportDirectory, + 'patterns.zip' + ); + const output = fs.createWriteStream(exportsPath); + + output.on('close', () => { + debug( + `export: Exported patterns in ${exportsPath} - ${archive.pointer()} total bytes.` + ); + }); + + archive.on('error', function(err) { + throw new TypeError( + `export: An error occured during zipping the patterns: ${err}` + ); + }); + + archive.pipe(output); + + archive + .glob( + '?(_patterns|_data|_meta|_annotations)/**', + { + cwd: config.paths.source.root, + }, + {} + ) + .finalize(); +} + +module.exports = exportPatterns; diff --git a/packages/cli/bin/ask.js b/packages/cli/bin/ask.js new file mode 100644 index 000000000..55a337230 --- /dev/null +++ b/packages/cli/bin/ask.js @@ -0,0 +1,49 @@ +'use strict'; +const inquirer = require('inquirer'); +const wrapsAsync = require('./utils').wrapAsync; +const confirmSetup = require('./inquiries/confirm'); +const editionSetup = require('./inquiries/edition'); +const starterkitSetup = require('./inquiries/starterkit'); +const ask = inquirer.prompt; + +/** + * @func init + * @desc Initiates a Pattern Lab project by getting user input through inquiry. Scaffolds the project and download mandatory files + * @param {object} options - Options passed in from CLI + * @param {boolean} options.force - Flag whether to force install in existing project directory. May overwrite stuff. + */ +const init = options => + wrapsAsync(function*() { + /** + * @property {string} project_root="./" - Path to the project root directory + * @property {string|Symbol} edition - The name of the edition npm package or a Symbol for no install + */ + const editionAnswers = yield ask(editionSetup); + + /** + * @property {object|Symbol} starterkit - The name of a starterkit npm package or a Symbol for no install + */ + const starterkitAnswers = yield ask(starterkitSetup); + + /** + * @property {boolean} confirm - A bool hold the confirmation status + */ + const confirmation = yield ask(confirmSetup); + + // IF we have no confirmation we start all over again. + if (!confirmation.confirm) { + return init(options); + } + + return { + // Destructure the answers + projectDir: editionAnswers.project_root, + edition: editionAnswers.edition !== false ? editionAnswers.edition : '', + starterkit: + starterkitAnswers.starterkit !== false + ? starterkitAnswers.starterkit + : '', + }; + }); + +module.exports = init; diff --git a/packages/cli/bin/build.js b/packages/cli/bin/build.js new file mode 100644 index 000000000..51108a33d --- /dev/null +++ b/packages/cli/bin/build.js @@ -0,0 +1,38 @@ +'use strict'; +const pl = require('@pattern-lab/core'); +const { debug } = require('./utils'); +const isValidConfig = require('./validate-config'); + +/** + * @func build + * @desc Init patternLab core and build the Pattern Lab files. + * @param {object} config - The passed Pattern Lab config. + * @param {object} options - Additional opts to specify build mode. + */ +function build(config, options) { + if (!isValidConfig) { + throw new TypeError( + 'build: Expects config not to be empty and of type object.' + ); + } + + // Initiate Pattern Lab core with the config + const patternLab = pl(config); + + /** + * Check whether a flag was passed for build + * 1. Build only patterns + * 2. Normal build + */ + if (options && options.patternsOnly) { + // 1 + debug(`build: Building only patterns now into ${config.paths.public.root}`); + return patternLab.patternsonly(config.cleanPublic); + } else { + // 2 + debug(`build: Building your project now into ${config.paths.public.root}`); + return patternLab.build(config.cleanPublic); + } +} + +module.exports = build; diff --git a/packages/cli/bin/cli-actions/build.js b/packages/cli/bin/cli-actions/build.js new file mode 100644 index 000000000..c6a34434a --- /dev/null +++ b/packages/cli/bin/cli-actions/build.js @@ -0,0 +1,17 @@ +'use strict'; +const buildPatterns = require('../build'); +const resolveConfig = require('../resolve-config'); +const { error, info, wrapAsync } = require('../utils'); + +const build = options => + wrapAsync(function*() { + try { + const config = yield resolveConfig(options.parent.config); + yield buildPatterns(config, options); + info(`build: Yay, your Pattern Lab project was successfully built ☺`); + } catch (err) { + error(err); + } + }); + +module.exports = build; diff --git a/packages/cli/bin/cli-actions/disable.js b/packages/cli/bin/cli-actions/disable.js new file mode 100644 index 000000000..6c53cb5a2 --- /dev/null +++ b/packages/cli/bin/cli-actions/disable.js @@ -0,0 +1,42 @@ +'use strict'; +const ora = require('ora'); +const _ = require('lodash'); +const resolveConfig = require('../resolve-config'); +const wrapAsync = require('../utils').wrapAsync; +const writeJsonAsync = require('../utils').writeJsonAsync; + +/** + * disable + * @desc Handles deactivation of starterkits/plugins + * @param {object} options + */ +const enable = options => + wrapAsync(function*() { + const { + parent: { config: configPath }, + plugins, + } = options; + const config = yield resolveConfig(configPath); + + const spinner = ora(`⊙ patternlab → Disable …`).start(); + + if (plugins && Array.isArray(plugins)) { + spinner.succeed( + `⊙ patternlab → Disable following plugins: ${plugins.join(', ')}` + ); + plugins.map(plugin => { + if (_.has(config, `plugins[${plugin}]`)) { + _.set(config, `plugins[${plugin}]['enabled']`, false); + spinner.succeed( + `⊙ patternlab → Disabled following plugin: ${plugin}` + ); + } else { + spinner.warn(`⊙ patternlab → Couldn't find plugin: ${plugin}`); + } + }); + } + yield writeJsonAsync(options.parent.config, config); + spinner.succeed(`⊙ patternlab → Updated config`); + }); + +module.exports = enable; diff --git a/packages/cli/bin/cli-actions/enable.js b/packages/cli/bin/cli-actions/enable.js new file mode 100644 index 000000000..0edb57401 --- /dev/null +++ b/packages/cli/bin/cli-actions/enable.js @@ -0,0 +1,40 @@ +'use strict'; +const ora = require('ora'); +const _ = require('lodash'); +const resolveConfig = require('../resolve-config'); +const wrapAsync = require('../utils').wrapAsync; +const writeJsonAsync = require('../utils').writeJsonAsync; + +/** + * enable + * @desc Handles activation of starterkits/plugins + * @param {object} options + */ +const enable = options => + wrapAsync(function*() { + const { + parent: { config: configPath }, + plugins, + } = options; + const config = yield resolveConfig(configPath); + + const spinner = ora(`⊙ patternlab → Enable …`).start(); + + if (plugins && Array.isArray(plugins)) { + spinner.succeed( + `⊙ patternlab → Enable following plugins: ${plugins.join(', ')}` + ); + plugins.map(plugin => { + if (_.has(config, `plugins[${plugin}]`)) { + _.set(config, `plugins[${plugin}]['enabled']`, true); + spinner.succeed(`⊙ patternlab → Enabled following plugin: ${plugin}`); + } else { + spinner.warn(`⊙ patternlab → Couldn't find plugin: ${plugin}`); + } + }); + } + yield writeJsonAsync(options.parent.config, config); + spinner.succeed(`⊙ patternlab → Updated config`); + }); + +module.exports = enable; diff --git a/packages/cli/bin/cli-actions/export.js b/packages/cli/bin/cli-actions/export.js new file mode 100644 index 000000000..5f4f4ebc7 --- /dev/null +++ b/packages/cli/bin/cli-actions/export.js @@ -0,0 +1,12 @@ +'use strict'; +const archive = require('../archive'); +const resolveConfig = require('../resolve-config'); +const wrapAsync = require('../utils').wrapAsync; + +const _export = options => + wrapAsync(function*() { + const config = yield resolveConfig(options.parent.config); + archive(config); + }); + +module.exports = _export; diff --git a/packages/cli/bin/cli-actions/help.js b/packages/cli/bin/cli-actions/help.js new file mode 100644 index 000000000..6448e7749 --- /dev/null +++ b/packages/cli/bin/cli-actions/help.js @@ -0,0 +1,10 @@ +'use strict'; +module.exports = () => { + /* eslint-disable */ + console.log(` + Examples: + $ patternlab init # Initialize a Pattern Lab project.'); + $ patternlab # Builds Pattern Lab from the current dir'); + $ patternlab --config # Pattern Lab from a config in a specified directory');`); + /* eslint-enable */ +}; diff --git a/packages/cli/bin/cli-actions/init.js b/packages/cli/bin/cli-actions/init.js new file mode 100644 index 000000000..afc715a99 --- /dev/null +++ b/packages/cli/bin/cli-actions/init.js @@ -0,0 +1,89 @@ +'use strict'; +const patternlab = require('@pattern-lab/core'); +const merge = require('deepmerge'); +const ask = require('../ask'); +const scaffold = require('../scaffold'); +const installEdition = require('../install-edition'); +const installStarterkit = require('../install-starterkit'); +const replaceConfigPaths = require('../replace-config'); +const ora = require('ora'); +const path = require('path'); +const wrapAsync = require('../utils').wrapAsync; +const writeJsonAsync = require('../utils').writeJsonAsync; + +const defaultPatternlabConfig = patternlab.getDefaultConfig(); + +// https://github.com/TehShrike/deepmerge#overwrite-array +const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray; + +const init = options => + wrapAsync(function*() { + const sourceDir = 'source'; + const publicDir = 'public'; + const exportDir = 'pattern_exports'; + const answers = options.projectDir ? options : yield ask(options); + const projectDir = answers.projectDir || './'; + const edition = answers.edition; + const starterkit = answers.starterkit; + + /** + * Process the init routines + * 1 Replace config paths + * 2. Scaffold the folder structure + * 3. If `edition` is present: + * 3.1 Install edition + * 3.2 Reassign adjustedconfig + * 4. If `starterkit` is present install it and copy over the mandatory starterkit files to sourceDir + * 5. Save patternlab-config.json in projectDir + */ + const spinner = ora(`Setting up Pattern Lab in ${projectDir}`).start(); + let patternlabConfig = replaceConfigPaths( + defaultPatternlabConfig, + projectDir, + sourceDir, + publicDir, + exportDir + ); // 1 + + yield scaffold(projectDir, sourceDir, publicDir, exportDir); // 2 + + if (edition) { + spinner.text = `⊙ patternlab → Installing edition: ${edition}`; + const newConf = yield installEdition( + edition, + patternlabConfig, + projectDir + ); // 3.1 + if (newConf) { + patternlabConfig = merge(patternlabConfig, newConf, { + arrayMerge: overwriteMerge, + }); // 3.2 + } + spinner.succeed(`⊙ patternlab → Installed edition: ${edition}`); + } + if (starterkit) { + spinner.text = `⊙ patternlab → Installing starterkit ${starterkit}`; + spinner.start(); + const starterkitConfig = yield installStarterkit( + starterkit, + patternlabConfig + ); + spinner.succeed(`⊙ patternlab → Installed starterkit: ${starterkit}`); + if (starterkitConfig) { + patternlabConfig = merge(patternlabConfig, starterkitConfig, { + arrayMerge: overwriteMerge, + }); + } + } // 4 + yield writeJsonAsync( + path.resolve(projectDir, 'patternlab-config.json'), + patternlabConfig + ); // 5 + + spinner.succeed( + `⊙ patternlab → Yay ☺. Pattern Lab Node was successfully initialized in ${projectDir}` + ); + return true; + }); + +module.exports = init; diff --git a/packages/cli/bin/cli-actions/install.js b/packages/cli/bin/cli-actions/install.js new file mode 100644 index 000000000..28aa8a445 --- /dev/null +++ b/packages/cli/bin/cli-actions/install.js @@ -0,0 +1,65 @@ +'use strict'; +const ora = require('ora'); +const installPlugin = require('../install-plugin'); +const installStarterkit = require('../install-starterkit'); +const resolveConfig = require('../resolve-config'); +const wrapAsync = require('../utils').wrapAsync; +const writeJsonAsync = require('../utils').writeJsonAsync; + +/** + * install + * @desc Handles async install and activation of starterkits/plugins + * @param {object} options + */ +const install = options => + wrapAsync(function*() { + const config = yield resolveConfig(options.parent.config); + + const spinner = ora( + `⊙ patternlab → Installing additional resources …` + ).start(); + + if (options.starterkits && Array.isArray(options.starterkits)) { + const starterkits = yield Promise.all( + options.starterkits.map(starterkit => + wrapAsync(function*() { + spinner.text = `⊙ patternlab → Installing starterkit: ${starterkit}`; + return yield installStarterkit( + { + name: starterkit, + value: starterkit, + }, + config + ); + }) + ) + ); + spinner.succeed( + `⊙ patternlab → Installed following starterkits: ${starterkits.join( + ', ' + )}` + ); + } + if (options.plugins && Array.isArray(options.plugins)) { + const plugins = yield Promise.all( + options.plugins.map(plugin => + wrapAsync(function*() { + return yield installPlugin( + { + name: plugin, + value: plugin, + }, + config + ); + }) + ) + ); + spinner.succeed( + `⊙ patternlab → Installed following plugins: ${plugins.join(', ')}` + ); + } + yield writeJsonAsync(options.parent.config, config); + spinner.succeed(`⊙ patternlab → Updated config`); + }); + +module.exports = install; diff --git a/packages/cli/bin/cli-actions/serve.js b/packages/cli/bin/cli-actions/serve.js new file mode 100644 index 000000000..6e2cc8fd2 --- /dev/null +++ b/packages/cli/bin/cli-actions/serve.js @@ -0,0 +1,12 @@ +'use strict'; +const resolveConfig = require('../resolve-config'); +const servePatterns = require('../serve'); +const wrapAsync = require('../utils').wrapAsync; + +const serve = options => + wrapAsync(function*() { + const config = yield resolveConfig(options.parent.config); + servePatterns(config, options); + }); + +module.exports = serve; diff --git a/packages/cli/bin/cli-actions/version.js b/packages/cli/bin/cli-actions/version.js new file mode 100644 index 000000000..9332488b0 --- /dev/null +++ b/packages/cli/bin/cli-actions/version.js @@ -0,0 +1,5 @@ +'use strict'; +const patternlab = require('@pattern-lab/core'); + +module.exports = ({ version }) => + `${version} (Pattern Lab Node Core version: ${patternlab.getVersion()})`; diff --git a/packages/cli/bin/inquiries/confirm.js b/packages/cli/bin/inquiries/confirm.js new file mode 100644 index 000000000..9cabdf88d --- /dev/null +++ b/packages/cli/bin/inquiries/confirm.js @@ -0,0 +1,12 @@ +'use strict'; +/** confirmSetup {Array} - Inquirer question to confirm selection */ +const confirmSetup = [ + { + type: 'confirm', + name: 'confirm', + message: 'Are you happy with your choices? (Hit enter for YES)?', + default: true, + }, +]; + +module.exports = confirmSetup; diff --git a/packages/cli/bin/inquiries/edition.js b/packages/cli/bin/inquiries/edition.js new file mode 100644 index 000000000..f62dba232 --- /dev/null +++ b/packages/cli/bin/inquiries/edition.js @@ -0,0 +1,40 @@ +'use strict'; +const inquirer = require('inquirer'); + +/** editionSetup {Array} - Inquirer question logic for first question regarding editions */ +const editionSetup = [ + { + type: 'input', + name: 'project_root', + message: 'Please specify a directory for your Pattern Lab project.', + default: () => './', + }, + { + type: 'list', + name: 'edition', + message: 'What templating language do you want to use with Pattern Lab?', + choices: [ + { + name: 'Handlebars', + value: '@pattern-lab/edition-node', + }, + { + name: 'Twig (PHP)', + value: '@pattern-lab/edition-twig', + }, + new inquirer.Separator(), + { + name: 'None', + value: false, + }, + ], + default: function() { + return { + name: 'Handlebars', + value: '@pattern-lab/edition-node', + }; + }, + }, +]; + +module.exports = editionSetup; diff --git a/packages/cli/bin/inquiries/starterkit.js b/packages/cli/bin/inquiries/starterkit.js new file mode 100644 index 000000000..523eb64bc --- /dev/null +++ b/packages/cli/bin/inquiries/starterkit.js @@ -0,0 +1,50 @@ +'use strict'; +const inquirer = require('inquirer'); +const CUSTOM_STARTERKIT = Symbol('CUSTOM_STARTERKIT'); + +/** starterkitSetup {Array} - Inquirer question logic for regarding starterkits */ +const starterkitSetup = [ + { + type: 'list', + name: 'starterkit', + message: 'What initial patterns do you want included in your project?', + choices: [ + { + name: + 'Handlebars base patterns (some basic patterns to get started with)', + value: '@pattern-lab/starterkit-handlebars-vanilla', + }, + { + name: 'Handlebars demo patterns (full demo website and patterns)', + value: '@pattern-lab/starterkit-handlebars-demo', + }, + { + name: 'Twig (PHP) demo patterns (full demo website and patterns)', + value: '@pattern-lab/starterkit-twig-demo', + }, + new inquirer.Separator(), + { + name: 'Custom starterkit', + value: CUSTOM_STARTERKIT, + }, + new inquirer.Separator(), + { + name: 'Blank project (no patterns)', + value: false, + }, + ], + default: { + name: 'Handlebars demo patterns (full demo website and patterns)', + value: 'starterkit-handlebars-demo', + }, + }, + { + name: 'starterkit', + message: 'Type the name of the custom starterkit to use:', + type: 'input', + when(answers) { + return answers.starterkit === CUSTOM_STARTERKIT; + }, + }, +]; +module.exports = starterkitSetup; diff --git a/packages/cli/bin/install-edition.js b/packages/cli/bin/install-edition.js new file mode 100644 index 000000000..a8cffe72b --- /dev/null +++ b/packages/cli/bin/install-edition.js @@ -0,0 +1,109 @@ +/* eslint-disable no-param-reassign */ +'use strict'; + +const path = require('path'); +const merge = require('deepmerge'); +const EOL = require('os').EOL; +const { + checkAndInstallPackage, + copyAsync, + wrapAsync, + writeJsonAsync, + getJSONKey, +} = require('./utils'); + +// https://github.com/TehShrike/deepmerge#overwrite-array +const overwriteMerge = (destinationArray, sourceArray, options) => sourceArray; + +const installEdition = (edition, config, projectDir) => { + const pkg = require(path.resolve(projectDir, 'package.json')); + + return wrapAsync(function*() { + /** + * 1. Trigger edition install + * 2. Copy over the mandatory edition files to sourceDir + * 3. Copy dependencies defined in edition + * 4. Do custom post-install procedures for different core editions: + * 4.1 Copy gulpfile.js for edition-node-gulp + * 4.2 Copy scripts for edition-node + * 4.3 Copy items for edition-twig + */ + const sourceDir = config.paths.source.root; + yield checkAndInstallPackage(edition); // 1 + yield copyAsync( + path.resolve('./node_modules', edition, 'source', '_meta'), + path.resolve(sourceDir, '_meta') + ); // 2 + pkg.dependencies = Object.assign( + {}, + pkg.dependencies || {}, + yield getJSONKey(edition, 'dependencies') + ); // 3 + switch ( + edition // 4 + ) { + // 4.1 + case '@pattern-lab/edition-node-gulp': { + yield copyAsync( + path.resolve('./node_modules', edition, 'gulpfile.js'), + path.resolve(sourceDir, '../', 'gulpfile.js') + ); + break; + } + // 4.2 + case '@pattern-lab/edition-node': { + const editionPath = path.resolve('./node_modules', edition); + const editionConfigPath = path.resolve( + editionPath, + 'patternlab-config.json' + ); + + const editionConfig = require(editionConfigPath); + + pkg.scripts = Object.assign( + {}, + pkg.scripts || {}, + yield getJSONKey(edition, 'scripts') + ); + + yield copyAsync( + path.join(editionPath, path.sep, 'helpers', path.sep, 'test.js'), + path.resolve(sourceDir, '../', 'helpers/test.js') + ); + + config = merge(config, editionConfig, { arrayMerge: overwriteMerge }); + break; + } + // 4.3 + case '@pattern-lab/edition-twig': { + const editionPath = path.resolve('./node_modules', edition); + const editionConfigPath = path.resolve( + editionPath, + 'patternlab-config.json' + ); + const editionConfig = require(editionConfigPath); + + pkg.scripts = Object.assign( + {}, + pkg.scripts || {}, + yield getJSONKey(edition, 'scripts') + ); + + yield copyAsync( + path.resolve(editionPath, 'alter-twig.php'), + path.resolve(sourceDir, '../', 'alter-twig.php') + ); + + config = merge(config, editionConfig, { arrayMerge: overwriteMerge }); + break; + } + } + yield writeJsonAsync(path.resolve(projectDir, 'package.json'), pkg, { + spaces: 2, + EOL: EOL, + }); + return config; + }); +}; + +module.exports = installEdition; diff --git a/packages/cli/bin/install-plugin.js b/packages/cli/bin/install-plugin.js new file mode 100644 index 000000000..f297bd62a --- /dev/null +++ b/packages/cli/bin/install-plugin.js @@ -0,0 +1,33 @@ +'use strict'; + +const path = require('path'); + +const _ = require('lodash'); + +const checkAndInstallPackage = require('./utils').checkAndInstallPackage; +const wrapAsync = require('./utils').wrapAsync; + +const installPlugin = (plugin, config) => + wrapAsync(function*() { + const name = plugin.name || plugin; + yield checkAndInstallPackage(name); + // Put the installed plugin in the patternlab-config.json + _.set(config, `plugins[${name}]['enabled']`, true); + _.set(config, `plugins[${name}]['initialized']`, false); + + // Get the options from the plugin, if any + const pluginPathConfig = path.resolve( + path.join(process.cwd(), 'node_modules', name, 'config.json') + ); + try { + const pluginConfigJSON = require(pluginPathConfig); + if (!_.has(config.plugins[name].options)) { + _.set(config, `plugins[${name}][options]`, pluginConfigJSON); + } + } catch (ex) { + //a config.json file is not required at this time + } + return name; + }); + +module.exports = installPlugin; diff --git a/packages/cli/bin/install-starterkit.js b/packages/cli/bin/install-starterkit.js new file mode 100644 index 000000000..f7ce2b61c --- /dev/null +++ b/packages/cli/bin/install-starterkit.js @@ -0,0 +1,26 @@ +'use strict'; +const path = require('path'); +const fs = require('fs-extra'); +const { + copyAsync, + wrapAsync, + checkAndInstallPackage, + readJsonAsync, +} = require('./utils'); + +const installStarterkit = (starterkit, config) => + wrapAsync(function*() { + const sourceDir = config.paths.source.root; + const name = starterkit.value || starterkit; + yield checkAndInstallPackage(name); + const kitPath = path.resolve('./node_modules', name); + yield copyAsync(path.resolve(kitPath, 'dist'), path.resolve(sourceDir)); + let kitConfig; + const kitConfigPath = path.resolve(kitPath, 'patternlab-config.json'); + if (fs.existsSync(kitConfigPath)) { + kitConfig = yield readJsonAsync(kitConfigPath); + } + return kitConfig; + }); + +module.exports = installStarterkit; diff --git a/packages/cli/bin/patternlab.js b/packages/cli/bin/patternlab.js new file mode 100755 index 000000000..a76468994 --- /dev/null +++ b/packages/cli/bin/patternlab.js @@ -0,0 +1,152 @@ +#!/usr/bin/env node +/* eslint-disable no-unused-vars */ +'use strict'; +const cli = require('commander'); +const path = require('path'); +const build = require('./cli-actions/build'); +const disable = require('./cli-actions/disable'); +const enable = require('./cli-actions/enable'); +const help = require('./cli-actions/help'); +const version = require('./cli-actions/version'); +const init = require('./cli-actions/init'); +const install = require('./cli-actions/install'); +const exportPatterns = require('./cli-actions/export'); +const serve = require('./cli-actions/serve'); +const { error, log } = require('./utils'); +const pkg = require('../package.json'); + +// Register info and error logging +log.on('patternlab.error', err => console.log(err)); // eslint-disable-line +log.on('patternlab.info', msg => console.log(msg)); // eslint-disable-line + +// Conditionally register verbose logging +const verboseLogs = verbose => + log.on('patternlab.debug', msg => console.log(msg)); // eslint-disable-line + +// Conditionally unregister all logging +const silenceLogs = () => { + log.removeAllListeners('patternlab.debug'); + log.removeAllListeners('patternlab.info'); + log.removeAllListeners('patternlab.error'); +}; + +// Split strings into an array +const list = val => val.split(','); + +/** + * Hook up cli version, usage and options + */ +cli + .version(version(pkg), '-V, --version') + .usage(' [options]') + .arguments(' [options]') + .option( + '-c, --config ', + 'Specify config file. Default looks up the project dir', + val => val.trim(), + path.resolve(process.cwd(), 'patternlab-config.json') + ) + .option('-v, --verbose', 'Show verbose console logs', verboseLogs) + .option('--silent', 'Turn off console logs', silenceLogs); + +/** + * build + * @desc Setup Pattern Lab's `build` cmd + */ +cli + .command('build') + .alias('compile') + .description('Build Pattern Lab. Optionally (re-)build only the patterns') + .option('-p, --patterns-only', 'Whether to only build patterns') + .option('--no-watch', 'Start watching for changes') + .action(build); + +/** + * export + * @desc Export a Pattern Lab patterns into a compressed format + */ +cli + .command('export') + .description('Export Pattern Lab patterns into a compressed format') + .action(exportPatterns); + +/** + * init + * @desc Initialize a Pattern Lab project from scratch or import an edition and/or starterkit + */ +cli + .command('init') + .description( + 'Initialize a Pattern Lab project from scratch or import an edition and/or starterkit' + ) + .option('-p, --project-dir ', 'Specify a project directory') + .option('-e, --edition ', 'Specify an edition to install') + .option('-k, --starterkit ', 'Specify a starterkit to install') + .action(init); + +/** + * install + * @desc Installs Pattern Lab related modules like starterkits or plugins + */ +cli + .command('install') + .alias('add') + .description( + 'Installs Pattern Lab related modules like starterkits or plugins' + ) + .option( + '--starterkits ', + 'Specify one or more starterkit to install', + list + ) + .option('--plugins ', 'Specify one or more plugins to install', list) + .action(install); + +/** + * enable + * @desc Enable Pattern Lab plugins. Unavailable plugins are just skipped + */ +cli + .command('enable') + .alias('on') + .description('Enable Pattern Lab plugins') + .option('--plugins ', 'Specify one or more plugins to enable', list) + .action(enable); + +/** + * disable + * @desc Enable Pattern Lab plugins. Unavailable plugins are just skipped + */ +cli + .command('disable') + .alias('off') + .description('Disable Pattern Lab plugins') + .option('--plugins ', 'Specify one or more plugins to disable', list) + .action(disable); + +/** + * serve + * @desc Starts a server to inspect files in browser + */ +cli + .command('serve') + .alias('browse') + .description('Starts a server to inspect files in browser') + .option('--no-watch', 'Start watching for changes') + .action(serve); + +// Show additional help +cli.on('--help', help); + +/** + * Catch all unsupported commands and delegate to the cli's help + * Parse at the end because Node emit is immediate + */ +cli + .on('*', () => { + error( + 'Invalid command provided. See the help for available commands/options.' + ); + cli.help(); + }) + .parse(process.argv); diff --git a/packages/cli/bin/replace-config.js b/packages/cli/bin/replace-config.js new file mode 100644 index 000000000..19dd7f2ea --- /dev/null +++ b/packages/cli/bin/replace-config.js @@ -0,0 +1,37 @@ +'use strict'; +const path = require('path'); +const _ = require('lodash'); + +/** + * @func replaceConfigPaths + * @desc Immutable replace source and public paths in the passed config. + * @param {config} config - The passed Pattern Lab config. + * @param {string} projectDir - The project directory path, defaults to ./ + * @param {string} sourceDir - The source root directory path. + * @param {string} publicDir - The public root directory path. + * @param {string} exportDir - The export root directory path. + * @return {config} - Returns a modified config. Original stays unaltered. + */ +function replaceConfigPaths( + config, + projectDir, + sourceDir, + publicDir, + exportDir +) { + const conf = Object.assign({}, config); + _.map(conf.paths.source, (value, key) => { + conf.paths.source[key] = _.isString(value) + ? value.replace(/^\.\/source/g, path.join(projectDir, sourceDir)) + : value; + }); + _.map(conf.paths.public, (value, key) => { + conf.paths.public[key] = _.isString(value) + ? value.replace(/^\.\/public/g, path.join(projectDir, publicDir)) + : value; + }); + conf.patternExportDirectory = path.join(projectDir, exportDir); + return conf; +} + +module.exports = replaceConfigPaths; diff --git a/packages/cli/bin/resolve-config.js b/packages/cli/bin/resolve-config.js new file mode 100644 index 000000000..f34c5e083 --- /dev/null +++ b/packages/cli/bin/resolve-config.js @@ -0,0 +1,45 @@ +'use strict'; +const exists = require('path-exists'); +const path = require('path'); +const error = require('./utils').error; +const readJsonAsync = require('./utils').readJsonAsync; +const wrapAsync = require('./utils').wrapAsync; + +/** + * @func resolveConfig + * @desc Resolves the given Pattern Lab config file. + * @param {string} [configPath=./patternlab-config.json] - Path to the patternlab-config.json. Defaults to project dir. + * @return {object|boolean} Returns the config object or false otherwise. + */ +function resolveConfig(configPath) { + return wrapAsync(function*() { + if (typeof configPath !== 'string') { + error( + 'resolveConfig: If configPath is set, it is expected to be of type string.' + ); + return false; + } + if (!exists.sync(configPath)) { + error(`resolveConfig: configPath ${configPath} does not exists`); + return false; + } + + /** + * Setup the config. + * 1. Check if user specified custom Pattern Lab config location + * 2. Read the config file + */ + try { + const absoluteConfigPath = path.resolve(configPath); // 1 + return yield readJsonAsync(absoluteConfigPath); // 2 + } catch (err) { + error( + 'resolveConfig: Got an error during parsing your Pattern Lab config. Please make sure your config file exists.' + ); + error(err); + return false; + } + }); +} + +module.exports = resolveConfig; diff --git a/packages/cli/bin/scaffold.js b/packages/cli/bin/scaffold.js new file mode 100644 index 000000000..de1e4df73 --- /dev/null +++ b/packages/cli/bin/scaffold.js @@ -0,0 +1,39 @@ +'use strict'; +const path = require('path'); +const execa = require('execa'); +const fs = require('fs-extra'); +const wrapAsync = require('./utils').wrapAsync; +const mkdirsAsync = require('./utils').mkdirsAsync; + +/** + * @func scaffold + * @desc Generate file and folder structure for a Pattern Lab project + * @param {string} projectDir - The project root directory path. + * @param {string} sourceDir - The source root directory path. + * @param {string} publicDir - The public root directory path. + * @param {string} exportDir - The export root directory path. + * @return {void} + */ +const scaffold = (projectDir, sourceDir, publicDir, exportDir) => + wrapAsync(function*() { + const projectPath = path.join(process.cwd(), projectDir); + if (!fs.existsSync(path.join(projectPath, 'package.json'))) { + fs.ensureDirSync(projectPath); + execa.sync('npm', ['init', '-y'], { + cwd: projectPath, + }); + } + /** + * Create mandatory files structure + * 1. Create project source directory + * 2. Create project public directory + * 3. Create project export directory + */ + yield Promise.all([ + mkdirsAsync(path.resolve(projectDir, path.normalize(sourceDir))), // 1 + mkdirsAsync(path.resolve(projectDir, path.normalize(publicDir))), // 2 + mkdirsAsync(path.resolve(projectDir, path.normalize(exportDir))), // 3 + ]); + }); + +module.exports = scaffold; diff --git a/packages/cli/bin/serve.js b/packages/cli/bin/serve.js new file mode 100644 index 000000000..a7502b65f --- /dev/null +++ b/packages/cli/bin/serve.js @@ -0,0 +1,47 @@ +'use strict'; +const patternlab = require('@pattern-lab/core'); +const _ = require('lodash'); + +const isValidConfig = require('./validate-config'); +const { error, info } = require('./utils'); + +/** + * @func serve + * @desc Start a browser-sync server in the Pattern Lab public dir + * @param {object} config - The passed Pattern Lab config + * @param {object} options - The passed options at invocation time + */ +function serve(config, options) { + if (!isValidConfig) { + throw new TypeError( + 'serve: Expects config not to be empty and of type object.' + ); + } + + if ( + !_.has(config, 'paths.public.root') || + _.isEmpty(config.paths.public.root) + ) { + throw new TypeError( + 'serve: config.paths.public.root is empty or does not exist. Please check your Pattern Lab config.' + ); + } + if ( + !_.has(config, 'paths.source.root') || + _.isEmpty(config.paths.source.root) + ) { + throw new TypeError( + 'serve: config.paths.source.root is empty or does not exist. Please check your Pattern Lab config.' + ); + } + + try { + info(`serve: Serving your files …`); + const pl = patternlab(config); + pl.server.serve(options); + } catch (err) { + error(err); + } +} + +module.exports = serve; diff --git a/packages/cli/bin/utils.js b/packages/cli/bin/utils.js new file mode 100644 index 000000000..771a6f1a9 --- /dev/null +++ b/packages/cli/bin/utils.js @@ -0,0 +1,216 @@ +'use strict'; +const fs = require('fs-extra'); +const spawn = require('execa'); +const glob = require('glob'); +const path = require('path'); +const chalk = require('chalk'); +const EventEmitter = require('events').EventEmitter; +const hasYarn = require('has-yarn'); + +/** + * @name log + * @desc tiny event-based logger + * @type {*} + */ +const log = Object.assign( + { + debug(msg) { + this.emit( + 'patternlab.debug', + `${chalk.green('⊙ patternlab →')} ${chalk.dim(msg)}` + ); + }, + info(msg) { + this.emit('patternlab.info', `⊙ patternlab → ${chalk.dim(msg)}`); + }, + error(msg) { + this.emit( + 'patternlab.error', + `${chalk.red('⊙ patternlab →')} ${chalk.dim(msg)}` + ); + }, + }, + EventEmitter.prototype +); + +/** + * @func debug + * @desc Coloured debug log + * @param {*} msg - The variadic messages to log out. + * @return {void} + */ +const debug = log.debug.bind(log); + +/** + * @func info + * @desc Coloured debug log + * @param {*} msg - The variadic messages to log out. + * @return {void} + */ +const info = log.info.bind(log); + +/** + * @func error + * @desc Coloured error log + * @param {*} e - The variadic messages to log out. + * @return {void} + */ +const error = log.error.bind(log); + +/** + * @func wrapAsync + * @desc Wraps an generator function to yield out promisified stuff + * @param {function} fn - Takes a generator function + */ +const wrapAsync = fn => + new Promise((resolve, reject) => { + const generator = fn(); + /* eslint-disable */ + (function spwn(val) { + let res; + try { + res = + {}.toString.call(val) !== '[object Error]' + ? generator.next(val) + : generator.throw(val); + } catch (err) { + return reject(err); + } + const v = res.value; + if (res.done) { + return resolve(v); + } + Promise.resolve(v) + .then(spwn) + .catch(spwn); + })(); + /* eslint-enable */ + }); + +/** + * @func glob + * @desc Promisified glob function + * @param {string} pattern - A glob pattern to match against + * @param {object} opts - A configuration object. See glob package for details + * @return {Promise} + */ +const asyncGlob = (pattern, opts) => + new Promise((resolve, reject) => + glob(pattern, opts, (err, matches) => + err !== null ? reject(err) : resolve(matches) + ) + ); + +/** + * @func copyWithPattern + * @desc Copies multiple files asynchronously from one dir to another according to a glob pattern specified + * @param {string} cwd - The path to search for file(s) at + * @param {string} pattern - A glob pattern to match the file(s) + * @param {string} dest - The destination dir path + * @return {Promise} + */ +const copyWithPattern = (cwd, pattern, dest) => + wrapAsync(function*() { + const files = yield asyncGlob(pattern, { cwd: cwd }); + if (files.length === 0) { + debug('copy: Nothing to copy'); + } + // Copy concurrently + const promises = files.map(file => + fs.copy(path.join(cwd, file), path.join(dest, file)) + ); + return yield Promise.all(promises); + }); + +/** + * @func fetchPackage + * @desc Fetches and saves packages from npm into node_modules and adds a reference in the package.json under dependencies + * @param {string} packageName - The package name + */ +const fetchPackage = packageName => + wrapAsync(function*() { + const useYarn = hasYarn(); + const pm = useYarn ? 'yarn' : 'npm'; + const installCmd = useYarn ? 'add' : 'install'; + try { + if (packageName) { + const cmd = yield spawn(pm, [installCmd, packageName]); + error(cmd.stderr); + } + } catch (err) { + error( + `fetchPackage: Fetching required dependencies from ${pm} failed for ${packageName} with ${err}` + ); + throw err; // Rethrow error + } + }); + +/** + * @func checkAndInstallPackage + * Checks whether a package for a given packageName is installed locally. If package cannot be found, fetch and install it + * @param {string} packageName - The package name + * @return {boolean} + */ +const checkAndInstallPackage = packageName => + wrapAsync(function*() { + try { + require.resolve(packageName); + return true; + } catch (err) { + debug( + `checkAndInstallPackage: ${packageName} not installed. Fetching it now …` + ); + yield fetchPackage(packageName); + return false; + } + }); + +/** + * @func noop + * @desc Plain arrow expression for noop + */ +const noop = () => {}; + +/** + * @func writeJsonAsync + * Wrapper for fs.writeJsonAsync with consistent spacing + * @param {string} filePath + * @param {object} data + */ +const writeJsonAsync = (filePath, data) => + wrapAsync(function*() { + yield fs.outputJSON(filePath, data, { spaces: 2 }); + }); + +/** + * @func getJSONKey + * Installs package, then returns the value for the given JSON file's key within + * @param {string} packageName - the node_module to install / load + * @param {object} key - the key to find + * @param {object} fileName - the filePath of the JSON + */ +const getJSONKey = (packageName, key, fileName = 'package.json') => + wrapAsync(function*() { + yield checkAndInstallPackage(packageName); + const jsonData = yield fs.readJson( + path.resolve('node_modules', packageName, fileName) + ); + return jsonData[key]; + }); + +module.exports = { + copyWithPattern, + copyAsync: fs.copy, + mkdirsAsync: fs.mkdirs, + moveAsync: fs.move, + writeJsonAsync: writeJsonAsync, + readJsonAsync: fs.readJson, + error, + info, + debug, + log, + wrapAsync, + checkAndInstallPackage, + noop, + getJSONKey, +}; diff --git a/packages/cli/bin/validate-config.js b/packages/cli/bin/validate-config.js new file mode 100644 index 000000000..eb4e521e8 --- /dev/null +++ b/packages/cli/bin/validate-config.js @@ -0,0 +1,12 @@ +'use strict'; +/** + * @func isValidConfig + * @desc Checks validity of a patternlab config + * @param {object} config - Name of the command to check against. + * @return {object} - Returns true is all is good, false otherwise. + */ +function isValidConfig(config) { + return !config || typeof config !== 'object'; +} + +module.exports = isValidConfig; diff --git a/packages/cli/license b/packages/cli/license new file mode 100644 index 000000000..ec350935b --- /dev/null +++ b/packages/cli/license @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Raphael Okon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 000000000..847f1d94d --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,63 @@ +{ + "name": "@pattern-lab/cli", + "description": "Command-line interface (CLI) for the @pattern-lab/core.", + "version": "5.9.3", + "bin": { + "patternlab": "bin/patternlab.js" + }, + "author": { + "name": "Raphael Okon" + }, + "dependencies": { + "@pattern-lab/core": "^5.9.3", + "@pattern-lab/live-server": "^5.9.3", + "@pattern-lab/starterkit-mustache-base": "3.0.3", + "archiver": "2.1.1", + "chalk": "2.4.1", + "commander": "2.15.1", + "deepmerge": "^2.1.1", + "execa": "0.10.0", + "fs-extra": "6.0.1", + "glob": "7.1.2", + "has-yarn": "1.0.0", + "inquirer": "5.1.0", + "lodash": "4.17.15", + "ora": "2.1.0", + "path-exists": "3.0.0", + "sanitize-filename": "1.6.1", + "starterkit-mustache-acidtest": "0.0.3", + "starterkit-mustache-bootstrap": "0.1.1", + "starterkit-mustache-foundation": "0.1.1", + "starterkit-mustache-materialdesign": "0.1.2" + }, + "devDependencies": { + "eslint": "4.18.2", + "eslint-config-prettier": "2.9.0", + "eslint-plugin-prettier": "2.6.0", + "prettier": "1.11.1", + "proxyquire": "2.0.1", + "tap": "14.6.4" + }, + "files": [ + "bin" + ], + "keywords": [ + "Pattern Lab", + "Atomic Web Design", + "Node", + "JavaScript" + ], + "scripts": { + "lint": "eslint ./{bin,test}", + "test:separate": "tap ./test/*.test.js --reporter spec --timeout=120" + }, + "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/cli", + "bugs": "https://github.com/pattern-lab/patternlab-node/issues", + "license": "MIT", + "engines": { + "node": ">=10.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/cli/readme.md b/packages/cli/readme.md new file mode 100644 index 000000000..e5a9ae95e --- /dev/null +++ b/packages/cli/readme.md @@ -0,0 +1,142 @@ +# Pattern Lab Node CLI + +> Command-line interface (CLI) for the patternlab-node core. + +[![Build Status](https://travis-ci.org/pattern-lab/patternlab-node.svg?branch=master)](https://travis-ci.org/pattern-lab/patternlab-node) + + +## Installation +*Note: Global installs are currently not supported and will be fixed when the Pattern Lab core hits v3.0.0* + +#### Via NPM +`npm install @pattern-lab/cli --save-dev` + +#### Via Yarn +`yarn add @pattern-lab/cli --dev` + +## Configuring Your Project to Use the CLI + +If the CLI is installed globally, you may call commands directly, such as `patternlab --version`. + +If the CLI is not installed globally, you need to tell `npm` where to find the executable when invoking commands. + +Open `package.json` and add the following to your `scripts` object: + +```diff +"scripts": { ++ "patternlab": "patternlab" +}, +``` +This tells `npm` to look in the local `node_modules/.bin` directory for the `patternlab` CLI. + +Subcommands and options can then be forwarded to the CLI like this: + +```bash +npm run patternlab -- serve +``` + +Installing [`edition-node`](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node) will add the following CLI commands for convenience: + +```diff + "scripts": { ++ "pl:build": "patternlab build --config ./patternlab-config.json", ++ "pl:help": "patternlab --help", ++ "pl:install": "patternlab install --config ./patternlab-config.json", ++ "pl:serve": "patternlab serve --config ./patternlab-config.json", ++ "pl:version": "patternlab --version" + }, +``` + +Then you can invoke any of these like this: + +``` +npm run pl:serve +``` + +## API & Usage +### General usage +``` +Usage: patternlab [options] + Commands: + build|compile [options] Build Pattern Lab. Optionally (re-)build only the patterns + export Export a Pattern Lab patterns into a compressed format + init [options] Initialize a Pattern Lab project from scratch or import an edition and/or starterkit + install|add [options] Installs Pattern Lab related modules like starterkits or plugins + serve|browse [options] Starts a server to inspect files in browser + + Options: + -h, --help output usage information + -V, --version output the version number + -c, --config Specify config file. Default looks up the project dir + -v, --verbose Show verbose logging + --silent Turn off console logs +``` + +### Build/Compile Pattern Lab +``` +Usage: build|compile [options] + +Build Pattern Lab. Optionally (re-)build only the patterns + + Options: + -h, --help output usage information + -p, --patterns-only Whether to only build patterns +``` + +### Initialize Pattern Lab +``` +Usage: init [options] + +Initialize a Pattern Lab project from scratch or import an edition and/or starterkit +Passing no options starts the init in interactive mode + + Options: + -h, --help output usage information + -p, --project-dir Specify a project directory. Default: ./ + -e, --edition Specify an edition to install. Default: edition-node + -k, --starterkit Specify a starterkit to install. Default: starterkit-mustache-base +``` + +### Serve Pattern Lab +``` +Usage: serve|browse [options] + +Starts a server to inspect files in browser + + + Options: + -h, --help output usage information + -w, --watch Start watching for changes +``` + +### Export Pattern Lab +``` +Usage: export [options] + +Export a Pattern Lab patterns into a compressed format + + Options: + -h, --help output usage information +``` + +### Install Pattern Lab starterkits or plugins +``` +Usage: install|add [options] + +Installs Pattern Lab related modules like starterkits or plugins + + Options: + -h, --help output usage information + --starterkits Specify one or more starterkits to install + --plugins Specify one or more plugins to install + +``` + +## Examples +``` + $ patternlab init # Initialize a Pattern Lab project. + $ patternlab build # Builds Pattern Lab from the current dir + $ patternlab build --config # Builds Pattern Lab from different project directory +``` +## License +MIT © [Raphael Okon](https://github.com/raphaelokon) diff --git a/packages/cli/test/build.test.js b/packages/cli/test/build.test.js new file mode 100644 index 000000000..6a56cb6fe --- /dev/null +++ b/packages/cli/test/build.test.js @@ -0,0 +1,45 @@ +const patternlab = require('@pattern-lab/core'); +const proxyquire = require('proxyquire'); +const tap = require('tap'); +const patternLabMock = require('./mocks/patternlab.mock.js'); +const config = patternlab.getDefaultConfig(); + +// Require build and mock patternlab.build() so that we only test the build module behavior +const build = proxyquire('../bin/build', { + '@pattern-lab/core': patternLabMock, +}); +const opts = { patternsOnly: true }; + +tap.test('Build ->', t => { + t.throws( + () => { + build(); + }, + {}, + 'throws when config is empty' + ); + t.throws( + () => { + build(123); + }, + {}, + 'throws when config is not of type object' + ); + t.throws( + () => { + build(undefined, opts); + }, + {}, + '--patterns-only throws when config is empty' + ); + t.throws( + () => { + build(undefined, opts); + }, + {}, + '--patterns-only throws when config is not of type object' + ); + t.type(build(config), 'boolean', 'returns a bool'); + t.type(build(config, opts), 'boolean', '--patterns-only returns a bool'); + t.end(); +}); diff --git a/packages/cli/test/cli-build.test.js b/packages/cli/test/cli-build.test.js new file mode 100644 index 000000000..d6712deae --- /dev/null +++ b/packages/cli/test/cli-build.test.js @@ -0,0 +1,49 @@ +const exists = require('path-exists'); +const getUniqueProjectPath = require('./utils/getUniqueProjectPath'); +const path = require('path'); +const spawnCmd = require('./utils/spawnCmd'); +const tap = require('tap'); +const wrapAsync = require('../bin/utils').wrapAsync; + +const projectRoot = getUniqueProjectPath(); + +tap.test('Init and build ->', t => + wrapAsync(function*() { + yield spawnCmd([ + 'init', + '--verbose', + '--project-dir', + projectRoot, + '--edition', + '@pattern-lab/edition-node', + '--starterkit', + '@pattern-lab/starterkit-mustache-demo', + ]); + yield spawnCmd([ + 'build', + '--config', + `${projectRoot}/patternlab-config.json`, + ]); + t.ok( + exists.sync(path.resolve(projectRoot, 'public')), + 'should build all files into public dir' + ); + t.ok( + exists.sync(path.resolve(projectRoot, 'public', 'annotations')), + 'with an annotations dir' + ); + t.ok( + exists.sync(path.resolve(projectRoot, 'public', 'css')), + 'with a css dir' + ); + t.ok( + exists.sync(path.resolve(projectRoot, 'public', 'images')), + 'with a images dir' + ); + t.ok( + exists.sync(path.resolve(projectRoot, 'public', 'styleguide')), + 'with a styleguide dir' + ); + t.end(); + }) +); diff --git a/packages/cli/test/cli-disable.test.js b/packages/cli/test/cli-disable.test.js new file mode 100644 index 000000000..64715b882 --- /dev/null +++ b/packages/cli/test/cli-disable.test.js @@ -0,0 +1,52 @@ +const getUniqueProjectPath = require('./utils/getUniqueProjectPath'); +const spawnCmd = require('./utils/spawnCmd'); +const tap = require('tap'); +const { readFileSync } = require('fs'); +const wrapAsync = require('../bin/utils').wrapAsync; + +const projectRoot = getUniqueProjectPath(); + +tap.test('Disable ->', t => + wrapAsync(function*() { + yield spawnCmd([ + 'init', + '--verbose', + '--project-dir', + projectRoot, + '--edition', + '@pattern-lab/edition-node', + '--starterkit', + '@pattern-lab/starterkit-mustache-base', + ]); + yield spawnCmd([ + 'install', + '--plugins', + '@pattern-lab/plugin-tab', + '--config', + `${projectRoot}/patternlab-config.json`, + ]); + yield spawnCmd([ + 'enable', + '--plugins', + '@pattern-lab/plugin-tab', + '--config', + `${projectRoot}/patternlab-config.json`, + ]); + yield spawnCmd([ + 'disable', + '--plugins', + '@pattern-lab/plugin-tab', + '--config', + `${projectRoot}/patternlab-config.json`, + ]); + const config = JSON.parse( + readFileSync(`${projectRoot}/patternlab-config.json`, 'utf8') + ); + t.equal( + config.plugins['@pattern-lab/plugin-tab'].enabled, + false, + 'and set the enabled flag to false in patternlab-config.json' + ); + t.end(); + }) +); diff --git a/packages/cli/test/cli-enable.test.js b/packages/cli/test/cli-enable.test.js new file mode 100644 index 000000000..62b7d837a --- /dev/null +++ b/packages/cli/test/cli-enable.test.js @@ -0,0 +1,45 @@ +const getUniqueProjectPath = require('./utils/getUniqueProjectPath'); +const spawnCmd = require('./utils/spawnCmd'); +const tap = require('tap'); +const { readFileSync } = require('fs'); +const wrapAsync = require('../bin/utils').wrapAsync; + +const projectRoot = getUniqueProjectPath(); + +tap.test('Enable ->', t => + wrapAsync(function*() { + yield spawnCmd([ + 'init', + '--verbose', + '--project-dir', + projectRoot, + '--edition', + '@pattern-lab/edition-node', + '--starterkit', + '@pattern-lab/starterkit-mustache-base', + ]); + yield spawnCmd([ + 'install', + '--plugins', + '@pattern-lab/plugin-tab', + '--config', + `${projectRoot}/patternlab-config.json`, + ]); + yield spawnCmd([ + 'enable', + '--plugins', + '@pattern-lab/plugin-tab', + '--config', + `${projectRoot}/patternlab-config.json`, + ]); + const config = JSON.parse( + readFileSync(`${projectRoot}/patternlab-config.json`, 'utf8') + ); + t.equal( + config.plugins['@pattern-lab/plugin-tab'].enabled, + true, + 'and set the enabled flag in patternlab-config.json' + ); + t.end(); + }) +); diff --git a/packages/cli/test/cli-export.test.js b/packages/cli/test/cli-export.test.js new file mode 100644 index 000000000..63e82ba06 --- /dev/null +++ b/packages/cli/test/cli-export.test.js @@ -0,0 +1,33 @@ +const exists = require('path-exists'); +const getUniqueProjectPath = require('./utils/getUniqueProjectPath'); +const path = require('path'); +const spawnCmd = require('./utils/spawnCmd'); +const tap = require('tap'); +const wrapAsync = require('../bin/utils').wrapAsync; + +const projectRoot = getUniqueProjectPath(); + +tap.test('Init and export ->', t => + wrapAsync(function*() { + yield spawnCmd([ + 'init', + '--verbose', + '--project-dir', + projectRoot, + '--edition', + '@pattern-lab/edition-node', + '--starterkit', + '@pattern-lab/starterkit-mustache-base', + ]); + yield spawnCmd([ + 'export', + '--config', + `${projectRoot}/patternlab-config.json`, + ]); + t.ok( + exists.sync(path.resolve(projectRoot, 'pattern_exports', 'patterns.zip')), + ' should create patterns.zip' + ); + t.end(); + }) +); diff --git a/packages/cli/test/cli-init.test.js b/packages/cli/test/cli-init.test.js new file mode 100644 index 000000000..a80c031ff --- /dev/null +++ b/packages/cli/test/cli-init.test.js @@ -0,0 +1,38 @@ +const exists = require('path-exists'); +const getUniqueProjectPath = require('./utils/getUniqueProjectPath'); +const path = require('path'); +const spawnCmd = require('./utils/spawnCmd'); +const tap = require('tap'); +const wrapAsync = require('../bin/utils').wrapAsync; + +const projectRoot = getUniqueProjectPath(); + +tap.test('Init ->', t => + wrapAsync(function*() { + yield spawnCmd([ + 'init', + '--verbose', + '--project-dir', + projectRoot, + '--edition', + '@pattern-lab/edition-node', + '--starterkit', + '@pattern-lab/starterkit-mustache-base', + ]); + t.ok( + exists.sync(path.resolve(projectRoot)), + 'should initialize a Pattern Lab project' + ); + t.ok(exists.sync(path.resolve(projectRoot, 'source')), 'with a source dir'); + t.ok(exists.sync(path.resolve(projectRoot, 'public')), 'with a public dir'); + t.ok( + exists.sync(path.resolve(projectRoot, 'pattern_exports')), + 'with a pattern_exports dir' + ); + t.ok( + exists.sync(path.resolve(projectRoot, 'patternlab-config.json')), + 'with a pattern_exports dir' + ); + t.end(); + }) +); diff --git a/packages/cli/test/export.test.js b/packages/cli/test/export.test.js new file mode 100644 index 000000000..169b60771 --- /dev/null +++ b/packages/cli/test/export.test.js @@ -0,0 +1,31 @@ +const exportPatterns = require('../bin/cli-actions/export'); +const tap = require('tap'); +const wrapAsync = require('../bin/utils').wrapAsync; + +tap.test('Export ->', t => { + t.plan(2); + t.test('with options empty', tt => + wrapAsync(function*() { + try { + yield exportPatterns(); + } catch (err) { + tt.type(err, TypeError, 'throws when options are empty'); + tt.end(); + } + }) + ); + t.test('with options not an object', tt => + wrapAsync(function*() { + try { + yield exportPatterns(123); + } catch (err) { + tt.type( + err, + TypeError, + 'throws when passed options are not of type object' + ); + tt.end(); + } + }) + ); +}); diff --git a/packages/cli/test/fixtures/patternlab-config.json b/packages/cli/test/fixtures/patternlab-config.json new file mode 100644 index 000000000..6102f522f --- /dev/null +++ b/packages/cli/test/fixtures/patternlab-config.json @@ -0,0 +1,85 @@ +{ + "cacheBust": true, + "cleanPublic": true, + "defaultPattern": "all", + "defaultShowPatternInfo": false, + "ishControlsHide": { + "s": false, + "m": false, + "l": false, + "full": false, + "random": false, + "disco": false, + "hay": true, + "mqs": false, + "find": false, + "views-all": false, + "views-annotations": false, + "views-code": false, + "views-new": false, + "tools-all": false, + "tools-docs": false + }, + "ishViewportRange": { + "s": [240, 500], + "m": [500, 800], + "l": [800, 2600] + }, + "logLevel": "info", + "outputFileSuffixes": { + "rendered": ".rendered", + "rawTemplate": "", + "markupOnly": ".markup-only" + }, + "paths": { + "source": { + "root": "./test/fixtures/source/", + "patterns": "./test/fixtures/source/_patterns/", + "data": "./test/fixtures/source/_data/", + "meta": "./test/fixtures/source/_meta/", + "annotations": "./test/fixtures/source/_annotations/", + "styleguide": "node_modules/@pattern-lab/uikit-workshop/dist/", + "patternlabFiles": { + "general-header": + "node_modules/@pattern-lab/uikit-workshop/views/partials/general-header.mustache", + "general-footer": + "node_modules/@pattern-lab/uikit-workshop/views/partials/general-footer.mustache", + "patternSection": + "node_modules/@pattern-lab/uikit-workshop/views/partials/patternSection.mustache", + "patternSectionSubtype": + "node_modules/@pattern-lab/uikit-workshop/views/partials/patternSectionSubtype.mustache", + "viewall": + "node_modules/@pattern-lab/uikit-workshop/views/viewall.mustache" + }, + "js": "./test/fixtures/source/js", + "images": "./test/fixtures/source/images", + "fonts": "./test/fixtures/source/fonts", + "css": "./test/fixtures/source/css/" + }, + "public": { + "root": "./test/fixtures/public/", + "patterns": "./test/fixtures/public/patterns/", + "data": "./test/fixtures/public/styleguide/data/", + "annotations": "./test/fixtures/public/annotations/", + "styleguide": "./test/fixtures/public/styleguide/", + "js": "./test/fixtures/public/js", + "images": "./test/fixtures/public/images", + "fonts": "./test/fixtures/public/fonts", + "css": "./test/fixtures/public/css" + } + }, + "patternExtension": "mustache", + "patternStateCascade": ["inprogress", "inreview", "complete"], + "patternExportDirectory": "./pattern_exports/", + "patternExportPatternPartials": [], + "serverOptions": { + "wait": 1000 + }, + "starterkitSubDir": "dist", + "styleGuideExcludes": [], + "theme": { + "color": "dark", + "density": "compact", + "layout": "horizontal" + } +} diff --git a/packages/cli/test/install-plugin.test.js b/packages/cli/test/install-plugin.test.js new file mode 100644 index 000000000..269953007 --- /dev/null +++ b/packages/cli/test/install-plugin.test.js @@ -0,0 +1,29 @@ +const tap = require('tap'); +const installPlugin = require('../bin/install-plugin'); +const wrapAsync = require('../bin/utils').wrapAsync; +const getUniqueProjectPath = require('./utils/getUniqueProjectPath'); +const moduleExist = require.resolve; + +const projectRoot = getUniqueProjectPath(); + +const minimalConfig = { + paths: { + source: { + root: projectRoot, + }, + }, +}; + +tap.test('Install plugin-tab ->', t => + wrapAsync(function*() { + yield installPlugin('@pattern-lab/plugin-tab', minimalConfig); + const pkg = yield moduleExist('@pattern-lab/plugin-tab'); + t.ok(pkg, 'module should exist after install'); + t.equal( + minimalConfig.plugins['@pattern-lab/plugin-tab'].enabled, + false, + 'and persist it on the patternlab-config.json' + ); + t.end(); + }) +); diff --git a/packages/cli/test/install-starterkit.test.js b/packages/cli/test/install-starterkit.test.js new file mode 100644 index 000000000..0cda685c9 --- /dev/null +++ b/packages/cli/test/install-starterkit.test.js @@ -0,0 +1,78 @@ +const tap = require('tap'); +const installStarterkit = require('../bin/install-starterkit'); +const wrapAsync = require('../bin/utils').wrapAsync; +const getUniqueProjectPath = require('./utils/getUniqueProjectPath'); +const moduleExist = require.resolve; + +const projectRoot = getUniqueProjectPath(); + +const minimalConfig = { + paths: { + source: { + root: projectRoot, + }, + }, +}; + +tap.test('Install starterkit-mustache-demo ->', t => + wrapAsync(function*() { + yield installStarterkit( + '@pattern-lab/starterkit-mustache-demo', + minimalConfig + ); + const pkg = yield moduleExist('@pattern-lab/starterkit-mustache-demo'); + t.ok(pkg, 'module should exist after install'); + t.end(); + }) +); + +tap.test('Install starterkit-mustache-base ->', t => + wrapAsync(function*() { + yield installStarterkit( + '@pattern-lab/starterkit-mustache-base', + minimalConfig + ); + const pkg = yield moduleExist('@pattern-lab/starterkit-mustache-base'); + t.ok(pkg, 'module should exist after install'); + t.end(); + }) +); + +tap.test('Install starterkit-mustache-bootstrap ->', t => + wrapAsync(function*() { + yield installStarterkit('starterkit-mustache-bootstrap', minimalConfig); + const pkg = yield moduleExist('starterkit-mustache-bootstrap'); + t.ok(pkg, 'module should exist after install'); + t.end(); + }) +); + +tap.test('Install starterkit-mustache-foundation ->', t => + wrapAsync(function*() { + yield installStarterkit('starterkit-mustache-foundation', minimalConfig); + const pkg = yield moduleExist('starterkit-mustache-foundation'); + t.ok(pkg, 'module should exist after install'); + t.end(); + }) +); + +tap.test('Install starterkit-mustache-acidtest ->', t => + wrapAsync(function*() { + yield installStarterkit('starterkit-mustache-acidtest', minimalConfig); + const pkg = yield moduleExist('starterkit-mustache-acidtest'); + t.ok(pkg, 'module should exist after install'); + t.end(); + }) +); + +tap.test('Install starterkit-mustache-materialdesign ->', t => + wrapAsync(function*() { + yield installStarterkit( + 'starterkit-mustache-materialdesign', + minimalConfig + ); + const pkg = yield moduleExist('starterkit-mustache-materialdesign'); + t.ok(pkg, 'module should exist after install'); + t.end(); + }) +); diff --git a/packages/cli/test/mocks/liverserver.mock.js b/packages/cli/test/mocks/liverserver.mock.js new file mode 100644 index 000000000..754fd5fc1 --- /dev/null +++ b/packages/cli/test/mocks/liverserver.mock.js @@ -0,0 +1,15 @@ +function liveServerMock() { + return { + reload: function() { + return true; + }, + refreshCSS: function() { + return true; + }, + start: function() { + return true; + }, + }; +} + +module.exports = liveServerMock; diff --git a/packages/cli/test/mocks/patternlab.mock.js b/packages/cli/test/mocks/patternlab.mock.js new file mode 100644 index 000000000..7186ca734 --- /dev/null +++ b/packages/cli/test/mocks/patternlab.mock.js @@ -0,0 +1,21 @@ +function patternLabMock() { + return { + build: function() { + return true; + }, + help: function() { + return true; + }, + patternsonly: function() { + return true; + }, + liststarterkits: function() { + return true; + }, + loadstarterkit: function() { + return true; + }, + }; +} + +module.exports = patternLabMock; diff --git a/packages/cli/test/replace_config_paths.test.js b/packages/cli/test/replace_config_paths.test.js new file mode 100644 index 000000000..6a98385ac --- /dev/null +++ b/packages/cli/test/replace_config_paths.test.js @@ -0,0 +1,44 @@ +const patternlab = require('@pattern-lab/core'); +const tap = require('tap'); +const replaceConfigPaths = require('../bin/replace-config'); +const config = patternlab.getDefaultConfig(); + +tap.test('replaceConfigPaths ->', t => { + const newConfig = replaceConfigPaths( + config, + 'projectDir', + 'sourceDir', + 'publicDir', + 'exportDir' + ); + for (const k of Object.keys(newConfig.paths.source)) { + if (k === 'patternlabFiles') { + for (const l of Object.keys(newConfig.paths.source[k])) { + t.ok( + /^projectDir\/sourceDir\/|^\.\/node_modules/.test( + newConfig.paths.source[k][l] + ), + `should be ok for newConfig.paths.source.${k}.${l}` + ); + } + } else { + t.ok( + /^projectDir\/sourceDir\/|^\.\/node_modules/.test( + newConfig.paths.source[k] + ), + `should be ok for newConfig.paths.source.${k}` + ); + } + } + for (const l of Object.keys(newConfig.paths.public)) { + t.ok( + /^projectDir\/publicDir\//.test(newConfig.paths.public[l]), + `should be ok for newConfig.paths.public.${l}` + ); + } + t.ok( + /^projectDir\/exportDir/.test(newConfig.patternExportDirectory), + `should be ok for newConfig.patternExportDirectory` + ); + t.end(); +}); diff --git a/packages/cli/test/resolve_config.test.js b/packages/cli/test/resolve_config.test.js new file mode 100644 index 000000000..36c250d9a --- /dev/null +++ b/packages/cli/test/resolve_config.test.js @@ -0,0 +1,18 @@ +const tap = require('tap'); +const wrapAsync = require('../bin/utils').wrapAsync; +const resolveConfig = require('../bin/resolve-config'); + +tap.test('resolveConfig ->', t => + wrapAsync(function*() { + const config = yield resolveConfig( + './test/fixtures/patternlab-config.json' + ); + const badConfig = yield resolveConfig(123); + const configNotFound = yield resolveConfig('./test/fixtures/some-config'); + t.type(config, 'object', 'should return a config of type object'); + t.ok(config.paths, 'config should have a paths property'); + t.notOk(badConfig, 'returns false when configPath is not of type string'); + t.notOk(configNotFound, 'returns false when configPath is not found'); + t.end(); + }) +); diff --git a/packages/cli/test/scaffold.test.js b/packages/cli/test/scaffold.test.js new file mode 100644 index 000000000..c63d2365d --- /dev/null +++ b/packages/cli/test/scaffold.test.js @@ -0,0 +1,31 @@ +const tap = require('tap'); +const path = require('path'); +const exists = require('path-exists'); +const scaffold = require('../bin/scaffold'); +const getUniqueProjectPath = require('./utils/getUniqueProjectPath'); +const wrapAsync = require('../bin/utils').wrapAsync; + +const projectDir = getUniqueProjectPath(); +const sourceDir = 'source'; +const publicDir = 'public'; +const exportDir = 'patterns_export'; + +tap.test('Scaffold ->', t => + wrapAsync(function*() { + yield scaffold(projectDir, sourceDir, publicDir, exportDir); + t.ok(exists.sync(path.resolve(projectDir)), 'should create project dir'); + t.ok( + exists.sync(path.resolve(projectDir, sourceDir)), + 'should create source dir' + ); + t.ok( + exists.sync(path.resolve(projectDir, publicDir)), + 'should create public dir' + ); + t.ok( + exists.sync(path.resolve(projectDir, exportDir)), + 'should create export dir' + ); + t.end(); + }) +); diff --git a/packages/cli/test/serve.test.js b/packages/cli/test/serve.test.js new file mode 100644 index 000000000..c322cc41d --- /dev/null +++ b/packages/cli/test/serve.test.js @@ -0,0 +1,51 @@ +const proxyquire = require('proxyquire'); +const tap = require('tap'); +const _ = require('lodash'); +const resolveConfig = require('../bin/resolve-config'); +const patternLabMock = require('./mocks/patternlab.mock.js'); +const wrapAsync = require('../bin/utils').wrapAsync; + +// Require preview but mock patternlab so that we only test the module behavior +const preview = proxyquire('../bin/serve', { + '@pattern-lab/core': patternLabMock, +}); + +tap.test('Serve ->', t => + wrapAsync(function*() { + const config = yield resolveConfig( + './test/fixtures/patternlab-config.json' + ); + config.paths.source.root = undefined; + t.throws( + () => { + preview(); + }, + {}, + 'throws when config is empty' + ); + t.throws( + () => { + preview(123); + }, + {}, + 'throws when config is not of type object' + ); + t.throws( + () => { + _.unset(config, 'paths.source.root'); + preview(config); + }, + {}, + 'throws when no source root dir is set on config' + ); + t.throws( + () => { + _.unset(config, 'paths.public.root'); + preview(config); + }, + {}, + 'throws when no public root dir is set on config' + ); + t.end(); + }) +); diff --git a/packages/cli/test/utils/getUniqueProjectPath.js b/packages/cli/test/utils/getUniqueProjectPath.js new file mode 100644 index 000000000..0ac892b19 --- /dev/null +++ b/packages/cli/test/utils/getUniqueProjectPath.js @@ -0,0 +1,6 @@ +const crypto = require('crypto'); + +module.exports = () => { + const UUID = crypto.randomBytes(16).toString('hex'); + return `./tmp/${UUID}`; +}; diff --git a/packages/cli/test/utils/spawnCmd.js b/packages/cli/test/utils/spawnCmd.js new file mode 100644 index 000000000..4d0623515 --- /dev/null +++ b/packages/cli/test/utils/spawnCmd.js @@ -0,0 +1,13 @@ +const path = require('path'); +const spawn = require('execa'); +const wrapAsync = require('../../bin/utils').wrapAsync; +const cli = path.resolve(__dirname, '../../bin/patternlab.js'); + +const spawnCmd = (args, endFn) => + wrapAsync(function*() { + const fn = endFn || function() {}; + yield spawn('node', [cli].concat(args)); + fn(); + }); + +module.exports = spawnCmd; diff --git a/packages/core/.eslintrc.js b/packages/core/.eslintrc.js new file mode 100644 index 000000000..5a2cc7f1e --- /dev/null +++ b/packages/core/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: ['../../.eslintrc.js'], +}; diff --git a/packages/core/.npmrc b/packages/core/.npmrc new file mode 100644 index 000000000..ce73f58bf --- /dev/null +++ b/packages/core/.npmrc @@ -0,0 +1,2 @@ +package-lock=false +save-exact=true diff --git a/packages/core/.nvmrc b/packages/core/.nvmrc new file mode 100644 index 000000000..95c4e8d27 --- /dev/null +++ b/packages/core/.nvmrc @@ -0,0 +1 @@ +10.0.0 \ No newline at end of file diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md new file mode 100644 index 000000000..dc3dbfe77 --- /dev/null +++ b/packages/core/CHANGELOG.md @@ -0,0 +1,331 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [5.9.3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.9.2...v5.9.3) (2020-05-01) + +**Note:** Version bump only for package @pattern-lab/core + + + + + +# [5.9.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.8.0...v5.9.0) (2020-04-24) + + +### Bug Fixes + +* **core:** do not warn about uikit-polyfills ([6bb68e7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/6bb68e763769969546542bf7aaf6d1f4235c6622)) + + + + + +# [5.7.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.6.0...v5.7.0) (2020-02-17) + +**Note:** Version bump only for package @pattern-lab/core + + + + + + +# [5.4.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.3.3...v5.4.0) (2019-11-26) + + +### Bug Fixes + +* add a new method to check if PL is currently compiling + add new method to get the config PL is using ([26e886c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/26e886c93db5d135c91de648724f7278c4d5b3e9)) + + + + + +# [5.3.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.2.0...v5.3.0) (2019-11-13) + + +### Bug Fixes + +* **core:** re-add cleanPublic fix ([c100bbc](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/c100bbca3f339e9132acb9c482e98c1c8a66b8b5)) + + + + + +# [5.1.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v5.0.2...v5.1.0) (2019-10-29) + + +### Features + +* **config:** add new default pattern export options ([a7487a0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/a7487a0681cb11e6f3c5c8eaefd62e5648ad5ea3)) +* **core:** fix pattern export all conflicts ([b210d82](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/b210d820ba8ac0b64c82c7ff0f18c9f8a900fce2)) + + + + + +# [5.0.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25) + + +### Bug Fixes + +* add eslint fixes ([00d7bbe](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/00d7bbe319ea77a6ee8cc9cd0348856feaaf13ad)) +* correct typo in build logging ([96d989f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/96d989f8869630ba9f59705bfca66755f20e35ab)) +* updates to address eslint / prettier issues ([d945acc](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/d945acc13b8e4e36f3815b017fbc12266c323d1f)) +* updates to fix eslint / prettier issues; update packages/core to reuse root .eslintrc.js file ([5b7a057](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5b7a057d46ccd16b5832af1441030c7b76f237a8)) +* **1049:** Treat folders like patterns only if they're subfolders of pattern groupings ([4eb79ab](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/4eb79ab48b335a35b2e5ed3b7053974b8e8bb6b6)) +* **core:** allow plugin resolution to follow normal algorithm ([3f6b83b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/3f6b83be080c88aec1d8b73bececb76f0f57a79d)) +* **core:** find plugins from config only and with simpler args ([fe7351c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/fe7351cba346425512cbb2ef3a1b7728ab06ae60)) +* **plugin:** correct spelling error and function locations ([d4abd88](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/d4abd88cb017550002407241b5045a2ad1adb1dc)) + + +### Features + +* **core:** invoke registered plugin hooks ([a54d775](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/a54d7753b6939fe6a58da543f4fb34f64dd8901a)) +* **plugin-tab, core:** initial plugin hook exploration ([2f3d39a](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/2f3d39ac6b125ad4c6b872e27ee224ce2ea33a12)) + + +### BREAKING CHANGES + +* **core:** plugins now use async functions instead of events + + + + + + +## [3.0.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.1...@pattern-lab/core@3.0.2) (2019-08-23) + + +### Bug Fixes + +* add eslint fixes ([00d7bbe](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/00d7bbe)) +* correct typo in build logging ([96d989f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/96d989f)) +* updates to address eslint / prettier issues ([d945acc](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/d945acc)) +* updates to fix eslint / prettier issues; update packages/core to reuse root .eslintrc.js file ([5b7a057](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5b7a057)) + + + + + + +## [3.0.1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.1-alpha.0...@pattern-lab/core@3.0.1) (2019-05-16) + +**Note:** Version bump only for package @pattern-lab/core + + + + + +# [3.0.0-beta.2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-beta.0...@pattern-lab/core@3.0.0-beta.2) (2019-02-09) + + +### Bug Fixes + +* **cli:** pass watch options cleanly to core ([8bf186b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/8bf186b)) +* **docs:** regenerate API documentation ([830c568](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/830c568)) +* **patterns:** find all patterns inlcuding pseudo patterns ([d0672f6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/d0672f6)), closes [#975](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/975) +* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/98dfadf)) + + + + + + +# [3.0.0-beta.0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.16...@pattern-lab/core@3.0.0-beta.0) (2018-09-07) + + +### Bug Fixes + +* **docs:** update event info with tab example ([0f227a3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/0f227a3)) +* **package:** Allow .json extension on annotations file (issue [#836](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/836)) ([b92e62b](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/b92e62b)) +* **package:** update tap dependency ([2b70ff4](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/2b70ff4)) +* **plugins:** support scoped packages ([44f0f8e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/44f0f8e)) + + +### Features + +* **API:** remove reliance on patternlab object during plugin install ([0850fd6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/0850fd6)) +* **core:** remove plugin install / disable / enable logic ([5a58824](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5a58824)), closes [#872](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/872) + + + + + + + +# [3.0.0-alpha.16](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.15...@pattern-lab/core@3.0.0-alpha.16) (2018-07-06) + +**Note:** Version bump only for package @pattern-lab/core + + + +# [3.0.0-alpha.15](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.14...@pattern-lab/core@3.0.0-alpha.15) (2018-07-06) + +### Features + +* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/55f5bc2)) +* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/415698e)) +* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5ab3995)) + + + +# [3.0.0-alpha.14](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.13...@pattern-lab/core@3.0.0-alpha.14) (2018-07-05) + +### Bug Fixes + +* **core:** rename serverModule import to avoid conflict with CLI ([f3170e7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/f3170e7)) +* **server:** remove setInterval hack ([a76e4a2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/a76e4a2)) +* **uikits:** fix generation of view all pages within uikits ([7d6bdce](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/7d6bdce)) +* **viewall:** fix viewall generation ([543558a](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/543558a)) +* **watch:** wire up serve and watch listeners correctly ([04cd18e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/04cd18e)) + +### Features + +* **events:** add PATTERNLAB_BUILD_END event and rename BUILD_START ([5b7bfa3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5b7bfa3)) +* **help:** remove help. API is now documented ([2aef3a1](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/2aef3a1)) +* **server:** beginning of refator ([a3d65c3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/a3d65c3)) +* **server:** continue server refactor ([8f6cd91](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/8f6cd91)) +* **watches:** add additional assets to ignore ([18e74c2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/18e74c2)) + + + +# [3.0.0-alpha.13](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.12...@pattern-lab/core@3.0.0-alpha.13) (2018-05-04) + +### Features + +* **api:** expose getVersion statically ([4683cd0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/4683cd0)) + + + +# [3.0.0-alpha.12](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.11...@pattern-lab/core@3.0.0-alpha.12) (2018-05-04) + +### Bug Fixes + +* **build:** improve stability of changes causing a live-server reload ([06c6123](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/06c6123)) +* **buildPatterns:** move meta processing back into function for now ([cea2c45](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/cea2c45)) +* **pattern graph:** move support and coverage of graph file to root ([bb9ef3c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/bb9ef3c)) +* **pattern watch:** Defensively add change listeners ([cdbd11f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/cdbd11f)) +* **test:** fix the test please and thank you ([cdc6c38](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/cdc6c38)) +* **test:** sledgehammer a test ([8b34be0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/8b34be0)) +* **tests:** prevent dependency graph output file from being written ([0d9c57e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/0d9c57e)) +* **uikits:** fix ui_builder_tests ([e75f434](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/e75f434)) + +### Features + +* **API:** standardize v() and version() into a single call ([6309e69](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/6309e69)) +* **uikits:** add uikits to test config ([43a2017](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/43a2017)) +* **uikits:** additional test coverage ([f5b60b2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/f5b60b2)) +* **uikits:** additional test coverage of the main API ([fbcacfb](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/fbcacfb)) +* **uikits:** clean each build directory if configured ([8e11342](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/8e11342)) +* **uikits:** copy pattern-specific javascript ([3ac93dc](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/3ac93dc)) +* **uikits:** create MVP output to disk ([e1598d3](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/e1598d3)) +* **uikits:** filter out excluded pattern states from uikit output ([87c9d0d](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/87c9d0d)) +* **uikits:** load uikits before build ([4565202](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/4565202)) +* **uikits:** output assets and annotations to each location ([b0a84ca](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/b0a84ca)) +* **uikits:** output pattern files to each location ([5df87b0](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5df87b0)) +* **uikits:** promote dependencyGraph.json output to root ([dd3e708](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/dd3e708)) +* **uikits:** render header and footer data correctly ([f2a6f23](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/f2a6f23)) +* **uikits:** support incremental builds ([6670364](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/6670364)) +* **uikits:** support watched-asset copying ([4f05311](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/4f05311)) +* **uikits:** uikits config to default ([a393851](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/a393851)) + +### BREAKING CHANGES + +* **API:** change `version()` to return a string representation of the version, removing `v()` + + + +# [3.0.0-alpha.11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.10...@pattern-lab/core@3.0.0-alpha.11) (2018-03-21) + +### Bug Fixes + +* **changes_hunter:** guard for incrementalRebuild while watching ([c652b9c](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/c652b9c)), closes [#794](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/794) [#802](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/802) +* **data_loader:** look for exact name of the file passed in ([eb46be2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/eb46be2)) +* **get:** add internal ability to omit missing pattern warning ([e3dddc6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/e3dddc6)), closes [#786](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/786) +* **lint:** run code through prettier ([ca52fde](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/ca52fde)), closes [#825](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/issues/825) +* **package:** update LICENSE ([337aa32](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/337aa32)) +* **tests:** Revert annotations file back to expected legacy format ([3618f27](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/3618f27)) + +### Features + +* **package:** standardize and hoist common devDependencies ([7f4ce6f](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/7f4ce6f)) + + + +# [3.0.0-alpha.10](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/compare/@pattern-lab/core@3.0.0-alpha.9...@pattern-lab/core@3.0.0-alpha.10) (2018-03-05) + +### Bug Fixes + +* **config:** Add npm registry to lerna config ([1473cd5](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/1473cd5)) +* **config:** update patch to uikit files ([5ccd0d2](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5ccd0d2)) +* **package:** clarify description of package ([c65611e](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/c65611e)) + +### Features + +* **README:** Update for brevity and consistency ([aec7c50](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/aec7c50)) + + + +# 3.0.0-alpha.9 (2018-03-02) + +### Bug Fixes + +* **core:** Fix tests ([31d67a7](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/31d67a7)) +* **packages:** Allow scoped publishing ([58beeb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/58beeb6)) + +### Features + +* **core:** Add tests for help command ([62cd8fb](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/62cd8fb)) +* **package:** Hoist up tap and test command. ([6cacdb6](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/6cacdb6)) +* **packages:** Update all package.json repo and bug links ([5eb2c11](https://github.com/pattern-lab/patternlab-node/tree/master/packages/core/commit/5eb2c11)) + + + +# [3.0.0-alpha.8](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-alpha.7...v3.0.0-alpha.8) (2018-02-22) + +### Bug Fixes + +* **asset copy:** Resolve paths correctly. Break apart files & asyncify ([379419c](https://github.com/pattern-lab/patternlab-node/commit/379419c)) +* **event emission:** Commit failing test ([12ccbd7](https://github.com/pattern-lab/patternlab-node/commit/12ccbd7)) +* **event emission:** Only listen once to changes ([ea6b7d3](https://github.com/pattern-lab/patternlab-node/commit/ea6b7d3)) +* **loadPattern:** Check proper data file paths for modification ([b7ba5b0](https://github.com/pattern-lab/patternlab-node/commit/b7ba5b0)) +* **package** Update dependencies to reduce vulnerabilities ([367d38f](https://github.com/pattern-lab/patternlab-node/commit/367d38f)) +* **package:** Update gitignore and npmignore with current files ([581b3c4](https://github.com/pattern-lab/patternlab-node/commit/581b3c4)) +* **serve:** Reference events by constants ([9f5c143](https://github.com/pattern-lab/patternlab-node/commit/9f5c143)) +* **test configuration:** Remove vestigial configuration entries ([481fce9](https://github.com/pattern-lab/patternlab-node/commit/481fce9)) +* **watchPatternLabFiles:** Register and manager watchers ([48f0190](https://github.com/pattern-lab/patternlab-node/commit/48f0190)) + +### Features + +* **docs:** Add jsdoc output to public API and events ([d45e7b9](https://github.com/pattern-lab/patternlab-node/commit/d45e7b9)) +* **docs:** Experiment with doc generation ([8e1808b](https://github.com/pattern-lab/patternlab-node/commit/8e1808b)) +* **index:** Make the cleaning of public/ an asynchronous adventure ([bd485d2](https://github.com/pattern-lab/patternlab-node/commit/bd485d2)) +* **package:** Communicate official node support ([96ca87f](https://github.com/pattern-lab/patternlab-node/commit/96ca87f)) +* **pattern lab:** Copy pattern-specific js ([99bfc02](https://github.com/pattern-lab/patternlab-node/commit/99bfc02)) +* **pattern lab:** Pass `patternPartial` as data to render ([351ea5e](https://github.com/pattern-lab/patternlab-node/commit/351ea5e)) + + + +# [3.0.0-alpha.7](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-alpha.6...v3.0.0-alpha.7) (2018-01-19) + +### Bug Fixes + +* **lint:** Manually resolve final lint issues ([7cad1f1](https://github.com/pattern-lab/patternlab-node/commit/7cad1f1)) +* **README:** Fix npm link instructions ([ce3a7f0](https://github.com/pattern-lab/patternlab-node/commit/ce3a7f0)) +* **README:** Update npm shield to point to scoped package ([1f62617](https://github.com/pattern-lab/patternlab-node/commit/1f62617)), closes [#760](https://github.com/pattern-lab/patternlab-node/issues/760) +* **unit test:** Fix path to fixture ([b932f14](https://github.com/pattern-lab/patternlab-node/commit/b932f14)) + +### Features + +* **Contributing:** Update contributing info with prettier ([2a0ce52](https://github.com/pattern-lab/patternlab-node/commit/2a0ce52)) +* **list_item_hunter:** Re-work algorithm ([1ac77a7](https://github.com/pattern-lab/patternlab-node/commit/1ac77a7)) +* **package:** Add prettier ([b8e3e11](https://github.com/pattern-lab/patternlab-node/commit/b8e3e11)) +* **package:** Add prettier precommit hook ([a0b85b5](https://github.com/pattern-lab/patternlab-node/commit/a0b85b5)) +* **package:** Add standard version ([b2ba31c](https://github.com/pattern-lab/patternlab-node/commit/b2ba31c)) +* **README:** Add prettier badge ([7c2787b](https://github.com/pattern-lab/patternlab-node/commit/7c2787b)) + +--- + +Older releases found at https://github.com/pattern-lab/patternlab-node/wiki/ChangeLog diff --git a/packages/core/LICENSE b/packages/core/LICENSE new file mode 100644 index 000000000..c9b8c1daa --- /dev/null +++ b/packages/core/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2018 Brian Muenzenmeyer, http://brianmuenzenmeyer.com & Brad Frost, http://bradfrostweb.com + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 000000000..f6a81c7bd --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,120 @@ +![Pattern Lab Logo](https://github.com/pattern-lab/patternlab-node/raw/master/patternlab.png 'Pattern Lab Logo') + +[![Build Status](https://travis-ci.org/pattern-lab/patternlab-node.svg?branch=master)](https://travis-ci.org/pattern-lab/patternlab-node) +![current release](https://img.shields.io/npm/v/@pattern-lab/core.svg) +![license](https://img.shields.io/github/license/pattern-lab/patternlab-node.svg) +[![Coverage Status](https://coveralls.io/repos/github/pattern-lab/patternlab-node/badge.svg?branch=master)](https://coveralls.io/github/pattern-lab/patternlab-node?branch=master) +[![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) +[![node (scoped)](https://img.shields.io/node/v/@pattern-lab/patternlab-node.svg)]() +[![Join the chat at Gitter](https://badges.gitter.im/pattern-lab/node.svg)](https://gitter.im/pattern-lab/node) + +# Pattern Lab Node Core + +This is the core API and orchestrator of the [Pattern Lab ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). + +## Installation + +Pattern Lab Node can be used different ways. Editions are **example** pairings of Pattern Lab code and do not always have an upgrade path or simple means to run as a dependency within a larger project. Users wishing to be most current and have the greatest flexibility are encouraged to consume `patternlab-node` directly. Users wanting to learn more about Pattern Lab and have a tailored default experience are encouraged to start with an Edition. Both methods still expect to interact with other elements of the [Pattern Lab Ecosystem](#ecosystem). + +### Direct Consumption + +As of Pattern Lab Node 3.X, `patternlab-node` can run standalone, without the need for task runners like gulp or grunt. + +`npm install @pattern-lab/core` + +See [Usage](#usage) for more information. + +### Editions + +For users wanting a more pre-packaged experience several editions are available. + +* [Pattern Lab/Node: Vanilla Edition](https://github.com/pattern-lab/patternlab-node/tree/dev/packages/edition-node) contains info how to get started within a pure node environment. + +* [Pattern Lab/Node: Gulp Edition](https://github.com/pattern-lab/patternlab-node/tree/dev/packages/edition-node-gulp) contains info how to get started within a Gulp task running environment. + + +## Ecosystem + +![Pattern Lab Ecosystem](http://patternlab.io/assets/pattern-lab-2-image_18-large-opt.png) + +Core, and Editions, are part of the [Pattern Lab Ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). With this architecture, we encourage people to write and maintain their own Editions, Starterkits, and even PatternEngines. + +## Usage + +`@pattern-lab/core` can be required within any Node environment, taking in a configuration file at instantiation. + +```javascript +const config = require('./patternlab-config.json'); +const patternlab = require('@pattern-lab/core')(config); + +// build, optionally watching or choosing incremental builds +patternlab.build({ + cleanPublic: true, + watch: true, +}); + +// or build, watch, and then self-host +patternlab.serve({ + cleanPublic: true, +}); +``` + +* Read more about [configuration](http://patternlab.io/docs/advanced-config-options.html#node) via `patternlab-config.json`. + +* Read more about the rest of [Public API](./docs), and already implemented for you within [Editions](#editions). + +* A full-featured [command line interface](https://github.com/pattern-lab/patternlab-node/tree/dev/packages/cli) is also available. + +### Events + +Many [events](./docs/events.md) are emitted during Pattern Lab operations, originally built to support plugins. Below is a sample, allowing users to be informed of asset or pattern changes. + +```javascript +patternlab.serve(...); + +patternlab.events.on('patternlab-asset-change', (data) => { + console.log(data); // {file: 'path/to/file.css', dest: 'path/to/destination'} +}); + +patternlab.events.on('patternlab-pattern-change', (data) => { + console.log(data); // {file: 'path/to/file.ext'} +}); + +patternlab.events.on('patternlab-global-change', (data) => { + console.log(data); // {file: 'path/to/file.ext'} +}); +``` + +## Development Installation / Workflow + +If you are interested in contributing to Pattern Lab, please do take some time to learn how we [develop locally](https://github.com/pattern-lab/patternlab-node/blob/master/.github/CONTRIBUTING.md#developing-locally) within the contribution guidelines. +## Upgrading + +If you find yourself here and are looking to upgrade, check out how to upgrade from version to version of Pattern Lab Node here: [https://github.com/pattern-lab/patternlab-node/wiki/Upgrading](https://github.com/pattern-lab/patternlab-node/wiki/Upgrading) + +View the [latest releases](https://github.com/pattern-lab/patternlab-node/releases) for comprehensive changelogs. + +## Contributing + +If you'd like to contribute to Pattern Lab Node, please do so! There is always a lot of ground to cover and something for your wheelhouse. + +Please read the [contribution guidelines](https://github.com/pattern-lab/patternlab-node/blob/master/.github/CONTRIBUTING.md). + +## Core Team + +* [@bmuenzenmeyer](https://github.com/bmuenzenmeyer) - Lead Maintainer +* [@geoffp](https://github.com/geoffp) - Core Contributor +* [@raphaelokon](https://github.com/raphaelokon) - CLI Contributor +* [@tburny](https://github.com/tburny) - Core Contributor + +## Community + +The Pattern Lab Node team uses [our gitter.im channel, pattern-lab/node](https://gitter.im/pattern-lab/node) to keep in sync, share updates, and talk shop. Please stop by to say hello or as a first place to turn if stuck. Other channels in the Pattern Lab organization can be found on gitter too. + +There is also a dedicated Pattern Lab channel on the [design system slack](http://designsystems.herokuapp.com) run by [@jina](https://twitter.com/jina). + +Ask or answer Pattern Lab questions on Stack Overflow: http://stackoverflow.com/questions/tagged/patternlab.io + +## License + +[MIT](https://github.com/pattern-lab/patternlab-node/blob/master/LICENSE) diff --git a/packages/core/docs/README.md b/packages/core/docs/README.md new file mode 100644 index 000000000..7317083f6 --- /dev/null +++ b/packages/core/docs/README.md @@ -0,0 +1,198 @@ +# Pattern Lab Node API + +[![Join the chat at Gitter](https://badges.gitter.im/pattern-lab/node.svg)](https://gitter.im/pattern-lab/node) + +## [Installation](https://github.com/pattern-lab/patternlab-node#installation) + +## Usage + +`patternlab-node` can be required within any Node environment, taking in a configuration file at instantiation. + +``` javascript +const config = require('./patternlab-config.json'); +const patternlab = require('@pattern-lab/core')(config); +``` + + + +## `patternlab` : object +Build thoughtful, pattern-driven user interfaces using atomic design principles. +Many of these functions are exposed to users within [Editions](https://github.com/pattern-lab/patternlab-node#editions), but [direct consumption](https://github.com/pattern-lab/patternlab-node#direct-consumption) is also encouraged. + +**Kind**: global namespace +**See** + +- [patternlab.io](patternlab.io) for more documentation. +- [https://github.com/pattern-lab/patternlab-node](https://github.com/pattern-lab/patternlab-node) for code, issues, and releases + +**License**: MIT + +* [`patternlab`](#patternlab) : object + * _instance_ + * [`.version`](#patternlab+version) ⇒ string + * [`.build`](#patternlab+build) ⇒ Promise + * [`.getDefaultConfig`](#patternlab+getDefaultConfig) ⇒ object + * [`.getSupportedTemplateExtensions`](#patternlab+getSupportedTemplateExtensions) ⇒ Array.<string> + * [`.installplugin`](#patternlab+installplugin) ⇒ void + * [`.liststarterkits`](#patternlab+liststarterkits) ⇒ Promise + * [`.loadstarterkit`](#patternlab+loadstarterkit) ⇒ void + * [`.patternsonly`](#patternlab+patternsonly) ⇒ Promise + * _static_ + * [`.getDefaultConfig`](#patternlab.getDefaultConfig) ⇒ object + * [`.getVersion`](#patternlab.getVersion) ⇒ string + * [`.server`](#patternlab.server) : object + * [`.serve(options)`](#patternlab.server.serve) ⇒ Promise + * [`.reload()`](#patternlab.server.reload) ⇒ Promise + * [`.refreshCSS()`](#patternlab.server.refreshCSS) ⇒ Promise + * [`.events`](#patternlab.events) : EventEmitter + + + +### `patternlab.version` ⇒ string +Returns current version + +**Kind**: instance property of [patternlab](#patternlab) +**Returns**: string - current patternlab-node version as defined in `package.json`, as string + + +### `patternlab.build` ⇒ Promise +Builds patterns, copies assets, and constructs user interface + +**Kind**: instance property of [patternlab](#patternlab) +**Returns**: Promise - a promise fulfilled when build is complete +**Emits**: event:PATTERNLAB_BUILD_START, event:PATTERNLAB_BUILD_END +**See**: [all events](./events.md) + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| options | object | | an object used to control build behavior | +| [options.cleanPublic] | bool | true | whether or not to delete the configured output location (usually `public/`) before build | +| [options.data] | object | {} | additional data to be merged with global data prior to build | +| [options.watch] | bool | true | whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild | + + + +### `patternlab.getDefaultConfig` ⇒ object +Returns the standardized default config used to run Pattern Lab. This method can be called statically or after instantiation. + +**Kind**: instance property of [patternlab](#patternlab) +**Returns**: object - Returns the object representation of the `patternlab-config.json` + + +### `patternlab.getSupportedTemplateExtensions` ⇒ Array.<string> +Returns all file extensions supported by installed PatternEngines + +**Kind**: instance property of [patternlab](#patternlab) +**Returns**: Array.<string> - all supported file extensions + + +### `patternlab.installplugin` ⇒ void +Installs plugin already available via `node_modules/` + +**Kind**: instance property of [patternlab](#patternlab) + +| Param | Type | Description | +| --- | --- | --- | +| pluginName | string | name of plugin | + + + +### `patternlab.liststarterkits` ⇒ Promise +Fetches starterkit repositories from pattern-lab github org that contain 'starterkit' in their name + +**Kind**: instance property of [patternlab](#patternlab) +**Returns**: Promise - Returns an Array<{name,url}> for the starterkit repos + + +### `patternlab.loadstarterkit` ⇒ void +Loads starterkit already available via `node_modules/` + +**Kind**: instance property of [patternlab](#patternlab) + +| Param | Type | Description | +| --- | --- | --- | +| starterkitName | string | name of starterkit | +| clean | boolean | whether or not to delete contents of source/ before load | + + + +### `patternlab.patternsonly` ⇒ Promise +Builds patterns only, leaving existing user interface files intact + +**Kind**: instance property of [patternlab](#patternlab) +**Returns**: Promise - a promise fulfilled when build is complete + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| [options.cleanPublic] | bool | true | whether or not to delete the configured output location (usually `public/`) before build | +| [options.data] | object | {} | additional data to be merged with global data prior to build | +| [options.watch] | bool | true | whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild | + + + +### `patternlab.getDefaultConfig` ⇒ object +Static method that returns the standardized default config used to run Pattern Lab. This method can be called statically or after instantiation. + +**Kind**: static property of [patternlab](#patternlab) +**Returns**: object - Returns the object representation of the `patternlab-config.json` + + +### `patternlab.getVersion` ⇒ string +Static method that returns current version + +**Kind**: static property of [patternlab](#patternlab) +**Returns**: string - current @pattern-lab/core version as defined in `package.json` + + +### `patternlab.server` : object +Server module + +**Kind**: static property of [patternlab](#patternlab) + +* [`.server`](#patternlab.server) : object + * [`.serve(options)`](#patternlab.server.serve) ⇒ Promise + * [`.reload()`](#patternlab.server.reload) ⇒ Promise + * [`.refreshCSS()`](#patternlab.server.refreshCSS) ⇒ Promise + + + +#### `server.serve(options)` ⇒ Promise +Build patterns, copies assets, and constructs user interface. Watches configured `source/` directories, and serves all output locally + +**Kind**: static method of [server](#patternlab.server) +**Returns**: Promise - a promise fulfilled when build is complete + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| options | object | | an object used to control build behavior | +| [options.cleanPublic] | bool | true | whether or not to delete the configured output location (usually `public/`) before build | +| [options.data] | object | {} | additional data to be merged with global data prior to build | +| [options.watch] | bool | true | whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild | + + + +#### `server.reload()` ⇒ Promise +Reloads any active live-server instances + +**Kind**: static method of [server](#patternlab.server) +**Returns**: Promise - a promise fulfilled when operation is complete + + +#### `server.refreshCSS()` ⇒ Promise +Reloads CSS on any active live-server instances + +**Kind**: static method of [server](#patternlab.server) +**Returns**: Promise - a promise fulfilled when operation is complete + + +### `patternlab.events` : EventEmitter +**Kind**: static property of [patternlab](#patternlab) +**See** + +- [EventEmitter](https://nodejs.org/api/events.html#events_class_eventemitter) +- [All Pattern Lab events](./events.md) + + +* * * + +[Pattern Lab](http://patternlab.io) Node is [MIT Licensed](https://github.com/pattern-lab/patternlab-node/blob/master/LICENSE) diff --git a/packages/core/docs/events.md b/packages/core/docs/events.md new file mode 100644 index 000000000..2df8191d1 --- /dev/null +++ b/packages/core/docs/events.md @@ -0,0 +1,159 @@ +# Pattern Lab Node Events + +[![Join the chat at Gitter](https://badges.gitter.im/pattern-lab/node.svg)](https://gitter.im/pattern-lab/node) + +Pattern Lab emits numerous events during the [build](../docs/) process. Some uses of events: + +* Core uses `patternlab-pattern-change` events when watching for changes in order to trigger another build +* Plugins such as [plugin-tab](https://github.com/pattern-lab/patternlab-node/tree/master/packages/plugin-tab) can use an event like `patternlab-pattern-write-end` to define additional code tabs to the pattern viewer / modal + +Learn more about [Creating Plugins](https://github.com/pattern-lab/patternlab-node/wiki/Creating-Plugins). + + + +## Events + + + +### `EVENTS` ⏏ + +**Kind**: Exported constant + + +#### `EVENTS~PATTERNLAB_BUILD_START` + +Emitted before any logic run inside `build()`, which is the entry point for single builds, pattern-only builds, run singly or when watched. + +**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS) +**Properties** + +| Name | Type | Description | +| ---------- | ------------------- | ----------------- | +| patternlab | object | global data store | + + + +#### `EVENTS~PATTERNLAB_BUILD_END` + +Emitted after all logic run inside `build()`, which is the entry point for single builds, pattern-only builds, run singly or when watched. + +**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS) +**Properties** + +| Name | Type | Description | +| ---------- | ------------------- | ----------------- | +| patternlab | object | global data store | + + + +#### `EVENTS~PATTERNLAB_PATTERN_ITERATION_END` + +Emitted after patterns are iterated over to gather data about them. Right before Pattern Lab processes and renders patterns into HTML + +**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS) +**Properties** + +| Name | Type | Description | +| ---------- | ------------------- | ----------------- | +| patternlab | object | global data store | + + + +#### `EVENTS~PATTERNLAB_BUILD_GLOBAL_DATA_END` + +Emitted after global `data.json` and `listitems.json` are read, and the supporting Pattern Lab templates are loaded into memory (header, footer, patternSection, patternSectionSubType, viewall). Right before patterns are iterated over to gather data about them. + +**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS) +**Properties** + +| Name | Type | Description | +| ---------- | ------------------- | ----------------- | +| patternlab | object | global data store | + + + +#### `EVENTS~PATTERNLAB_PATTERN_BEFORE_DATA_MERGE` + +Emitted before all data is merged prior to a Pattern's render. Global `data.json` is merged with any pattern `.json`. Global `listitems.json` is merged with any pattern `.listitems.json`. + +**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS) +**See**: [Pattern](https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16) +**Properties** + +| Name | Type | Description | +| ---------- | -------------------- | ----------------- | +| patternlab | object | global data store | +| pattern | Pattern | current pattern | + + + +#### `EVENTS~PATTERNLAB_PATTERN_WRITE_BEGIN` + +Emitted before a pattern's template, HTML, and encoded HTML files are written to their output location + +**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS) +**See**: [Pattern](https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16) +**Properties** + +| Name | Type | Description | +| ---------- | -------------------- | ----------------- | +| patternlab | object | global data store | +| pattern | Pattern | current pattern | + + + +#### `EVENTS~PATTERNLAB_PATTERN_WRITE_END` + +Emitted after a pattern's template, HTML, and encoded HTML files are written to their output location + +**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS) +**See**: [Pattern](https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16) +**Properties** + +| Name | Type | Description | +| ---------- | -------------------- | ----------------- | +| patternlab | object | global data store | +| pattern | Pattern | current pattern | + + + +#### `EVENTS~PATTERNLAB_PATTERN_ASSET_CHANGE` + +Invoked when a watched asset changes. Assets include anything in `source/` that is not under `['root', 'patterns', 'data', 'meta', 'annotations', 'patternlabFiles']` which are blacklisted for specific copying. + +**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS) +**Properties** + +| Name | Type | Description | +| -------- | ------------------- | --------------------------------------------------------- | +| fileInfo | object | `{file: 'path/to/file.css', dest: 'path/to/destination'}` | + + + +#### `EVENTS~PATTERNLAB_GLOBAL_CHANGE` + +Invoked when a watched global file changes. These are files within the directories specified in `['data', 'meta']`paths. + +**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS) +**Properties** + +| Name | Type | Description | +| -------- | ------------------- | ---------------------------- | +| fileInfo | object | `{file: 'path/to/file.ext'}` | + + + +#### `EVENTS~PATTERNLAB_PATTERN_CHANGE` + +Invoked when a pattern changes. + +**Kind**: inner property of [EVENTS](#exp_module_Events--EVENTS) +**Properties** + +| Name | Type | Description | +| -------- | ------------------- | ---------------------------- | +| fileInfo | object | `{file: 'path/to/file.ext'}` | + +--- + +[Pattern Lab](http://patternlab.io) Node is [MIT Licensed](https://github.com/pattern-lab/patternlab-node/blob/master/LICENSE) diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 000000000..866ea172d --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,79 @@ +{ + "name": "@pattern-lab/core", + "description": "Create atomic design systems with Pattern Lab. This is the core API and orchestrator of the ecosystem.", + "version": "5.9.3", + "main": "./src/index.js", + "dependencies": { + "@pattern-lab/engine-mustache": "^5.0.0", + "@pattern-lab/live-server": "^5.9.3", + "chalk": "1.1.3", + "chokidar": "1.7.0", + "dive": "0.5.0", + "fs-extra": "5.0.0", + "glob": "7.0.0", + "graphlib": "2.1.1", + "js-beautify": "1.6.3", + "js-yaml": "3.13.1", + "lodash": "4.17.15", + "markdown-it": "6.0.1", + "node-fetch": "1.6.0", + "recursive-copy": "2.0.8", + "update-notifier": "2.2.0" + }, + "devDependencies": { + "@babel/core": "^7.5.5", + "@babel/plugin-proposal-decorators": "^7.4.4", + "@babel/plugin-syntax-jsx": "^7.2.0", + "babel-eslint": "^10.0.2", + "eslint": "^6.1.0", + "eslint-config-airbnb-base": "^14.0.0", + "eslint-config-prettier": "^6.0.0", + "eslint-plugin-import": "^2.18.2", + "eslint-plugin-prettier": "^3.1.0", + "husky": "0.14.3", + "jsdoc-to-markdown": "5.0.1", + "prettier": "^1.14.3", + "pretty-quick": "^1.11.1", + "rewire": "2.5.2", + "standard-version": "4.3.0", + "tap": "14.6.4" + }, + "keywords": [ + "Pattern Lab", + "Atomic Web Design", + "Node", + "Grunt", + "Gulp", + "Javascript" + ], + "repository": "https://github.com/pattern-lab/patternlab-node/tree/master/packages/core", + "bugs": "https://github.com/pattern-lab/patternlab-node/issues", + "author": { + "name": "Brian Muenzenmeyer" + }, + "contributors": [ + { + "name": "Geoff Pursell" + }, + { + "name": "Raphael Okon" + }, + { + "name": "tburny" + } + ], + "license": "MIT", + "scripts": { + "docs": "node ./scripts/docs.js", + "lint": "eslint -c ../../.eslintrc.js src/**/*.js", + "pretest": "npm run lint", + "release": "standard-version", + "test": "tap test/*_tests.js --reporter spec --coverage" + }, + "engines": { + "node": ">=10.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/core/patternlab-config.json b/packages/core/patternlab-config.json new file mode 100644 index 000000000..39eed54fd --- /dev/null +++ b/packages/core/patternlab-config.json @@ -0,0 +1,98 @@ +{ + "cacheBust": true, + "cleanPublic" : true, + "defaultPattern": "all", + "defaultShowPatternInfo": false, + "ishControlsHide": { + "s": false, + "m": false, + "l": false, + "full": false, + "random": false, + "disco": false, + "hay": true, + "mqs": false, + "find": false, + "views-all": false, + "views-annotations": false, + "views-code": false, + "views-new": false, + "tools-all": false, + "tools-docs": false + }, + "ishViewportRange": { + "s": [240, 500], + "m": [500, 800], + "l": [800, 2600] + }, + "logLevel": "info", + "outputFileSuffixes": { + "rendered": ".rendered", + "rawTemplate": "", + "markupOnly": ".markup-only" + }, + "paths" : { + "source": { + "root": "./source/", + "patterns": "./source/_patterns/", + "data": "./source/_data/", + "meta": "./source/_meta/", + "annotations": "./source/_annotations/", + "styleguide": "dist/", + "patternlabFiles": { + "general-header": + "views/partials/general-header.mustache", + "general-footer": + "views/partials/general-footer.mustache", + "patternSection": + "views/partials/patternSection.mustache", + "patternSectionSubtype": + "views/partials/patternSectionSubtype.mustache", + "viewall": + "views/viewall.mustache" + }, + "js": "./source/js", + "images": "./source/images", + "fonts": "./source/fonts", + "css": "./source/css" + }, + "public": { + "root": "public/", + "patterns": "public/patterns/", + "data": "public/styleguide/data/", + "annotations": "public/annotations/", + "styleguide": "public/styleguide/", + "js": "public/js", + "images": "public/images", + "fonts": "public/fonts", + "css": "public/css" + } + }, + "patternExtension": "mustache", + "patternStateCascade": ["inprogress", "inreview", "complete"], + "patternExportAll": false, + "patternExportDirectory": "pattern_exports", + "patternExportPatternPartials": [], + "patternExportPreserveDirectoryStructure": true, + "patternExportRaw": false, + "serverOptions": { + "wait": 1000 + }, + "starterkitSubDir": "dist", + "styleGuideExcludes": [ + ], + "theme": { + "color": "dark", + "density": "compact", + "layout": "horizontal" + }, + "uikits": [ + { + "name": "uikit-workshop", + "outputDir": "", + "enabled": true, + "excludedPatternStates": [], + "excludedTags": [] + } + ] +} diff --git a/packages/core/scripts/api.handlebars b/packages/core/scripts/api.handlebars new file mode 100644 index 000000000..8959ef5cf --- /dev/null +++ b/packages/core/scripts/api.handlebars @@ -0,0 +1,20 @@ +# Pattern Lab Node API + +[![Join the chat at Gitter](https://badges.gitter.im/pattern-lab/node.svg)](https://gitter.im/pattern-lab/node) + +## [Installation](https://github.com/pattern-lab/patternlab-node#installation) + +## Usage + +`patternlab-node` can be required within any Node environment, taking in a configuration file at instantiation. + +``` javascript +const config = require('./patternlab-config.json'); +const patternlab = require('@pattern-lab/core')(config); +``` + +{{>main}} + +* * * + +[Pattern Lab](http://patternlab.io) Node is [MIT Licensed](https://github.com/pattern-lab/patternlab-node/blob/master/LICENSE) diff --git a/packages/core/scripts/docs.js b/packages/core/scripts/docs.js new file mode 100644 index 000000000..8b0907aca --- /dev/null +++ b/packages/core/scripts/docs.js @@ -0,0 +1,34 @@ +const doc = require('jsdoc-to-markdown'); +const path = require('path'); +const process = require('process'); +const fs = require('fs-extra'); + +// doc +// .getJsdocData({ +// files: './src/lib/events.js', +// }) +// .then(x => { +// console.log(x); +// }); + +doc + .render({ + 'example-lang': 'javascript', + files: path.resolve(process.cwd(), './src/index.js'), + 'name-format': 'backticks', + template: fs.readFileSync('./scripts/api.handlebars', 'utf8'), + }) + .then(x => { + fs.outputFile(path.resolve(process.cwd(), './docs/README.md'), x); + }); + +doc + .render({ + 'example-lang': 'javascript', + files: path.resolve(process.cwd(), './src/lib/events.js'), + 'name-format': 'backticks', + template: fs.readFileSync('./scripts/events.handlebars', 'utf8'), + }) + .then(x => { + fs.outputFile(path.resolve(process.cwd(), './docs/events.md'), x); + }); diff --git a/packages/core/scripts/events.handlebars b/packages/core/scripts/events.handlebars new file mode 100644 index 000000000..667e8c9bf --- /dev/null +++ b/packages/core/scripts/events.handlebars @@ -0,0 +1,19 @@ +# Pattern Lab Node Events + +[![Join the chat at Gitter](https://badges.gitter.im/pattern-lab/node.svg)](https://gitter.im/pattern-lab/node) + +Pattern Lab emits numerous events during the [build](../docs/) process. Some uses of events: + +* Core uses `patternlab-pattern-change` events when watching for changes in order to trigger another build +* Plugins such as [plugin-tab](https://github.com/pattern-lab/patternlab-node/tree/master/packages/plugin-tab) can use an event like `patternlab-pattern-write-end` to define additional code tabs to the pattern viewer / modal + +Learn more about [Creating Plugins](https://github.com/pattern-lab/patternlab-node/wiki/Creating-Plugins). + +{{#module name="Events"}} +{{>header~}} +{{>members}} +{{/module}} + +* * * + +[Pattern Lab](http://patternlab.io) Node is [MIT Licensed](https://github.com/pattern-lab/patternlab-node/blob/master/LICENSE) diff --git a/packages/core/src/index.js b/packages/core/src/index.js new file mode 100644 index 000000000..1c575c3ab --- /dev/null +++ b/packages/core/src/index.js @@ -0,0 +1,337 @@ +/** + * Build thoughtful, pattern-driven user interfaces using atomic design principles. + * Many of these functions are exposed to users within {@link https://github.com/pattern-lab/patternlab-node#editions|Editions}, but {@link https://github.com/pattern-lab/patternlab-node#direct-consumption|direct consumption} is also encouraged. + * + * @namespace patternlab + * @see {@link patternlab.io} for more documentation. + * @see {@link https://github.com/pattern-lab/patternlab-node} for code, issues, and releases + * @license MIT + */ + +'use strict'; + +const path = require('path'); + +const updateNotifier = require('update-notifier'); + +const packageInfo = require('../package.json'); +const events = require('./lib/events'); +const pe = require('./lib/pattern_exporter'); +const pm = require('./lib/plugin_manager'); + +const defaultConfig = require('../patternlab-config.json'); + +let buildPatterns = require('./lib/buildPatterns'); // eslint-disable-line +let logger = require('./lib/log'); // eslint-disable-line +let fs = require('fs-extra'); // eslint-disable-line +let ui_builder = require('./lib/ui_builder'); // eslint-disable-line +let copier = require('./lib/copier'); // eslint-disable-line +let pattern_exporter = new pe(); // eslint-disable-line +let serverModule = require('./lib/server'); // eslint-disable-line + +//bootstrap update notifier +updateNotifier({ + pkg: packageInfo, + updateCheckInterval: 1000 * 60 * 60 * 24, // notify at most once a day +}).notify(); + +/** + * Static method that returns the standardized default config used to run Pattern Lab. This method can be called statically or after instantiation. + * + * @memberof patternlab + * @name getDefaultConfig + * @static + * @return {object} Returns the object representation of the `patternlab-config.json` + */ +const getDefaultConfig = function() { + return defaultConfig; +}; + +/** + * Static method that returns current version + * + * @memberof patternlab + * @name getVersion + * @static + * @returns {string} current @pattern-lab/core version as defined in `package.json` + */ +const getVersion = function() { + return packageInfo.version; +}; + +const patternlab_module = function(config) { + const PatternLabClass = require('./lib/patternlab'); + const patternlab = new PatternLabClass(config); + const server = serverModule(patternlab); + + const _api = { + /** + * Returns current version + * + * @memberof patternlab + * @name version + * @instance + * @returns {string} current patternlab-node version as defined in `package.json`, as string + */ + version: function() { + return patternlab.getVersion(); + }, + + /** + * Returns the current pattern lab configuration being used + * + * @memberof patternlab + * @name getConfig + * @instance + * @returns {object} the current patternlab-node config (defaults + customizations) + */ + getConfig() { + return config; + }, + + /** + * Returns if Pattern Lab is busy compiling or not + * + * @memberof patternlab + * @name isBusy + * @instance + * @returns {boolean} if pattern lab is currently busy compiling + */ + isBusy: function() { + return patternlab.isBusy; + }, + + /** + * Builds patterns, copies assets, and constructs user interface + * + * @memberof patternlab + * @name build + * @instance + * @param {object} options an object used to control build behavior + * @param {bool} [options.cleanPublic=true] whether or not to delete the configured output location (usually `public/`) before build + * @param {object} [options.data={}] additional data to be merged with global data prior to build + * @param {bool} [options.watch=true] whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild + * @emits PATTERNLAB_BUILD_START + * @emits PATTERNLAB_BUILD_END + * @see {@link ./events.md|all events} + * @returns {Promise} a promise fulfilled when build is complete + */ + build: async function(options) { + // process.on('unhandledRejection', (reason, p) => { + // console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); + // // application specific logging, throwing an error, or other logic here + + // console.log(reason.stack); + // debugger; + // }); + + if (patternlab && patternlab.isBusy) { + logger.info( + 'Pattern Lab is busy building a previous run - returning early.' + ); + return Promise.resolve(); + } + patternlab.isBusy = true; + + return await buildPatterns( + options.cleanPublic, + patternlab, + options.data + ).then(() => { + return new ui_builder().buildFrontend(patternlab).then(() => { + copier() + .copyAndWatch(patternlab.config.paths, patternlab, options) + .then(() => { + patternlab.isBusy = false; + // only wire up this listener and the one inside serve.js + // figure out how to detect if serve was called. we should not assume it was + if ( + patternlab.serverReady //check for server presence + ? this.events.listenerCount( + events.PATTERNLAB_PATTERN_CHANGE + ) === 1 //if the server is started, it has already setup one listener + : !this.events.listenerCount(events.PATTERNLAB_PATTERN_CHANGE) // else, check for the presnce of none + ) { + this.events.on(events.PATTERNLAB_PATTERN_CHANGE, () => { + if (!patternlab.isBusy) { + return this.build(options).then(() => { + patternlab.isBusy = false; + }); + } + return Promise.resolve(); + }); + } + + if (!this.events.listenerCount(events.PATTERNLAB_GLOBAL_CHANGE)) { + this.events.on(events.PATTERNLAB_GLOBAL_CHANGE, () => { + if (!patternlab.isBusy) { + return this.build( + Object.assign({}, options, { cleanPublic: true }) // rebuild everything + ); + } + return Promise.resolve(); + }); + } + }) + .then(() => { + this.events.emit(events.PATTERNLAB_BUILD_END, patternlab); + }); + }); + }); + }, + + /** + * Returns the standardized default config used to run Pattern Lab. This method can be called statically or after instantiation. + * + * @memberof patternlab + * @name getDefaultConfig + * @instance + * @return {object} Returns the object representation of the `patternlab-config.json` + */ + getDefaultConfig: function() { + return getDefaultConfig(); + }, + + /** + * Returns all file extensions supported by installed PatternEngines + * + * @memberof patternlab + * @name getSupportedTemplateExtensions + * @instance + * @returns {Array} all supported file extensions + */ + getSupportedTemplateExtensions: function() { + return patternlab.getSupportedTemplateExtensions(); + }, + + /** + * Installs plugin already available via `node_modules/` + * + * @memberof patternlab + * @name installplugin + * @instance + * @param {string} pluginName name of plugin + * @returns {void} + */ + installplugin: function(pluginName) { + //get the config + const configPath = path.resolve(process.cwd(), 'patternlab-config.json'); + const plugin_manager = new pm(); + + plugin_manager.install_plugin(pluginName); + }, + + /** + * Fetches starterkit repositories from pattern-lab github org that contain 'starterkit' in their name + * + * @memberof patternlab + * @name liststarterkits + * @instance + * @returns {Promise} Returns an Array<{name,url}> for the starterkit repos + */ + liststarterkits: function() { + return patternlab.listStarterkits(); + }, + + /** + * Loads starterkit already available via `node_modules/` + * + * @memberof patternlab + * @name loadstarterkit + * @instance + * @param {string} starterkitName name of starterkit + * @param {boolean} clean whether or not to delete contents of source/ before load + * @returns {void} + */ + loadstarterkit: function(starterkitName, clean) { + patternlab.loadStarterKit(starterkitName, clean); + }, + + /** + * Builds patterns only, leaving existing user interface files intact + * + * @memberof patternlab + * @name patternsonly + * @instance + * @param {bool} [options.cleanPublic=true] whether or not to delete the configured output location (usually `public/`) before build + * @param {object} [options.data={}] additional data to be merged with global data prior to build + * @param {bool} [options.watch=true] whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild + * @returns {Promise} a promise fulfilled when build is complete + */ + patternsonly: async function(options) { + if (patternlab && patternlab.isBusy) { + logger.info( + 'Pattern Lab is busy building a previous run - returning early.' + ); + return Promise.resolve(); + } + patternlab.isBusy = true; + return await buildPatterns( + options.cleanPublic, + patternlab, + options.data + ).then(() => { + patternlab.isBusy = false; + }); + }, + + /** + * Server module + * + * @memberof patternlab + * @type {object} + */ + server: { + /** + * Build patterns, copies assets, and constructs user interface. Watches configured `source/` directories, and serves all output locally + * + * @method serve + * @memberof patternlab.server + * @param {object} options an object used to control build behavior + * @param {bool} [options.cleanPublic=true] whether or not to delete the configured output location (usually `public/`) before build + * @param {object} [options.data={}] additional data to be merged with global data prior to build + * @param {bool} [options.watch=true] whether or not Pattern Lab should watch configured `source/` directories for changes to rebuild + * @returns {Promise} a promise fulfilled when build is complete + */ + serve: options => { + return _api + .build(options) + .then(() => server.serve()) + .catch(e => + logger.error(`error inside core index.js server serve: ${e}`) + ); + }, + /** + * Reloads any active live-server instances + * + * @method reload + * @memberof patternlab.server + * @returns {Promise} a promise fulfilled when operation is complete + */ + reload: server.reload, + /** + * Reloads CSS on any active live-server instances + * + * @method refreshCSS + * @memberof patternlab.server + * @returns {Promise} a promise fulfilled when operation is complete + */ + refreshCSS: server.refreshCSS, + }, + + /** + * @memberof patternlab + * @type {EventEmitter} + * @see {@link https://nodejs.org/api/events.html#events_class_eventemitter|EventEmitter} + * @see {@link ./events.md|All Pattern Lab events} + */ + events: patternlab.events, + }; + + return _api; +}; + +patternlab_module.getDefaultConfig = getDefaultConfig; +patternlab_module.getVersion = getVersion; + +module.exports = patternlab_module; diff --git a/packages/core/src/lib/addPattern.js b/packages/core/src/lib/addPattern.js new file mode 100644 index 000000000..12c0c9429 --- /dev/null +++ b/packages/core/src/lib/addPattern.js @@ -0,0 +1,45 @@ +'use strict'; + +const logger = require('./log'); + +module.exports = function(pattern, patternlab) { + //add the link to the global object + if (!patternlab.data.link) { + patternlab.data.link = {}; + } + patternlab.data.link[pattern.patternPartial] = + '/patterns/' + pattern.patternLink; + + //only push to array if the array doesn't contain this pattern + let isNew = true; + for (let i = 0; i < patternlab.patterns.length; i++) { + //so we need the identifier to be unique, which patterns[i].relPath is + if (pattern.relPath === patternlab.patterns[i].relPath) { + //if relPath already exists, overwrite that element + patternlab.patterns[i] = pattern; + patternlab.partials[pattern.patternPartial] = + pattern.extendedTemplate || pattern.template; + isNew = false; + break; + } + } + + // if the pattern is new, we must register it with various data structures! + if (isNew) { + logger.debug(`found new pattern ${pattern.patternPartial}`); + + // do global registration + if (pattern.isPattern) { + patternlab.partials[pattern.patternPartial] = + pattern.extendedTemplate || pattern.template; + + // do plugin-specific registration + pattern.registerPartial(); + } else { + patternlab.partials[pattern.patternPartial] = pattern.patternDesc; + } + + patternlab.patterns.push(pattern); + patternlab.graph.add(pattern); + } +}; diff --git a/packages/core/src/lib/annotation_exporter.js b/packages/core/src/lib/annotation_exporter.js new file mode 100644 index 000000000..7ed86da28 --- /dev/null +++ b/packages/core/src/lib/annotation_exporter.js @@ -0,0 +1,125 @@ +'use strict'; +const path = require('path'); +const glob = require('glob'); +const fs = require('fs-extra'); +const _ = require('lodash'); +const mp = require('./markdown_parser'); +const logger = require('./log'); + +const annotations_exporter = function(pl) { + const paths = pl.config.paths; + let oldAnnotations; + + /** + * Parses JS annotations. + * @returns array of comments that used to be wrapped in raw JS + */ + function parseAnnotationsJS() { + //attempt to read the file + try { + oldAnnotations = fs.readFileSync( + path.resolve(paths.source.annotations, 'annotations.js'), + 'utf8' + ); + } catch (ex) { + logger.debug( + `annotations.js file missing from ${paths.source.annotations}. This may be expected if you do not use annotations or are using markdown.` + ); + return []; + } + + //parse as JSON by removing the old wrapping js syntax. comments and the trailing semi-colon + oldAnnotations = oldAnnotations.replace('var comments = ', ''); + oldAnnotations = oldAnnotations.replace('};', '}'); + + try { + const oldAnnotationsJSON = JSON.parse(oldAnnotations); + return oldAnnotationsJSON.comments; + } catch (ex) { + logger.error( + `There was an error parsing JSON for ${paths.source.annotations}annotations.js` + ); + return []; + } + } + + /** + * Build the annotation markdown. + * @param annotationsYAML + * @param markdown_parser + * @returns annotation + */ + function buildAnnotationMD(annotationsYAML, markdown_parser) { + const annotation = {}; + const markdownObj = markdown_parser.parse(annotationsYAML); + + annotation.el = markdownObj.el || markdownObj.selector; + annotation.title = markdownObj.title; + annotation.comment = markdownObj.markdown; + return annotation; + } + + /** + * Parse markdown file annotations. + * @param annotations + * @param parser + */ + function parseMDFile(annotations, parser) { + //let annotations = annotations; + const markdown_parser = parser; + + return function(filePath) { + const annotationsMD = fs.readFileSync(path.resolve(filePath), 'utf8'); + + //take the annotation snippets and split them on our custom delimiter + const annotationsYAML = annotationsMD.split('~*~'); + for (let i = 0; i < annotationsYAML.length; i++) { + const annotation = buildAnnotationMD( + annotationsYAML[i], + markdown_parser + ); + annotations.push(annotation); + } + return false; + }; + } + + /** + * Converts the *.md file yaml list into an array of annotations + * + * @returns annotations + */ + function parseAnnotationsMD() { + const markdown_parser = new mp(); + const annotations = []; + const mdFiles = glob.sync(paths.source.annotations + '/*.md'); + + mdFiles.forEach(parseMDFile(annotations, markdown_parser)); + return annotations; + } + + /** + * Gathers JS & MD annotations. + * + * @returns array of annotations + */ + function gatherAnnotations() { + const annotationsJS = parseAnnotationsJS(); + const annotationsMD = parseAnnotationsMD(); + return _.unionBy(annotationsJS, annotationsMD, 'el'); + } + + return { + gather: function() { + return gatherAnnotations(); + }, + gatherJS: function() { + return parseAnnotationsJS(); + }, + gatherMD: function() { + return parseAnnotationsMD(); + }, + }; +}; + +module.exports = annotations_exporter; diff --git a/packages/core/src/lib/buildFooter.js b/packages/core/src/lib/buildFooter.js new file mode 100644 index 000000000..5a3eb6217 --- /dev/null +++ b/packages/core/src/lib/buildFooter.js @@ -0,0 +1,43 @@ +'use strict'; + +const jsonCopy = require('./json_copy'); +const logger = require('./log'); +const of = require('./object_factory'); +const Pattern = of.Pattern; + +let render = require('./render'); //eslint-disable-line prefer-const + +/** + * Builds footer HTML from the general footer and user-defined footer + * @param patternlab - global data store + * @param patternPartial - the partial key to build this for, either viewall-patternPartial or a viewall-patternType-all + * @returns A promise which resolves with the HTML + */ +module.exports = function(patternlab, patternPartial, uikit) { + //first render the general footer + return render(Pattern.createEmpty({ extendedTemplate: uikit.footer }), { + patternData: JSON.stringify({ + patternPartial: patternPartial, + }), + cacheBuster: patternlab.cacheBuster, + }) + .then(footerPartial => { + let allFooterData; + try { + allFooterData = jsonCopy( + patternlab.data, + 'config.paths.source.data plus patterns data' + ); + } catch (err) { + logger.warning('There was an error parsing JSON for patternlab.data'); + logger.warning(err); + } + allFooterData.patternLabFoot = footerPartial; + + return render(patternlab.userFoot, allFooterData); + }) + .catch(reason => { + console.log(reason); + logger.error('Error building buildFooterHTML'); + }); +}; diff --git a/packages/core/src/lib/buildListItems.js b/packages/core/src/lib/buildListItems.js new file mode 100644 index 000000000..af9ba783d --- /dev/null +++ b/packages/core/src/lib/buildListItems.js @@ -0,0 +1,53 @@ +'use strict'; + +let _ = require('lodash'); //eslint-disable-line prefer-const + +const items = [ + 'zero', + 'one', + 'two', + 'three', + 'four', + 'five', + 'six', + 'seven', + 'eight', + 'nine', + 'ten', + 'eleven', + 'twelve', + 'thirteen', + 'fourteen', + 'fifteen', + 'sixteen', + 'seventeen', + 'eighteen', + 'nineteen', + 'twenty', +]; + +module.exports = function(container) { + //combine all list items into one structure + const list = []; + for (const item in container.listitems) { + if (container.listitems.hasOwnProperty(item)) { + list.push(container.listitems[item]); + } + } + const listItemArray = _.shuffle(list); + + for (let i = 1; i <= listItemArray.length; i++) { + const tempItems = []; + if (i === 1) { + tempItems.push(listItemArray[0]); + container.listitems['listItems-' + items[i]] = tempItems; + delete container.listitems[i]; + } else { + for (let c = 1; c <= i; c++) { + tempItems.push(listItemArray[c - 1]); + container.listitems['listItems-' + items[i]] = tempItems; + delete container.listitems[i]; + } + } + } +}; diff --git a/packages/core/src/lib/buildPatterns.js b/packages/core/src/lib/buildPatterns.js new file mode 100644 index 000000000..c550a6880 --- /dev/null +++ b/packages/core/src/lib/buildPatterns.js @@ -0,0 +1,229 @@ +'use strict'; + +const { concat, map } = require('lodash'); +const copy = require('recursive-copy'); +const path = require('path'); + +const cleanBuildDirectory = require('./cleanBuildDirectory'); +const compose = require('./compose'); +const events = require('./events'); +const loadPatternGraph = require('./loadPatternGraph'); +const logger = require('./log'); +const PatternGraph = require('./pattern_graph').PatternGraph; +const CompileState = require('./object_factory').CompileState; +const processMetaPattern = require('./processMetaPattern'); +const pe = require('./pattern_exporter'); +const lh = require('./lineage_hunter'); +const pm = require('./plugin_manager'); +const pluginMananger = new pm(); +const markModifiedPatterns = require('./markModifiedPatterns'); +const parseAllLinks = require('./parseAllLinks'); +const render = require('./render'); +const Pattern = require('./object_factory').Pattern; + +let fs = require('fs-extra'); // eslint-disable-line +let pattern_exporter = new pe(); // eslint-disable-line + +const lineage_hunter = new lh(); + +module.exports = async (deletePatternDir, patternlab, additionalData) => { + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_BUILD_START, + patternlab + ); + + const paths = patternlab.config.paths; + + // + // CHECK INCREMENTAL BUILD GRAPH + // + const graph = (patternlab.graph = loadPatternGraph( + patternlab, + patternlab.config.cleanPublic + )); + const graphNeedsUpgrade = !PatternGraph.checkVersion(graph); + if (graphNeedsUpgrade) { + logger.info( + 'Due to an upgrade, a complete rebuild is required and the public/patterns directory was deleted. ' + + 'Incremental build is available again on the next successful run.' + ); + + // Ensure that the freshly built graph has the latest version again. + patternlab.graph.upgradeVersion(); + } + + // Flags + patternlab.incrementalBuildsEnabled = !( + patternlab.config.cleanPublic || graphNeedsUpgrade + ); + + // + // CLEAN BUILD DIRECTORY, maybe + // + return cleanBuildDirectory( + patternlab.incrementalBuildsEnabled, + patternlab + ).then(() => { + patternlab.buildGlobalData(additionalData); + + return patternlab + .processAllPatternsIterative(paths.source.patterns) + .then(async () => { + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_PATTERN_ITERATION_END, + patternlab + ); + + //now that all the main patterns are known, look for any links that might be within data and expand them + //we need to do this before expanding patterns & partials into extendedTemplates, otherwise we could lose the data -> partial reference + parseAllLinks(patternlab); + + //dive again to recursively include partials, filling out the + //extendedTemplate property of the patternlab.patterns elements + + return patternlab + .processAllPatternsRecursive(paths.source.patterns) + .then(() => { + //take the user defined head and foot and process any data and patterns that apply + + //todo this need to be made aware of multiple ui kits + //perhaps we can check for a convention like [uikitname]_00-head.mustache, and if found, add them to patternlab.uikits[uikitname].userFoot + //then, if present, use those during compose() + const headPatternPromise = processMetaPattern( + `_00-head.${patternlab.config.patternExtension}`, + 'userHead', + patternlab + ); + const footPatternPromise = processMetaPattern( + `_01-foot.${patternlab.config.patternExtension}`, + 'userFoot', + patternlab + ); + + return Promise.all([headPatternPromise, footPatternPromise]) + .then(() => { + //cascade any patternStates + lineage_hunter.cascade_pattern_states(patternlab); + + //set the pattern-specific header by compiling the general-header with data, and then adding it to the meta header + return render( + Pattern.createEmpty({ + // todo should this be uikit.header? + extendedTemplate: patternlab.header, + }), + { + cacheBuster: patternlab.cacheBuster, + } + ) + .then(results => { + patternlab.data.patternLabHead = results; + + // If deletePatternDir == true or graph needs to be updated + // rebuild all patterns + let patternsToBuild = null; + + // If deletePatternDir == true or graph needs to be updated + // rebuild all patterns + patternsToBuild = null; + + if (patternlab.incrementalBuildsEnabled) { + // When the graph was loaded from file, some patterns might have been moved/deleted between runs + // so the graph data become out of sync + patternlab.graph.sync().forEach(n => { + logger.info('[Deleted/Moved] ' + n); + }); + + // TODO Find created or deleted files + const now = new Date().getTime(); + markModifiedPatterns(now, patternlab); + patternsToBuild = patternlab.graph.compileOrder(); + } else { + // build all patterns, mark all to be rebuilt + patternsToBuild = patternlab.patterns; + for (const p of patternsToBuild) { + p.compileState = CompileState.NEEDS_REBUILD; + } + } + //render all patterns last, so lineageR works + const allPatternsPromise = patternsToBuild.map( + async pattern => + await compose( + pattern, + patternlab + ) + ); + //copy non-pattern files like JavaScript + const allJS = patternsToBuild.map(pattern => { + const { name, patternPartial, subdir } = pattern; + const { + source: { patterns: sourceDir }, + public: { patterns: publicDir }, + } = patternlab.config.paths; + const src = path.join(sourceDir, subdir); + const dest = path.join(publicDir, name); + return map(patternlab.uikits, uikit => { + return copy( + src, + path.resolve(process.cwd(), uikit.outputDir, dest), + { + overwrite: true, + filter: ['*.js'], + rename: () => { + return `${patternPartial}.js`; + }, + } + ).on(copy.events.COPY_FILE_COMPLETE, () => { + logger.debug( + `Copied JavaScript files from ${src} to ${dest}` + ); + }); + }); + }); + return Promise.all(concat(allPatternsPromise, allJS)) + .then(() => { + // Saves the pattern graph when all files have been compiled + PatternGraph.storeToFile(patternlab); + if (patternlab.config.exportToGraphViz) { + PatternGraph.exportToDot( + patternlab, + 'dependencyGraph.dot' + ); + logger.info( + `Exported pattern graph to ${path.join( + patternlab.config.paths.public.root, + 'dependencyGraph.dot' + )}` + ); + } + + //export patterns if necessary + pattern_exporter.export_patterns(patternlab); + }) + .catch(reason => { + console.log(reason); + logger.error('Error rendering patterns'); + }); + }) + .catch(reason => { + console.log(reason); + logger.error('Error rendering pattern lab header'); + }); + }) + .catch(reason => { + console.log(reason); + logger.error('Error processing meta patterns'); + }); + }) + .catch(reason => { + console.log(reason); + logger.error('Error processing patterns recursively'); + }); + }) + .catch(reason => { + console.log(reason); + logger.error('Error in buildPatterns()'); + }); + }); +}; diff --git a/packages/core/src/lib/changes_hunter.js b/packages/core/src/lib/changes_hunter.js new file mode 100644 index 000000000..038be8b41 --- /dev/null +++ b/packages/core/src/lib/changes_hunter.js @@ -0,0 +1,121 @@ +'use strict'; + +const path = require('path'); +const _ = require('lodash'); + +const CompileState = require('./object_factory').CompileState; + +//this is mocked in unit tests +let fs = require('fs-extra'); //eslint-disable-line prefer-const + +/** + * For detecting changed patterns. + * @constructor + */ +const ChangesHunter = function() {}; + +ChangesHunter.prototype = { + /** + * Checks the build state of a pattern by comparing the modification date of the rendered output + * file with the {@link Pattern.lastModified}. If the pattern was modified after the last + * time it has been rendered, it is flagged for rebuilding via {@link CompileState.NEEDS_REBUILD}. + * + * @param {Pattern} pattern + * @param patternlab + * + * @see {@link CompileState} + */ + checkBuildState: function(pattern, patternlab) { + //write the compiled template to the public patterns directory + const renderedTemplatePath = + patternlab.config.paths.public.patterns + + pattern.getPatternLink(patternlab, 'rendered'); + + //write the compiled template to the public patterns directory + const markupOnlyPath = + patternlab.config.paths.public.patterns + + pattern.getPatternLink(patternlab, 'markupOnly'); + + if (!pattern.compileState) { + pattern.compileState = CompileState.NEEDS_REBUILD; + } + + _.each(patternlab.uikits, uikit => { + try { + // renderedTemplatePath required to display a single element + // Markup only is required for "View All" pages. It will get loaded later on. + // If any of these is missing, mark pattern for recompile + [renderedTemplatePath, markupOnlyPath].forEach(renderedFile => { + // Prevent error message if file does not exist + fs.accessSync( + path.join(process.cwd(), uikit.outputDir, renderedFile), + fs.F_OK + ); + }); + + const outputLastModified = fs + .statSync( + path.join(process.cwd(), uikit.outputDir, renderedTemplatePath) + ) + .mtime.getTime(); + + if (pattern.lastModified && outputLastModified > pattern.lastModified) { + pattern.compileState = CompileState.CLEAN; + } + } catch (e) { + // Output does not exist yet, force recompile + pattern.compileState = CompileState.NEEDS_REBUILD; + } + }); + + const node = patternlab.graph.node(pattern); + + // IF we are rebuilding due to watching and incrementally building, force add patterns to graph + if ( + patternlab.incrementalBuildsEnabled && + Object.keys(patternlab.watchers).length + ) { + patternlab.graph.add(pattern); + } else { + // Make the pattern known to the PatternGraph and remember its compileState + if (!node) { + patternlab.graph.add(pattern); + } else { + // Works via object reference, so we directly manipulate the node data here + node.compileState = pattern.compileState; + } + } + }, + + /** + * Updates {Pattern#lastModified} to the files modification date if the file was modified + * after {Pattern#lastModified}. + * + * @param {Pattern} currentPattern + * @param {string} file + */ + checkLastModified: function(currentPattern, file) { + if (file && fs.pathExistsSync(file)) { + try { + const stat = fs.statSync(file); + + // Needs recompile whenever one of the patterns files (template, json, pseudopatterns) changed + currentPattern.lastModified = Math.max( + stat.mtime.getTime(), + currentPattern.lastModified || 0 + ); + } catch (e) { + // Ignore, not a regular file + } + } + }, + + needsRebuild: function(lastModified, p) { + if (p.compileState !== CompileState.CLEAN || !p.lastModified) { + return true; + } + return p.lastModified >= lastModified; + }, +}; + +module.exports = ChangesHunter; diff --git a/packages/core/src/lib/cleanBuildDirectory.js b/packages/core/src/lib/cleanBuildDirectory.js new file mode 100644 index 000000000..11d78c01e --- /dev/null +++ b/packages/core/src/lib/cleanBuildDirectory.js @@ -0,0 +1,27 @@ +'use strict'; + +const _ = require('lodash'); +const path = require('path'); + +const logger = require('./log'); + +let fs = require('fs-extra'); // eslint-disable-line + +module.exports = (incrementalBuildsEnabled, patternlab) => { + const paths = patternlab.config.paths; + + if (incrementalBuildsEnabled) { + logger.info('Incremental builds enabled.'); + return Promise.resolve(); + } else { + return Promise.all( + _.map(patternlab.uikits, uikit => { + return fs.emptyDir( + path.join(process.cwd(), uikit.outputDir, paths.public.patterns) + ); + }) + ).catch(reason => { + logger.error(reason); + }); + } +}; diff --git a/packages/core/src/lib/compose.js b/packages/core/src/lib/compose.js new file mode 100644 index 000000000..07c202ce7 --- /dev/null +++ b/packages/core/src/lib/compose.js @@ -0,0 +1,213 @@ +'use strict'; + +const _ = require('lodash'); + +const events = require('./events'); +const jsonCopy = require('./json_copy'); +const logger = require('./log'); +const parseLink = require('./parseLink'); +const render = require('./render'); +const uikitExcludePattern = require('./uikitExcludePattern'); +const pm = require('./plugin_manager'); +const pluginMananger = new pm(); + +const Pattern = require('./object_factory').Pattern; +const CompileState = require('./object_factory').CompileState; + +module.exports = async function(pattern, patternlab) { + // Pattern does not need to be built and recompiled more than once + if (!pattern.isPattern || pattern.compileState === CompileState.CLEAN) { + return Promise.resolve(false); + } + + // Allows serializing the compile state + patternlab.graph.node(pattern).compileState = pattern.compileState = + CompileState.BUILDING; + + //todo move this into lineage_hunter + pattern.patternLineages = pattern.lineage; + pattern.patternLineageExists = pattern.lineage.length > 0; + pattern.patternLineagesR = pattern.lineageR; + pattern.patternLineageRExists = pattern.lineageR.length > 0; + pattern.patternLineageEExists = + pattern.patternLineageExists || pattern.patternLineageRExists; + + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_PATTERN_BEFORE_DATA_MERGE, + patternlab, + pattern + ); + + return Promise.all( + _.map(patternlab.uikits, uikit => { + // exclude pattern from uikit rendering + if (uikitExcludePattern(pattern, uikit)) { + return Promise.resolve(); + } + + //render the pattern, but first consolidate any data we may have + let allData; + + let allListItems = _.merge({}, patternlab.listitems, pattern.listitems); + allListItems = parseLink( + patternlab, + allListItems, + 'listitems.json + any pattern listitems.json' + ); + + allData = _.merge({}, patternlab.data, pattern.jsonFileData); + allData = _.merge({}, allData, allListItems); + allData.cacheBuster = patternlab.cacheBuster; + allData.patternPartial = pattern.patternPartial; + + /////////////// + // HEADER + /////////////// + + //re-rendering the headHTML each time allows pattern-specific data to influence the head of the pattern + let headPromise; + if (patternlab.userHead) { + headPromise = render(patternlab.userHead, allData); + } else { + headPromise = render( + Pattern.createEmpty({ extendedTemplate: uikit.header }), + allData + ); + } + + /////////////// + // PATTERN + /////////////// + + //render the extendedTemplate with all data + const patternPartialPromise = render( + pattern, + allData, + patternlab.partials + ); + + /////////////// + // FOOTER + /////////////// + + // stringify this data for individual pattern rendering and use on the styleguide + // see if patternData really needs these other duped values + + // construct our extraOutput dump + const extraOutput = Object.assign( + {}, + pattern.extraOutput, + pattern.allMarkdown + ); + delete extraOutput.title; + delete extraOutput.state; + delete extraOutput.markdown; + + pattern.patternData = JSON.stringify({ + cssEnabled: false, + patternLineageExists: pattern.patternLineageExists, + patternLineages: pattern.patternLineages, + lineage: pattern.patternLineages, + patternLineageRExists: pattern.patternLineageRExists, + patternLineagesR: pattern.patternLineagesR, + lineageR: pattern.patternLineagesR, + patternLineageEExists: + pattern.patternLineageExists || pattern.patternLineageRExists, + patternDesc: pattern.patternDescExists ? pattern.patternDesc : '', + patternBreadcrumb: + pattern.patternGroup === pattern.patternSubGroup + ? { + patternType: pattern.patternGroup, + } + : { + patternType: pattern.patternGroup, + patternSubtype: pattern.patternSubGroup, + }, + patternExtension: pattern.fileExtension.substr(1), //remove the dot because styleguide asset default adds it for us + patternName: pattern.patternName, + patternPartial: pattern.patternPartial, + patternState: pattern.patternState, + patternEngineName: pattern.engine.engineName, + extraOutput: extraOutput, + }); + + //set the pattern-specific footer by compiling the general-footer with data, and then adding it to the meta footer + const footerPartialPromise = render( + Pattern.createEmpty({ extendedTemplate: uikit.footer }), + { + isPattern: pattern.isPattern, + patternData: pattern.patternData, + cacheBuster: patternlab.cacheBuster, + } + ); + + return Promise.all([ + headPromise, + patternPartialPromise, + footerPartialPromise, + ]) + .then(intermediateResults => { + // retrieve results of promises + const headHTML = intermediateResults[0]; //headPromise + pattern.patternPartialCode = intermediateResults[1]; //patternPartialPromise + const footerPartial = intermediateResults[2]; //footerPartialPromise + + //finish up our footer data + let allFooterData; + try { + allFooterData = jsonCopy( + patternlab.data, + 'config.paths.source.data global data' + ); + } catch (err) { + logger.info( + 'There was an error parsing JSON for ' + pattern.relPath + ); + logger.info(err); + } + allFooterData = _.merge(allFooterData, pattern.jsonFileData); + allFooterData.cacheBuster = patternlab.cacheBuster; + allFooterData.patternLabFoot = footerPartial; + + return render(patternlab.userFoot, allFooterData).then( + async footerHTML => { + /////////////// + // WRITE FILES + /////////////// + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_PATTERN_WRITE_BEGIN, + patternlab, + pattern + ); + + //write the compiled template to the public patterns directory + patternlab.writePatternFiles( + headHTML, + pattern, + footerHTML, + uikit.outputDir + ); + + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_PATTERN_WRITE_END, + patternlab, + pattern + ); + + // Allows serializing the compile state + patternlab.graph.node( + pattern + ).compileState = pattern.compileState = CompileState.CLEAN; + logger.info('Built pattern: ' + pattern.patternPartial); + } + ); + }) + .catch(reason => { + console.log(reason); + }); + }) + ); +}; diff --git a/packages/core/src/lib/copier.js b/packages/core/src/lib/copier.js new file mode 100644 index 000000000..441ffdf5b --- /dev/null +++ b/packages/core/src/lib/copier.js @@ -0,0 +1,125 @@ +'use strict'; +const _ = require('lodash'); +const path = require('path'); +const process = require('process'); + +const copyFile = require('./copyFile'); +const watchAssets = require('./watchAssets'); +const watchPatternLabFiles = require('./watchPatternLabFiles'); + +const copier = () => { + const transform_paths = directories => { + //create array with all source keys minus our blacklist + const dirs = {}; + const blackList = [ + 'root', + 'patterns', + 'data', + 'meta', + 'annotations', + 'patternlabFiles', + 'styleguide', + ]; + _.each(directories.source, (dir, key) => { + if (blackList.includes(key)) { + return; + } + + if (!dirs.key) { + dirs[key] = {}; + } + }); + + // loop through all source keys + _.each(dirs, (dir, key) => { + // add source key path + dirs[key].source = directories.source[key]; + + // add public key path + dirs[key].public = directories.public[key]; + }); + + return dirs; + }; + + const copyAndWatch = (assetDirectories, patternlab, options) => { + //take our configured paths and sanitize best we can to only the assets + const dirs = transform_paths(assetDirectories); + + //find out where we are + const basePath = path.resolve(process.cwd()); + + const copyOptions = { + overwrite: true, + emitter: patternlab.events, + debug: patternlab.config.logLevel === 'debug', + }; + + //loop through each directory asset object (source / public pairing) + + const copyPromises = []; + + _.each(dirs, (dir, key) => { + //if we want to watch files, do so, otherwise just copy each file + if (options.watch) { + watchAssets(patternlab, basePath, dir, key, copyOptions); + } else { + //just copy + copyPromises.push( + _.map(patternlab.uikits, uikit => { + copyFile( + dir.source, + path.join(basePath, uikit.outputDir, dir.public), + copyOptions + ); + }) + ); + } + }); + + // copy the styleguide + copyPromises.push( + _.map(patternlab.uikits, uikit => { + copyFile( + path.join(uikit.modulePath, assetDirectories.source.styleguide), + path.join(basePath, uikit.outputDir, assetDirectories.public.root), + copyOptions + ); + }) + ); + + // copy the favicon + copyPromises.push( + _.map(patternlab.uikits, uikit => { + copyFile( + `${assetDirectories.source.root}/favicon.ico`, + path.join( + basePath, + uikit.outputDir, + `${assetDirectories.public.root}/favicon.ico` + ), + copyOptions + ); + }) + ); + + return Promise.all(copyPromises).then(() => { + //we need to special case patterns/**/*.md|.json|.pattern-extensions as well as the global structures + if (options.watch) { + return watchPatternLabFiles(patternlab, assetDirectories, basePath); + } + return Promise.resolve(); + }); + }; + + return { + copyAndWatch: (assetDirectories, patternlab, options) => { + return copyAndWatch(assetDirectories, patternlab, options); + }, + transformConfigPaths: paths => { + return transform_paths(paths); + }, + }; +}; + +module.exports = copier; diff --git a/packages/core/src/lib/copyFile.js b/packages/core/src/lib/copyFile.js new file mode 100644 index 000000000..ce1bc056a --- /dev/null +++ b/packages/core/src/lib/copyFile.js @@ -0,0 +1,25 @@ +'use strict'; + +const logger = require('./log'); +const events = require('./events'); + +let copy = require('recursive-copy'); // eslint-disable-line prefer-const + +const copyFile = (p, dest, options) => { + return copy(p, dest, options) + .on(copy.events.ERROR, function(error, copyOperation) { + logger.error('Unable to copy ' + copyOperation.dest); + }) + .on(copy.events.COPY_FILE_ERROR, error => { + logger.error(error); + }) + .on(copy.events.COPY_FILE_COMPLETE, () => { + logger.debug(`Moved ${p} to ${dest}`); + options.emitter.emit(events.PATTERNLAB_PATTERN_ASSET_CHANGE, { + file: p, + dest: dest, + }); + }); +}; + +module.exports = copyFile; diff --git a/packages/core/src/lib/data_loader.js b/packages/core/src/lib/data_loader.js new file mode 100644 index 000000000..f1ac874c2 --- /dev/null +++ b/packages/core/src/lib/data_loader.js @@ -0,0 +1,75 @@ +'use strict'; + +const glob = require('glob'), + _ = require('lodash'), + path = require('path'), + yaml = require('js-yaml'); + +/** + * Loads a single config file, in yaml/json format. + * + * @param dataFilePath - leave off the file extension. + * @param fsDep + * @returns {*} + */ +function loadFile(dataFilePath, fsDep) { + const dataFilesFullPath = `${dataFilePath}.{json,yml,yaml}`; + + if (dataFilePath) { + const dataFiles = glob.sync(dataFilesFullPath), + dataFile = _.head(dataFiles); + + if (dataFile && fsDep.existsSync(path.resolve(dataFile))) { + try { + return yaml.safeLoad( + fsDep.readFileSync(path.resolve(dataFile), 'utf8') + ); + } catch (err) { + throw new Error(`Error loading file: ${dataFile} - ${err.message}`); + } + } + } + + return null; +} + +/** + * Loads a set of config files from a folder, in yaml/json format. + * + * @param dataFilesPath - leave off the file extension + * @param excludeFileNames - leave off the file extension + * @param fsDep + * @returns Object, with merged data files, empty object if no files. + */ +function loadDataFromFolder(dataFilesPath, excludeFileNames, fsDep) { + const dataFilesFullPath = dataFilesPath + '*.{json,yml,yaml}', + excludeFullPath = dataFilesPath + excludeFileNames + '.{json,yml,yaml}'; + + const globOptions = {}; + if (excludeFileNames) { + globOptions.ignore = [excludeFullPath]; + } + + const dataFiles = glob.sync(dataFilesFullPath, globOptions); + let mergeObject = {}; + + dataFiles.forEach(function(filePath) { + try { + const jsonData = yaml.safeLoad( + fsDep.readFileSync(path.resolve(filePath), 'utf8') + ); + mergeObject = _.merge(mergeObject, jsonData); + } catch (err) { + throw new Error(`Error loading file: ${filePath} - ${err.message}`); + } + }); + + return mergeObject; +} + +module.exports = function configFileLoader() { + return { + loadDataFromFile: loadFile, + loadDataFromFolder: loadDataFromFolder, + }; +}; diff --git a/packages/core/src/lib/decompose.js b/packages/core/src/lib/decompose.js new file mode 100644 index 000000000..e541708b5 --- /dev/null +++ b/packages/core/src/lib/decompose.js @@ -0,0 +1,57 @@ +'use strict'; + +const logger = require('./log'); +const lh = require('./lineage_hunter'); +const lih = require('./list_item_hunter'); +const addPattern = require('./addPattern'); +const expandPartials = require('./expandPartials'); + +const lineage_hunter = new lh(); +const list_item_hunter = new lih(); + +/** + * A helper that unravels a pattern looking for partials or listitems to unravel. + * The goal is really to convert pattern.template into pattern.extendedTemplate + * @param pattern - the pattern to decompose + * @param patternlab - global data store + * @param ignoreLineage - whether or not to hunt for lineage for this pattern + */ +module.exports = function(pattern, patternlab, ignoreLineage) { + //set the extendedTemplate to operate on later if we find partials to replace + if (!pattern.extendedTemplate) { + pattern.extendedTemplate = pattern.template; + } + + //find any listItem blocks that within the pattern, even if there are no partials + const listItemPromise = list_item_hunter.process_list_item_partials( + pattern, + patternlab + ); + + const expandPartialPromise = expandPartials(pattern, patternlab); + + let lineagePromise; + + //find pattern lineage + if (!ignoreLineage) { + lineagePromise = Promise.resolve( + lineage_hunter.find_lineage(pattern, patternlab) + ); + } else { + lineagePromise = Promise.resolve(); + } + + const addPromise = Promise.resolve(() => { + //add to patternlab object so we can look these up later. + addPattern(pattern, patternlab); + }); + + return Promise.all([ + listItemPromise, + expandPartialPromise, + lineagePromise, + addPromise, + ]).catch(reason => { + logger.error(reason); + }); +}; diff --git a/packages/core/src/lib/events.js b/packages/core/src/lib/events.js new file mode 100644 index 000000000..b1653b3c8 --- /dev/null +++ b/packages/core/src/lib/events.js @@ -0,0 +1,81 @@ +'use strict'; + +/** + * All Pattern Lab Events + * @module Events + */ + +/** + * @alias module:Events + */ +const EVENTS = Object.freeze({ + /** + * @desc Emitted before any logic run inside `build()`, which is the entry point for single builds, pattern-only builds, run singly or when watched. + * @property {object} patternlab - global data store + * + */ + PATTERNLAB_BUILD_START: 'patternlab-build-start', + + /** + * @desc Emitted after all logic run inside `build()`, which is the entry point for single builds, pattern-only builds, run singly or when watched. + * @property {object} patternlab - global data store + * + */ + PATTERNLAB_BUILD_END: 'patternlab-build-end', + + /** + * @desc Emitted after patterns are iterated over to gather data about them. Right before Pattern Lab processes and renders patterns into HTML + * @property {object} patternlab - global data store + */ + PATTERNLAB_PATTERN_ITERATION_END: 'patternlab-pattern-iteration-end', + + /** + * @desc Emitted after global `data.json` and `listitems.json` are read, and the supporting Pattern Lab templates are loaded into memory (header, footer, patternSection, patternSectionSubType, viewall). Right before patterns are iterated over to gather data about them. + * @property {object} patternlab - global data store + */ + PATTERNLAB_BUILD_GLOBAL_DATA_END: 'patternlab-build-global-data-end', + + /** + * @desc Emitted before all data is merged prior to a Pattern's render. Global `data.json` is merged with any pattern `.json`. Global `listitems.json` is merged with any pattern `.listitems.json`. + * @property {object} patternlab - global data store + * @property {Pattern} pattern - current pattern + * @see {@link https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16|Pattern} + */ + PATTERNLAB_PATTERN_BEFORE_DATA_MERGE: 'patternlab-pattern-before-data-merge', + + /** + * @desc Emitted before a pattern's template, HTML, and encoded HTML files are written to their output location + * @property {object} patternlab - global data store + * @property {Pattern} pattern - current pattern + * @see {@link https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16|Pattern} + */ + PATTERNLAB_PATTERN_WRITE_BEGIN: 'patternlab-pattern-write-begin', + + /** + * @desc Emitted after a pattern's template, HTML, and encoded HTML files are written to their output location + * @property {object} patternlab - global data store + * @property {Pattern} pattern - current pattern + * @see {@link https://github.com/pattern-lab/patternlab-node/blob/master/src/lib/object_factory.js#L16|Pattern} + */ + PATTERNLAB_PATTERN_WRITE_END: 'patternlab-pattern-write-end', + + /** + * @desc Invoked when a watched asset changes. Assets include anything in `source/` that is not under `['root', 'patterns', 'data', 'meta', 'annotations', 'patternlabFiles']` which are blacklisted for specific copying. + * @property {object} fileInfo - `{file: 'path/to/file.css', dest: 'path/to/destination'}` + */ + PATTERNLAB_PATTERN_ASSET_CHANGE: 'patternlab-pattern-asset-change', + + /** + * @desc Invoked when a watched global file changes. These are files within the directories specified in `['data', 'meta']`paths. + * @property {object} fileInfo - `{file: 'path/to/file.ext'}` + */ + PATTERNLAB_GLOBAL_CHANGE: 'patternlab-global-change', + + /** + * @desc Invoked when a pattern changes. + * @property {object} fileInfo - `{file: 'path/to/file.ext'}` + */ + PATTERNLAB_PATTERN_CHANGE: 'patternlab-pattern-change', +}); + +module.exports = EVENTS; diff --git a/packages/core/src/lib/expandPartials.js b/packages/core/src/lib/expandPartials.js new file mode 100644 index 000000000..0a9200052 --- /dev/null +++ b/packages/core/src/lib/expandPartials.js @@ -0,0 +1,90 @@ +'use strict'; + +const logger = require('./log'); +const ph = require('./parameter_hunter'); +const smh = require('./style_modifier_hunter'); +const jsonCopy = require('./json_copy'); +const getPartial = require('./get'); + +const parameter_hunter = new ph(); +const style_modifier_hunter = new smh(); + +module.exports = function(currentPattern, patternlab) { + const processRecursive = require('./processRecursive'); + + //find how many partials there may be for the given pattern + const foundPatternPartials = currentPattern.findPartials(); + + // expand any partials present in this pattern; that is, drill down into + // the template and replace their calls in this template with rendered + // results + if ( + currentPattern.engine.expandPartials && + (foundPatternPartials !== null && foundPatternPartials.length > 0) + ) { + logger.debug(`found partials for ${currentPattern.patternPartial}`); + + // determine if the template contains any pattern parameters. if so they + // must be immediately consumed + return parameter_hunter + .find_parameters(currentPattern, patternlab) + .then(() => { + //do something with the regular old partials + foundPatternPartials.forEach(foundPartial => { + const partial = currentPattern.findPartial(foundPartial); + const partialPattern = getPartial(partial, patternlab); + + //recurse through nested partials to fill out this extended template. + return processRecursive(partialPattern.relPath, patternlab) + .then(() => { + //eslint-disable-line no-loop-func + + //complete assembly of extended template + //create a copy of the partial so as to not pollute it after the getPartial call. + const cleanPartialPattern = jsonCopy( + partialPattern, + `partial pattern ${partial}` + ); + + //if partial has style modifier data, replace the styleModifier value + if ( + currentPattern.stylePartials && + currentPattern.stylePartials.length > 0 + ) { + style_modifier_hunter.consume_style_modifier( + cleanPartialPattern, + foundPartial, + patternlab + ); + } + + //this is what we came here for + logger.debug( + `within ${currentPattern.patternPartial}, replacing extendedTemplate partial ${foundPartial} with ${cleanPartialPattern.patternPartial}'s extendedTemplate` + ); + + currentPattern.extendedTemplate = currentPattern.extendedTemplate.replace( + foundPartial, + cleanPartialPattern.extendedTemplate + ); + + // update the extendedTemplate in the partials object in case this + // pattern is consumed later + patternlab.partials[currentPattern.patternPartial] = + currentPattern.extendedTemplate; + + return Promise.resolve(); + }) + .catch(reason => { + console.log(reason); + logger.error(reason); + }); + }); + }) + .catch(reason => { + console.log(reason); + logger.error(reason); + }); + } + return Promise.resolve(); +}; diff --git a/packages/core/src/lib/exportData.js b/packages/core/src/lib/exportData.js new file mode 100644 index 000000000..9a8468ce5 --- /dev/null +++ b/packages/core/src/lib/exportData.js @@ -0,0 +1,112 @@ +'use strict'; + +const eol = require('os').EOL; +const path = require('path'); +const _ = require('lodash'); + +const ae = require('./annotation_exporter'); + +let fs = require('fs-extra'); //eslint-disable-line prefer-const + +/** + * Write out our pattern information for use by the front end + * @param patternlab - global data store + */ +module.exports = function(patternlab) { + const annotation_exporter = new ae(patternlab); + + const paths = patternlab.config.paths; + + //write out the data + let output = ''; + + //config + output += 'var config = ' + JSON.stringify(patternlab.config) + ';\n'; + + //ishControls + output += + 'var ishControls = {"ishControlsHide":' + + JSON.stringify(patternlab.config.ishControlsHide) + + '};' + + eol; + + //navItems + output += + 'var navItems = {"patternTypes": ' + + JSON.stringify(patternlab.patternTypes) + + ', "ishControlsHide": ' + + JSON.stringify(patternlab.config.ishControlsHide) + + '};' + + eol; + + //patternPaths + output += + 'var patternPaths = ' + JSON.stringify(patternlab.patternPaths) + ';' + eol; + + //viewAllPaths + output += + 'var viewAllPaths = ' + JSON.stringify(patternlab.viewAllPaths) + ';' + eol; + + //plugins + output += + 'var plugins = ' + JSON.stringify(patternlab.plugins || []) + ';' + eol; + + //smaller config elements + output += + 'var defaultShowPatternInfo = ' + + (patternlab.config.defaultShowPatternInfo + ? patternlab.config.defaultShowPatternInfo + : 'false') + + ';' + + eol; + output += + 'var defaultPattern = "' + + (patternlab.config.defaultPattern + ? patternlab.config.defaultPattern + : 'all') + + '";' + + eol; + + //annotations + const annotationsJSON = annotation_exporter.gather(); + const annotations = + 'var comments = { "comments" : ' + JSON.stringify(annotationsJSON) + '};'; + _.each(patternlab.uikits, uikit => { + fs.outputFileSync( + path.resolve( + path.join( + process.cwd(), + uikit.outputDir, + paths.public.annotations, + 'annotations.js' + ) + ), + annotations + ); + }); + + // add module.export to the Nodejs-specific file generated. + const exportedOutput = + output + + 'module.exports = { config, ishControls, navItems, patternPaths, viewAllPaths, plugins, defaultShowPatternInfo, defaultPattern };'; + + //write all output to patternlab-data + _.each(patternlab.uikits, uikit => { + fs.outputFileSync( + path.resolve( + path.join(process.cwd(), uikit.outputDir, paths.public.data), + 'patternlab-data.js' + ), + output + ); + fs.outputFileSync( + path.resolve( + path.join(process.cwd(), uikit.outputDir, paths.public.data), + 'patternlab-data.cjs.js' + ), + exportedOutput + ); + }); + + return output; +}; diff --git a/packages/core/src/lib/findModules.js b/packages/core/src/lib/findModules.js new file mode 100644 index 000000000..246612ec8 --- /dev/null +++ b/packages/core/src/lib/findModules.js @@ -0,0 +1,60 @@ +'use strict'; + +const path = require('path'); + +const isScopedPackage = require('./isScopedPackage'); + +let fs = require('fs-extra'); // eslint-disable-line + +const isDir = fPath => { + const stats = fs.lstatSync(fPath); + return stats.isDirectory() || stats.isSymbolicLink(); +}; + +module.exports = (dir, filter) => { + /** + * @name findModules + * @desc Traverse the given path and gather possible engines + * @param {string} fPath - The file path to traverse + * @param {Array} foundModules - An array of modules + * @return {Array} - The final array of engines + */ + const findModules = (fPath, foundModules) => { + /** + * @name dirList + * @desc A list of all directories in the given path + * @type {Array} + */ + const dirList = fs + .readdirSync(fPath) + .filter(p => isDir(path.join(fPath, p))); + + /** + * @name m + * @desc For the current dir get all modules + * @type {Array} + */ + const m = foundModules.concat( + dirList.filter(filter).map(mod => { + return { + name: filter(mod), + modulePath: path.join(fPath, mod), + }; + }) + ); + + /** + * 1. Flatten all engines from inner recursions and current dir + * 2. Filter the dirList for scoped packages + * 3. Map over every scoped package and recurse into it to find scoped modules + */ + return [].concat( + ...m, + ...dirList + .filter(isScopedPackage) // 2 + .map(scope => findModules(path.join(fPath, scope), m)) // 3 + ); + }; + + return findModules(dir, []); +}; diff --git a/packages/core/src/lib/get.js b/packages/core/src/lib/get.js new file mode 100644 index 000000000..2feccc135 --- /dev/null +++ b/packages/core/src/lib/get.js @@ -0,0 +1,43 @@ +'use strict'; + +const logger = require('./log'); + +module.exports = function(partialName, patternlab, reportWarning = true) { + //look for exact partial matches + for (let i = 0; i < patternlab.patterns.length; i++) { + if (patternlab.patterns[i].patternPartial === partialName) { + return patternlab.patterns[i]; + } + } + + //else look by verbose syntax + for (let j = 0; j < patternlab.patterns.length; j++) { + switch (partialName) { + case patternlab.patterns[j].relPath: + return patternlab.patterns[j]; + case patternlab.patterns[j].verbosePartial: + return patternlab.patterns[j]; + } + } + + //return the fuzzy match if all else fails + for (let k = 0; k < patternlab.patterns.length; k++) { + const partialParts = partialName.split('-'); + const partialType = partialParts[0]; + const partialNameEnd = partialParts.slice(1).join('-'); + + if ( + patternlab.patterns[k].patternPartial.split('-')[0] === partialType && + patternlab.patterns[k].patternPartial.indexOf(partialNameEnd) > -1 + ) { + return patternlab.patterns[k]; + } + } + if (reportWarning) { + logger.warning( + `Could not find pattern referenced with partial syntax ${partialName}. + This can occur when a pattern was renamed, moved, or no longer exists but it still referenced within a different template or within data as a link.` + ); + } + return undefined; +}; diff --git a/packages/core/src/lib/isScopedPackage.js b/packages/core/src/lib/isScopedPackage.js new file mode 100644 index 000000000..cffd1d811 --- /dev/null +++ b/packages/core/src/lib/isScopedPackage.js @@ -0,0 +1,16 @@ +'use strict'; + +const path = require('path'); + +const scopeMatch = /^@(.*)$/; + +/** + * @name isScopedPackage + * @desc Checks whether a path in modules belongs to a scoped package + * @param {string} filePath - The pathname to check + * @return {Boolean} - Returns a bool when found, false othersie + */ +module.exports = filePath => { + const baseName = path.basename(filePath); + return scopeMatch.test(baseName); +}; diff --git a/packages/core/src/lib/json_copy.js b/packages/core/src/lib/json_copy.js new file mode 100644 index 000000000..a757452e3 --- /dev/null +++ b/packages/core/src/lib/json_copy.js @@ -0,0 +1,16 @@ +'use strict'; + +const logger = require('./log'); +const json_copy = (data, callee) => { + try { + return JSON.parse(JSON.stringify(data)); + } catch (e) { + //this is unlikely to be hit due to the passed in data already being loaded using JSON parsers + logger.warning( + `JSON provided by ${callee} is invalid and cannot be copied` + ); + throw e; + } +}; + +module.exports = json_copy; diff --git a/packages/core/src/lib/lineage_hunter.js b/packages/core/src/lib/lineage_hunter.js new file mode 100644 index 000000000..657897d21 --- /dev/null +++ b/packages/core/src/lib/lineage_hunter.js @@ -0,0 +1,170 @@ +'use strict'; +const getPartial = require('./get'); +const logger = require('./log'); + +const lineage_hunter = function() { + function findlineage(pattern, patternlab) { + // As we are adding edges from pattern to ancestor patterns, ensure it is known to the graph + patternlab.graph.add(pattern); + + //find the {{> template-name }} within patterns + const matches = pattern.findPartials(); + if (matches !== null) { + matches.forEach(function(match) { + //get the ancestorPattern + const ancestorPattern = getPartial( + pattern.findPartial(match), + patternlab + ); + + if ( + ancestorPattern && + pattern.lineageIndex.indexOf(ancestorPattern.patternPartial) === -1 + ) { + //add it since it didnt exist + pattern.lineageIndex.push(ancestorPattern.patternPartial); + + //create the more complex patternLineage object too + const l = { + lineagePattern: ancestorPattern.patternPartial, + lineagePath: '../../patterns/' + ancestorPattern.patternLink, + }; + if (ancestorPattern.patternState) { + l.lineageState = ancestorPattern.patternState; + } + + patternlab.graph.add(ancestorPattern); + + // Confusing: pattern includes "ancestorPattern", not the other way round + patternlab.graph.link(pattern, ancestorPattern); + + pattern.lineage.push(l); + + //also, add the lineageR entry if it doesn't exist + if ( + ancestorPattern.lineageRIndex.indexOf(pattern.patternPartial) === -1 + ) { + ancestorPattern.lineageRIndex.push(pattern.patternPartial); + + //create the more complex patternLineage object in reverse + const lr = { + lineagePattern: pattern.patternPartial, + lineagePath: '../../patterns/' + pattern.patternLink, + }; + if (pattern.patternState) { + lr.lineageState = pattern.patternState; + } + + ancestorPattern.lineageR.push(lr); + Object.assign(patternlab.graph.node(ancestorPattern), lr); + } + } + }); + } + } + + /** + * Apply the target pattern state either to any predecessors or successors of the given + * pattern in the pattern graph. + * @param direction Either 'fromPast' or 'fromFuture' + * @param pattern {Pattern} + * @param targetPattern {Pattern} + * @param graph {PatternGraph} + */ + function setPatternState(direction, pattern, targetPattern, graph) { + let index = null; + if (direction === 'fromPast') { + index = graph.lineage(pattern); + } else { + index = graph.lineageR(pattern); + } + + // if the request came from the past, apply target pattern state to current pattern lineage + for (let i = 0; i < index.length; i++) { + if (index[i].patternPartial === targetPattern.patternPartial) { + index[i].lineageState = targetPattern.patternState; + } + } + } + + function cascadePatternStates(patternlab) { + for (let i = 0; i < patternlab.patterns.length; i++) { + const pattern = patternlab.patterns[i]; + + //for each pattern with a defined state + if (pattern.patternState) { + const lineage = patternlab.graph.lineage(pattern); + + if (lineage && lineage.length > 0) { + //find all lineage - patterns being consumed by this one + for (let h = 0; h < lineage.length; h++) { + setPatternState( + 'fromFuture', + lineage[h], + pattern, + patternlab.graph + ); + } + } + const lineageR = patternlab.graph.lineageR(pattern); + if (lineageR && lineageR.length > 0) { + //find all reverse lineage - that is, patterns consuming this one + for (let j = 0; j < lineageR.length; j++) { + const lineageRPattern = lineageR[j]; + + //only set patternState if pattern.patternState "is less than" the lineageRPattern.patternstate + //or if lineageRPattern.patternstate (the consuming pattern) does not have a state + //this makes patternlab apply the lowest common ancestor denominator + const patternStateCascade = patternlab.config.patternStateCascade; + const patternStateIndex = patternStateCascade.indexOf( + pattern.patternState + ); + const patternReverseStateIndex = patternStateCascade.indexOf( + lineageRPattern.patternState + ); + if ( + lineageRPattern.patternState === '' || + patternStateIndex < patternReverseStateIndex + ) { + const oldState = + lineageRPattern.patternState === '' + ? '<>' + : lineageRPattern.patternState; + logger.info( + `Found a lower common denominator pattern state: ${pattern.patternState} on ${pattern.patternPartial}. Setting reverse lineage pattern ${lineageRPattern.patternPartial} from ${oldState}` + ); + + lineageRPattern.patternState = pattern.patternState; + + //take this opportunity to overwrite the lineageRPattern's lineage state too + setPatternState( + 'fromPast', + lineageRPattern, + pattern, + patternlab.graph + ); + } else { + setPatternState( + 'fromPast', + pattern, + lineageRPattern, + patternlab.graph + ); + } + } + } + } + } + } + + return { + find_lineage: function(pattern, patternlab) { + findlineage(pattern, patternlab); + }, + cascade_pattern_states: function(patternlab) { + cascadePatternStates(patternlab); + }, + }; +}; + +module.exports = lineage_hunter; diff --git a/packages/core/src/lib/list_item_hunter.js b/packages/core/src/lib/list_item_hunter.js new file mode 100644 index 000000000..e6fd3818b --- /dev/null +++ b/packages/core/src/lib/list_item_hunter.js @@ -0,0 +1,48 @@ +'use strict'; + +const list_item_hunter = function() { + const logger = require('./log'); + + function processListItemPartials(pattern) { + //find any listitem blocks + const matches = pattern.findListItems(); + + if (matches !== null) { + return matches.reduce((previousMatchPromise, liMatchStart) => { + return previousMatchPromise.then(() => { + logger.debug( + `found listItem of size ${liMatchStart} inside ${pattern.patternPartial}` + ); + + //we found a listitem match + //replace it's beginning listitems.number with -number + const newStart = liMatchStart.replace('.', '-'); + pattern.extendedTemplate = pattern.extendedTemplate.replace( + liMatchStart, + newStart + ); + + //replace it's ending listitems.number with -number + const liMatchEnd = liMatchStart.replace('#', '/'); + const newEnd = liMatchEnd.replace('.', '-'); + pattern.extendedTemplate = pattern.extendedTemplate.replace( + liMatchEnd, + newEnd + ); + + return Promise.resolve(); + }); + }, Promise.resolve()); + } else { + return Promise.resolve(); + } + } + + return { + process_list_item_partials: function(pattern) { + return processListItemPartials(pattern); + }, + }; +}; + +module.exports = list_item_hunter; diff --git a/packages/core/src/lib/loadPattern.js b/packages/core/src/lib/loadPattern.js new file mode 100644 index 000000000..c0fb1d6a5 --- /dev/null +++ b/packages/core/src/lib/loadPattern.js @@ -0,0 +1,199 @@ +'use strict'; + +const path = require('path'); + +const Pattern = require('./object_factory').Pattern; +const mp = require('./markdown_parser'); +const logger = require('./log'); +const patternEngines = require('./pattern_engines'); +const ch = require('./changes_hunter'); +const da = require('./data_loader'); +const addPattern = require('./addPattern'); +const buildListItems = require('./buildListItems'); +const readDocumentation = require('./readDocumentation'); + +const markdown_parser = new mp(); +const changes_hunter = new ch(); +const dataLoader = new da(); + +//this is mocked in unit tests +let fs = require('fs-extra'); //eslint-disable-line prefer-const + +// loads a pattern from disk, creates a Pattern object from it and +// all its associated files, and records it in patternlab.patterns[] +module.exports = function(relPath, patternlab) { + const relativeDepth = (relPath.match(/\w(?=\\)|\w(?=\/)/g) || []).length; + if (relativeDepth > 3) { + logger.warning(''); + logger.warning('Warning:'); + logger.warning( + 'A pattern file: ' + + relPath + + ' was found greater than 3 levels deep from ' + + patternlab.config.paths.source.patterns + + '.' + ); + logger.warning( + "It's strongly suggested to not deviate from the following structure under _patterns/" + ); + logger.warning( + '[patternType]/[patternSubtype]/[patternName].[patternExtension]' + ); + logger.warning('or'); + logger.warning( + '[patternType]/[patternSubtype]/[patternName]/[patternName].[patternExtension]' + ); + logger.warning(''); + logger.warning( + 'While Pattern Lab may still function, assets may 404 and frontend links may break. Consider yourself warned. ' + ); + logger.warning( + 'Read More: http://patternlab.io/docs/pattern-organization.html' + ); + logger.warning(''); + } + + //check if the found file is a top-level markdown file + const fileObject = path.parse(relPath); + if (fileObject.ext === '.md') { + try { + const proposedDirectory = path.resolve( + patternlab.config.paths.source.patterns, + fileObject.dir, + fileObject.name + ); + const proposedDirectoryStats = fs.statSync(proposedDirectory); + if (proposedDirectoryStats.isDirectory()) { + const subTypeMarkdownFileContents = fs.readFileSync( + proposedDirectory + '.md', + 'utf8' + ); + const subTypeMarkdown = markdown_parser.parse( + subTypeMarkdownFileContents + ); + const subTypePattern = new Pattern(relPath, null, patternlab); + subTypePattern.patternSectionSubtype = true; + subTypePattern.patternDesc = subTypeMarkdown + ? subTypeMarkdown.markdown + : ''; + subTypePattern.flatPatternPath = + subTypePattern.flatPatternPath + '-' + subTypePattern.fileName; + subTypePattern.isPattern = false; + subTypePattern.engine = null; + patternlab.subtypePatterns[ + subTypePattern.patternPartial + ] = subTypePattern; + + return subTypePattern; + } + } catch (err) { + // no file exists, meaning it's a pattern markdown file + if (err.code !== 'ENOENT') { + logger.warning(err); + } + } + } + + //extract some information + const filename = fileObject.base; + const ext = fileObject.ext; + const patternsPath = patternlab.config.paths.source.patterns; + + // skip non-pattern files + if (!patternEngines.isPatternFile(filename, patternlab)) { + return null; + } + + //make a new Pattern Object + const currentPattern = new Pattern(relPath, null, patternlab); + + //if file is named in the syntax for variants + if (patternEngines.isPseudoPatternJSON(filename)) { + return currentPattern; + } + + //can ignore all non-supported files at this point + if (patternEngines.isFileExtensionSupported(ext) === false) { + return currentPattern; + } + + //look for a json file for this template + let jsonFilename; + try { + jsonFilename = path.resolve( + patternsPath, + currentPattern.subdir, + currentPattern.fileName + ); + const patternData = dataLoader.loadDataFromFile(jsonFilename, fs); + + if (patternData) { + currentPattern.jsonFileData = patternData; + logger.debug( + `found pattern-specific data for ${currentPattern.patternPartial}` + ); + } + } catch (err) { + logger.warning( + `There was an error parsing sibling JSON for ${currentPattern.relPath}` + ); + logger.warning(err); + } + + //look for a listitems.json file for this template + let listJsonFileName; + try { + listJsonFileName = path.resolve( + patternsPath, + currentPattern.subdir, + `${currentPattern.fileName}.listitems` + ); + const listItemsData = dataLoader.loadDataFromFile(listJsonFileName, fs); + + if (listItemsData) { + logger.debug( + `found pattern-specific listitems data for ${currentPattern.patternPartial}` + ); + currentPattern.listitems = listItemsData; + buildListItems(currentPattern); + } + } catch (err) { + logger.warning( + `There was an error parsing sibling listitem JSON for ${currentPattern.relPath}` + ); + logger.warning(err); + } + + //look for a markdown file for this template + readDocumentation(currentPattern, patternlab); + + //add the raw template to memory + const templatePath = path.resolve(patternsPath, currentPattern.relPath); + + currentPattern.template = fs.readFileSync(templatePath, 'utf8'); + + //find any stylemodifiers that may be in the current pattern + currentPattern.stylePartials = currentPattern.findPartialsWithStyleModifiers(); + + //find any pattern parameters that may be in the current pattern + currentPattern.parameteredPartials = currentPattern.findPartialsWithPatternParameters(); + + [ + templatePath, + `${jsonFilename}.json`, + `${jsonFilename}.yml`, + `${jsonFilename}.yaml`, + `${listJsonFileName}.json`, + `${listJsonFileName}.yml`, + `${listJsonFileName}.yaml`, + ].forEach(file => { + changes_hunter.checkLastModified(currentPattern, file); + }); + + changes_hunter.checkBuildState(currentPattern, patternlab); + + //add currentPattern to patternlab.patterns array + addPattern(currentPattern, patternlab); + + return currentPattern; +}; diff --git a/packages/core/src/lib/loadPatternGraph.js b/packages/core/src/lib/loadPatternGraph.js new file mode 100644 index 000000000..c1f4ab851 --- /dev/null +++ b/packages/core/src/lib/loadPatternGraph.js @@ -0,0 +1,21 @@ +'use strict'; + +const PatternGraph = require('./pattern_graph').PatternGraph; + +/** + * If a graph was serialized and then {@code deletePatternDir == true}, there is a mismatch in the + * pattern metadata and not all patterns might be recompiled. + * For that reason an empty graph is returned in this case, so every pattern will be flagged as + * "needs recompile". Otherwise the pattern graph is loaded from the meta data. + * + * @param patternlab + * @param {boolean} deletePatternDir When {@code true}, an empty graph is returned + * @return {PatternGraph} + */ +module.exports = (patternlab, deletePatternDir) => { + // Sanity check to prevent problems when code is refactored + if (deletePatternDir) { + return PatternGraph.empty(); + } + return PatternGraph.loadFromFile(); +}; diff --git a/packages/core/src/lib/loaduikits.js b/packages/core/src/lib/loaduikits.js new file mode 100644 index 000000000..dc152ae3d --- /dev/null +++ b/packages/core/src/lib/loaduikits.js @@ -0,0 +1,99 @@ +'use strict'; + +const path = require('path'); +const _ = require('lodash'); + +const logger = require('./log'); + +let findModules = require('./findModules'); //eslint-disable-line prefer-const +let fs = require('fs-extra'); // eslint-disable-line + +const uiKitMatcher = /^uikit-(.*)$/; +const nodeModulesPath = path.join(process.cwd(), 'node_modules'); + +/** + * Given a path: return the uikit name if the path points to a valid uikit + * module directory, or false if it doesn't. + * @param filePath + * @returns UIKit name if exists or FALSE + */ +const isUIKitModule = filePath => { + const baseName = path.basename(filePath); + const engineMatch = baseName.match(uiKitMatcher); + + if (engineMatch) { + return engineMatch[1]; + } + return false; +}; + +const readModuleFile = (kit, subPath) => { + return fs.readFileSync( + path.resolve(path.join(kit.modulePath, subPath)), + 'utf8' + ); +}; + +/** + * Loads uikits, connecting configuration and installed modules + * [1] Looks in node_modules for uikits. + * [2] Filter out our uikit-polyfills package. + * [3] Only continue if uikit is enabled in patternlab-config.json + * [4] Reads files from uikit that apply to every template + * @param {object} patternlab + */ +module.exports = patternlab => { + const paths = patternlab.config.paths; + + const uikits = findModules(nodeModulesPath, isUIKitModule) // [1] + .filter(kit => kit.name !== 'polyfills'); // [2] + uikits.forEach(kit => { + const configEntry = _.find(_.filter(patternlab.config.uikits, 'enabled'), { + name: `uikit-${kit.name}`, + }); // [3] + + if (!configEntry) { + logger.warning( + `Could not find uikit with name uikit-${kit.name} defined within patternlab-config.json, or it is not enabled.` + ); + return; + } + + try { + patternlab.uikits[`uikit-${kit.name}`] = { + name: `uikit-${kit.name}`, + modulePath: kit.modulePath, + enabled: true, + outputDir: configEntry.outputDir, + excludedPatternStates: configEntry.excludedPatternStates, + excludedTags: configEntry.excludedTags, + header: readModuleFile( + kit, + paths.source.patternlabFiles['general-header'] + ), + footer: readModuleFile( + kit, + paths.source.patternlabFiles['general-footer'] + ), + patternSection: readModuleFile( + kit, + paths.source.patternlabFiles.patternSection + ), + patternSectionSubType: readModuleFile( + kit, + paths.source.patternlabFiles.patternSectionSubtype + ), + viewAll: readModuleFile(kit, paths.source.patternlabFiles.viewall), + }; // [4] + } catch (ex) { + logger.error(ex); + logger.error( + '\nERROR: missing an essential file from ' + + kit.modulePath + + paths.source.patternlabFiles + + ". Pattern Lab won't work without this file.\n" + ); + } + }); + return Promise.resolve(); +}; diff --git a/packages/core/src/lib/log.js b/packages/core/src/lib/log.js new file mode 100644 index 000000000..87a7cd587 --- /dev/null +++ b/packages/core/src/lib/log.js @@ -0,0 +1,80 @@ +'use strict'; + +const chalk = require('chalk'); +const EventEmitter = require('events').EventEmitter; + +/** + * @name log + * @desc tiny event-based logger + * @type {*} + */ +const log = Object.assign( + { + debug(msg) { + this.emit('debug', chalk.green(msg)); + }, + info(msg) { + this.emit('info', msg); + }, + warning(msg) { + this.emit('warning', chalk.yellow(msg)); + }, + error(msg) { + this.emit('error', chalk.red(msg)); + }, + }, + EventEmitter.prototype +); + +/** + * @func debug + * @desc Coloured debug log + * @param {*} msg - The variadic messages to log out. + * @return {void} + */ +const debug = log.debug.bind(log); + +/** + * @func info + * @desc Coloured info log + * @param {*} msg - The variadic messages to log out. + * @return {void} + */ +const info = log.info.bind(log); + +/** + * @func warning + * @desc Coloured warning log + * @param {*} e - The variadic messages to log out. + * @return {void} + */ +const warning = log.warning.bind(log); + +/** + * @func error + * @desc Coloured error log + * @param {*} e - The variadic messages to log out. + * @return {void} + */ +const error = log.error.bind(log); + +/** + * Useful for reporting errors in .catch() on Promises + * @param {string} - a message to report + * @returns {function} - a callback to be passed to a Promise's .catch() + */ +const reportError = function(message) { + return function(err) { + console.log(message); + console.log(err); + }; +}; + +module.exports = { + debug, + info, + warning, + error, + log, + reportError, +}; diff --git a/packages/core/src/lib/markModifiedPatterns.js b/packages/core/src/lib/markModifiedPatterns.js new file mode 100644 index 000000000..aa80b25bb --- /dev/null +++ b/packages/core/src/lib/markModifiedPatterns.js @@ -0,0 +1,56 @@ +'use strict'; + +const path = require('path'); +const _ = require('lodash'); + +const CompileState = require('./object_factory').CompileState; +const ch = require('./changes_hunter'); +const changes_hunter = new ch(); + +//this is mocked in unit tests +let fs = require('fs-extra'); //eslint-disable-line prefer-const + +/** + * Finds patterns that were modified and need to be rebuilt. For clean patterns load the already + * rendered markup. + * + * @param lastModified + * @param patternlab + */ +module.exports = function(lastModified, patternlab) { + /** + * If the given array exists, apply a function to each of its elements + * @param {Array} array + * @param {Function} func + */ + const forEachExisting = (array, func) => { + if (array) { + array.forEach(func); + } + }; + const modifiedOrNot = _.groupBy(patternlab.patterns, p => + changes_hunter.needsRebuild(lastModified, p) ? 'modified' : 'notModified' + ); + + // For all unmodified patterns load their rendered template output + forEachExisting(modifiedOrNot.notModified, cleanPattern => { + _.each(patternlab.uikits, uikit => { + const xp = path.join( + process.cwd(), + uikit.outputDir, + patternlab.config.paths.public.patterns, + cleanPattern.getPatternLink(patternlab, 'markupOnly') + ); + + // Pattern with non-existing markupOnly files were already marked for rebuild and thus are not "CLEAN" + cleanPattern.patternPartialCode = fs.readFileSync(xp, 'utf8'); + }); + }); + + // For all patterns that were modified, schedule them for rebuild + forEachExisting( + modifiedOrNot.modified, + p => (p.compileState = CompileState.NEEDS_REBUILD) + ); + return modifiedOrNot; +}; diff --git a/core/lib/markdown_parser.js b/packages/core/src/lib/markdown_parser.js similarity index 70% rename from core/lib/markdown_parser.js rename to packages/core/src/lib/markdown_parser.js index 495e0d181..c940a5f67 100644 --- a/core/lib/markdown_parser.js +++ b/packages/core/src/lib/markdown_parser.js @@ -1,28 +1,27 @@ -"use strict"; - -var md = require('markdown-it')(); -var yaml = require('js-yaml'); - -var markdown_parser = function () { +'use strict'; +const md = require('markdown-it')(); +const yaml = require('js-yaml'); +const logger = require('./log'); +const markdown_parser = function() { /** * Converts a markdown block with frontmatter (each is optional, technically) to a well-formed object. * @param block - the ".md" file, which can contain frontmatter or not, or only frontmatter. * @returns an object with any frontmatter keys, plus a .markdown key - */ + */ function parseMarkdownBlock(block) { - var returnObject = {}; + let returnObject = {}; try { //for each block process the yaml frontmatter and markdown - var frontmatterRE = /---\r?\n{1}([\s\S]*)---\r?\n{1}([\s\S]*)+/gm; - var chunks = frontmatterRE.exec(block); + const frontmatterRE = /---\r?\n{1}([\s\S]*)---\r?\n{1}([\s\S]*)+/gm; + const chunks = frontmatterRE.exec(block); if (chunks) { //we got some frontmatter if (chunks && chunks[1]) { //parse the yaml if we got it - var frontmatter = chunks[1]; + const frontmatter = chunks[1]; returnObject = yaml.safeLoad(frontmatter); } @@ -37,8 +36,8 @@ var markdown_parser = function () { returnObject.markdown = md.render(block); } } catch (ex) { - console.log(ex); - console.log('error parsing markdown block', block); + logger.warning(ex); + logger.warning(`error parsing markdown block ${block}`); return undefined; } @@ -47,11 +46,10 @@ var markdown_parser = function () { } return { - parse: function (block) { + parse: function(block) { return parseMarkdownBlock(block); - } + }, }; - }; module.exports = markdown_parser; diff --git a/packages/core/src/lib/object_factory.js b/packages/core/src/lib/object_factory.js new file mode 100644 index 000000000..83524a9c7 --- /dev/null +++ b/packages/core/src/lib/object_factory.js @@ -0,0 +1,269 @@ +'use strict'; +const patternEngines = require('./pattern_engines'); +const path = require('path'); + +// patternPrefixMatcher 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+-)?/; + +// Pattern properties +/** + * Pattern constructor + * @constructor + */ +const Pattern = function(relPath, data, patternlab) { + /** + * 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; + + 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; + } else { + this.name = + this.subdir.replace(/[\/\\]/g, '-') + + '-' + + this.fileName.replace('~', '-'); // '00-atoms-00-global-00-colors' + } + + // the JSON used to render values in the pattern + this.jsonFileData = data || {}; + + // strip leading "00-" from the file name and flip tildes to dashes + this.patternBaseName = this.fileName + .replace(patternPrefixMatcher, '') + .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 + + //00-atoms if needed + this.patternType = this.getDirLevel(0); + + // the top-level pattern group this pattern belongs to. 'atoms' + this.patternGroup = this.patternType.replace(patternPrefixMatcher, ''); + + //00-colors if needed + this.patternSubType = this.getDirLevel(1); + + // the sub-group this pattern belongs to. + this.patternSubGroup = this.patternSubType.replace(patternPrefixMatcher, ''); // '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 + // (rendered!) html file for this pattern, to be shown in the iframe + this.patternLink = this.patternSectionSubtype + ? `$${this.name}/index.html` + : patternlab + ? this.getPatternLink(patternlab, 'rendered') + : null; + + // The canonical "key" by which this pattern is known. This is the callable + // name of the pattern. UPDATE: this.key is now known as this.patternPartial + this.patternPartial = this.patternGroup + '-' + this.patternBaseName; + + // 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.isPattern = true; + this.isFlatPattern = this.patternGroup === this.patternSubGroup; + this.patternState = ''; + this.template = ''; + this.patternPartialCode = ''; + this.lineage = []; + this.lineageIndex = []; + this.lineageR = []; + this.lineageRIndex = []; + this.isPseudoPattern = false; + this.order = Number.MAX_SAFE_INTEGER; + this.engine = patternEngines.getEngineForPattern(this); + + /** + * Determines if this pattern needs to be recompiled. + * + * @ee {@link CompileState}*/ + this.compileState = null; + + /** + * Timestamp in milliseconds when the pattern template or auxilary file (e.g. json) were modified. + * If multiple files are affected, this is the timestamp of the most recent change. + * + * @see {@link pattern} + */ + this.lastModified = null; +}; + +// Pattern methods + +Pattern.prototype = { + // render function - acts as a proxy for the PatternEngine's + render: function(data, partials) { + if (!this.extendedTemplate) { + this.extendedTemplate = this.template; + } + + if (this.engine) { + const promise = this.engine.renderPattern( + this, + data || this.jsonFileData, + partials + ); + return promise + .then(results => { + return results; + }) + .catch(reason => { + return Promise.reject(reason); + }); + } + return Promise.reject('where is the engine?'); + }, + + registerPartial: function() { + if (this.engine && typeof this.engine.registerPartial === 'function') { + this.engine.registerPartial(this); + } + }, + + // 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' + getPatternLink: function(patternlab, suffixType, customfileExtension) { + // if no suffixType is provided, we default to rendered + const suffixConfig = patternlab.config.outputFileSuffixes; + const suffix = suffixType + ? suffixConfig[suffixType] + : suffixConfig.rendered; + + if (suffixType === 'rawTemplate') { + return this.name + path.sep + this.name + suffix + this.fileExtension; + } + + if (suffixType === 'custom') { + return this.name + path.sep + this.name + customfileExtension; + } + + return this.name + path.sep + this.name + suffix + '.html'; + }, + + // the finders all delegate to the PatternEngine, which also encapsulates all + // appropriate regexes + findPartials: function() { + return this.engine.findPartials(this); + }, + + findPartialsWithStyleModifiers: function() { + return this.engine.findPartialsWithStyleModifiers(this); + }, + + findPartialsWithPatternParameters: function() { + return this.engine.findPartialsWithPatternParameters(this); + }, + + findListItems: function() { + return this.engine.findListItems(this); + }, + + findPartial: function(partialString) { + return this.engine.findPartial(partialString); + }, + + getDirLevel: function(level) { + const items = this.subdir.split(path.sep); + + if (items[level]) { + return items[level]; + } else if (items[level - 1]) { + return items[level - 1]; + } else { + return ''; + } + }, +}; + +// Pattern static methods + +// factory: creates an empty Pattern for miscellaneous internal use, such as +// by list_item_hunter +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; + } + } + + const pattern = new Pattern(relPath, null, 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. +Pattern.create = function(relPath, data, customProps, patternlab) { + const newPattern = new Pattern(relPath || '', data || null, patternlab); + return Object.assign(newPattern, customProps); +}; + +const CompileState = { + NEEDS_REBUILD: 'needs rebuild', + BUILDING: 'building', + CLEAN: 'clean', +}; + +module.exports = { + Pattern: Pattern, + CompileState: CompileState, +}; diff --git a/core/lib/parameter_hunter.js b/packages/core/src/lib/parameter_hunter.js similarity index 58% rename from core/lib/parameter_hunter.js rename to packages/core/src/lib/parameter_hunter.js index 6f2d50f1e..eeec121dc 100644 --- a/core/lib/parameter_hunter.js +++ b/packages/core/src/lib/parameter_hunter.js @@ -1,15 +1,15 @@ -"use strict"; +'use strict'; -var parameter_hunter = function () { +const smh = require('./style_modifier_hunter'); +const style_modifier_hunter = new smh(); - var extend = require('util')._extend, - JSON5 = require('json5'), - pa = require('./pattern_assembler'), - smh = require('./style_modifier_hunter'), - plutils = require('./utilities'), - style_modifier_hunter = new smh(), - pattern_assembler = new pa(); +const getPartial = require('./get'); +const logger = require('./log'); +const parseLink = require('./parseLink'); +const jsonCopy = require('./json_copy'); +const replaceParameter = require('./replaceParameter'); +const parameter_hunter = function() { /** * This function is really to accommodate the lax JSON-like syntax allowed by * Pattern Lab PHP for parameter submissions to partials. Unfortunately, no @@ -19,7 +19,7 @@ var parameter_hunter = function () { * The steps on a high-level are as follows: * * Further escape all escaped quotes and colons. Use the string * representation of their unicodes for this. This has the added bonus - * of being interpreted correctly by JSON5.parse() without further + * of being interpreted correctly by JSON.parse() without further * modification. This will be useful later in the function. * * Once escaped quotes are out of the way, we know the remaining quotes * are either key/value wrappers or wrapped within those wrappers. We know @@ -53,14 +53,24 @@ var parameter_hunter = function () { * @returns {string} paramStringWellFormed */ function paramToJson(pString) { - var colonPos = -1; - var keys = []; - var paramString = pString; // to not reassign param - var paramStringWellFormed; - var quotePos = -1; - var regex; - var values = []; - var wrapper; + let colonPos = -1; + const keys = []; + let paramString = pString; // to not reassign param + let paramStringWellFormed; + let quotePos = -1; + let regex; + const values = []; + let wrapper; + + // attempt to parse the data in case it is already well formed JSON + try { + paramStringWellFormed = JSON.stringify(JSON.parse(pString)); + return paramStringWellFormed; + } catch (err) { + logger.debug( + `Not valid JSON found for passed pattern parameter ${pString} will attempt to parse manually...` + ); + } //replace all escaped double-quotes with escaped unicode paramString = paramString.replace(/\\"/g, '\\u0022'); @@ -74,7 +84,6 @@ var parameter_hunter = function () { //with escaped chars out of the way, crawl through paramString looking for //keys and values do { - //check if searching for a key if (paramString[0] === '{' || paramString[0] === ',') { paramString = paramString.substring(1, paramString.length).trim(); @@ -82,10 +91,9 @@ var parameter_hunter = function () { //search for end quote if wrapped in quotes. else search for colon. //everything up to that position will be saved in the keys array. switch (paramString[0]) { - //need to search for end quote pos in case the quotes wrap a colon case '"': - case '\'': + case "'": wrapper = paramString[0]; quotePos = paramString.indexOf(wrapper, 1); break; @@ -98,11 +106,12 @@ var parameter_hunter = function () { keys.push(paramString.substring(0, quotePos + 1).trim()); //truncate the beginning from paramString and look for a value - paramString = paramString.substring(quotePos + 1, paramString.length).trim(); + paramString = paramString + .substring(quotePos + 1, paramString.length) + .trim(); //unset quotePos quotePos = -1; - } else if (colonPos > -1) { keys.push(paramString.substring(0, colonPos).trim()); @@ -112,8 +121,8 @@ var parameter_hunter = function () { //unset colonPos colonPos = -1; - //if there are no more colons, and we're looking for a key, there is - //probably a problem. stop any further processing. + //if there are no more colons, and we're looking for a key, there is + //probably a problem. stop any further processing. } else { paramString = ''; break; @@ -131,14 +140,13 @@ var parameter_hunter = function () { //readable, we'll use a regex for match() and replace() instead of //performing conditional logic with indexOf(). switch (paramString[0]) { - //since a quote of same type as its wrappers would be escaped, and we //escaped those even further with their unicodes, it is safe to look //for wrapper pairs and conclude that their contents are values case '"': regex = /^"(.|\s)*?"/; break; - case '\'': + case "'": regex = /^'(.|\s)*?'/; break; @@ -159,8 +167,8 @@ var parameter_hunter = function () { break; } - //if there are no more colons, and we're looking for a value, there is - //probably a problem. stop any further processing. + //if there are no more colons, and we're looking for a value, there is + //probably a problem. stop any further processing. } else { paramString = ''; break; @@ -169,20 +177,20 @@ var parameter_hunter = function () { //build paramStringWellFormed string for JSON parsing paramStringWellFormed = '{'; - for (var i = 0; i < keys.length; i++) { - + for (let i = 0; i < keys.length; i++) { //keys //replace single-quote wrappers with double-quotes - if (keys[i][0] === '\'' && keys[i][keys[i].length - 1] === '\'') { + if (keys[i][0] === "'" && keys[i][keys[i].length - 1] === "'") { paramStringWellFormed += '"'; //any enclosed double-quotes must be escaped - paramStringWellFormed += keys[i].substring(1, keys[i].length - 1).replace(/"/g, '\\"'); + paramStringWellFormed += keys[i] + .substring(1, keys[i].length - 1) + .replace(/"/g, '\\"'); paramStringWellFormed += '"'; } else { - //open wrap with double-quotes if no wrapper - if (keys[i][0] !== '"' && keys[i][0] !== '\'') { + if (keys[i][0] !== '"' && keys[i][0] !== "'") { paramStringWellFormed += '"'; //this is to clean up vestiges from Pattern Lab PHP's escaping scheme. @@ -196,7 +204,10 @@ var parameter_hunter = function () { paramStringWellFormed += keys[i]; //close wrap with double-quotes if no wrapper - if (keys[i][keys[i].length - 1] !== '"' && keys[i][keys[i].length - 1] !== '\'') { + if ( + keys[i][keys[i].length - 1] !== '"' && + keys[i][keys[i].length - 1] !== "'" + ) { paramStringWellFormed += '"'; } } @@ -206,14 +217,16 @@ var parameter_hunter = function () { //values //replace single-quote wrappers with double-quotes - if (values[i][0] === '\'' && values[i][values[i].length - 1] === '\'') { + if (values[i][0] === "'" && values[i][values[i].length - 1] === "'") { paramStringWellFormed += '"'; //any enclosed double-quotes must be escaped - paramStringWellFormed += values[i].substring(1, values[i].length - 1).replace(/"/g, '\\"'); + paramStringWellFormed += values[i] + .substring(1, values[i].length - 1) + .replace(/"/g, '\\"'); paramStringWellFormed += '"'; - //for everything else, just add the value however it's wrapped + //for everything else, just add the value however it's wrapped } else { paramStringWellFormed += values[i]; } @@ -226,76 +239,113 @@ var parameter_hunter = function () { paramStringWellFormed += '}'; //unescape escaped unicode except for double-quotes - paramStringWellFormed = paramStringWellFormed.replace(/\\u0027/g, '\''); + paramStringWellFormed = paramStringWellFormed.replace(/\\u0027/g, "'"); paramStringWellFormed = paramStringWellFormed.replace(/\\u0058/g, ':'); return paramStringWellFormed; } + //compile this partial immeadiately, essentially consuming it. function findparameters(pattern, patternlab) { - if (pattern.parameteredPartials && pattern.parameteredPartials.length > 0) { - - //compile this partial immeadiately, essentially consuming it. - pattern.parameteredPartials.forEach(function (pMatch) { - //find the partial's name and retrieve it - var partialName = pMatch.match(/([\w\-\.\/~]+)/g)[0]; - var partialPattern = pattern_assembler.getPartial(partialName, patternlab); - - //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #190 - partialPattern.extendedTemplate = partialPattern.template; - - if (patternlab.config.debug) { - console.log('found patternParameters for ' + partialName); - } - - //strip out the additional data, convert string to JSON. - var leftParen = pMatch.indexOf('('); - var rightParen = pMatch.lastIndexOf(')'); - var paramString = '{' + pMatch.substring(leftParen + 1, rightParen) + '}'; - var paramStringWellFormed = paramToJson(paramString); - - var paramData = {}; - var globalData = {}; - var localData = {}; - - try { - paramData = JSON5.parse(paramStringWellFormed); - globalData = JSON5.parse(JSON5.stringify(patternlab.data)); - localData = JSON5.parse(JSON5.stringify(pattern.jsonFileData || {})); - } catch (err) { - console.log('There was an error parsing JSON for ' + pattern.relPath); - console.log(err); - } - - var allData = plutils.mergeData(globalData, localData); - allData = plutils.mergeData(allData, paramData); - - //if partial has style modifier data, replace the styleModifier value - if (pattern.stylePartials && pattern.stylePartials.length > 0) { - style_modifier_hunter.consume_style_modifier(partialPattern, pMatch, patternlab); - } - - //extend pattern data links into link for pattern link shortcuts to work. we do this locally and globally - allData.link = extend({}, patternlab.data.link); - - var renderedPartial = pattern_assembler.renderPattern(partialPattern.extendedTemplate, allData, patternlab.partials); - - //remove the parameter from the partial and replace it with the rendered partial + paramData - pattern.extendedTemplate = pattern.extendedTemplate.replace(pMatch, renderedPartial); - - //update the extendedTemplate in the partials object in case this pattern is consumed later - patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate; - }); + logger.debug(`processing patternParameters for ${pattern.partialName}`); + + return pattern.parameteredPartials.reduce((previousPromise, pMatch) => { + return previousPromise + .then(() => { + logger.debug(`processing patternParameter ${pMatch}`); + + //find the partial's name and retrieve it + const partialName = pMatch.match(/([\w\-\.\/~]+)/g)[0]; + const partialPattern = jsonCopy( + getPartial( + partialName, + patternlab, + `partial pattern ${partialName}` + ) + ); + + //if we retrieved a pattern we should make sure that its extendedTemplate is reset. looks to fix #190 + if (!partialPattern.extendedTemplate) { + partialPattern.extendedTemplate = partialPattern.template; + } + + if (!pattern.extendedTemplate) { + pattern.extendedTemplate = pattern.template; + } + + logger.debug(`retrieved pattern ${partialName}`); + + //strip out the additional data, convert string to JSON. + const leftParen = pMatch.indexOf('('); + const rightParen = pMatch.lastIndexOf(')'); + const paramString = + '{' + pMatch.substring(leftParen + 1, rightParen) + '}'; + const paramStringWellFormed = paramToJson(paramString); + + let paramData = {}; + + try { + paramData = JSON.parse(paramStringWellFormed); + } catch (err) { + logger.warning( + `There was an error parsing JSON for ${pattern.relPath}` + ); + logger.warning(err); + } + + // resolve any pattern links that might be present + paramData = parseLink( + patternlab, + paramData, + pattern.patternPartial + ); + + // for each property in paramData + for (const prop in paramData) { + if (paramData.hasOwnProperty(prop)) { + // find it within partialPattern.extendedTemplate and replace its value + partialPattern.extendedTemplate = replaceParameter( + partialPattern.extendedTemplate, + prop, + paramData[prop] + ); + } + } + + //if partial has style modifier data, replace the styleModifier value + if (pattern.stylePartials && pattern.stylePartials.length > 0) { + style_modifier_hunter.consume_style_modifier( + partialPattern, + pMatch, + patternlab + ); + } + + // set pattern.extendedTemplate pMatch with replacedPartial + pattern.extendedTemplate = pattern.extendedTemplate.replace( + pMatch, + partialPattern.extendedTemplate + ); + + //todo: this no longer needs to be a promise + return Promise.resolve(); + }) + .catch(reason => { + console.log(reason); + logger.error(reason); + }); + }, Promise.resolve()); } + logger.debug(`pattern has no partials ${pattern.patternPartial}`); + return Promise.resolve(); } return { - find_parameters: function (pattern, patternlab) { - findparameters(pattern, patternlab); - } + find_parameters: function(pattern, patternlab) { + return findparameters(pattern, patternlab); + }, }; - }; module.exports = parameter_hunter; diff --git a/packages/core/src/lib/parseAllLinks.js b/packages/core/src/lib/parseAllLinks.js new file mode 100644 index 000000000..894ad6f83 --- /dev/null +++ b/packages/core/src/lib/parseAllLinks.js @@ -0,0 +1,19 @@ +'use strict'; + +const parseLink = require('./parseLink'); + +//look for pattern links included in data files. +//these will be in the form of link.* WITHOUT {{}}, which would still be there from direct pattern inclusion +module.exports = function(patternlab) { + //look for link.* such as link.pages-blog as a value + patternlab.data = parseLink(patternlab, patternlab.data, 'data.json'); + + //loop through all patterns + for (let i = 0; i < patternlab.patterns.length; i++) { + patternlab.patterns[i].jsonFileData = parseLink( + patternlab, + patternlab.patterns[i].jsonFileData, + patternlab.patterns[i].patternPartial + ); + } +}; diff --git a/packages/core/src/lib/parseLink.js b/packages/core/src/lib/parseLink.js new file mode 100644 index 000000000..d3d0c455e --- /dev/null +++ b/packages/core/src/lib/parseLink.js @@ -0,0 +1,65 @@ +'use strict'; + +const path = require('path'); + +const logger = require('./log'); +const getPartial = require('./get'); + +module.exports = function(patternlab, obj, key) { + //check for 'link.patternPartial' + const linkRE = /(?:'|")(link\.[A-z0-9-_]+)(?:'|")/g; + + //stringify the passed in object + let dataObjAsString; + dataObjAsString = JSON.stringify(obj); + if (!dataObjAsString) { + return obj; + } + + //find matches + const linkMatches = dataObjAsString.match(linkRE); + + if (linkMatches) { + for (let i = 0; i < linkMatches.length; i++) { + const dataLink = linkMatches[i]; + 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, '/'); + + logger.debug( + `expanded data link from ${dataLink} to ${fullLink} inside ${key}` + ); + + //also make sure our global replace didn't mess up a protocol + fullLink = fullLink.replace(/:\//g, '://'); + dataObjAsString = dataObjAsString.replace( + 'link.' + linkPatternPartial, + fullLink + ); + } + } else { + logger.warning(`pattern not found for ${dataLink} inside ${key}`); + } + } + } + } + + let dataObj; + try { + dataObj = JSON.parse(dataObjAsString); + } catch (err) { + logger.warning(`There was an error parsing JSON for ${key}`); + logger.warning(err); + } + + return dataObj; +}; diff --git a/packages/core/src/lib/pattern_engines.js b/packages/core/src/lib/pattern_engines.js new file mode 100644 index 000000000..cde010125 --- /dev/null +++ b/packages/core/src/lib/pattern_engines.js @@ -0,0 +1,251 @@ +// special shoutout to Geoffrey Pursell for single-handedly making Pattern Lab Node Pattern Engines possible! aww thanks :) +'use strict'; +const { existsSync } = require('fs'); +const path = require('path'); + +const findModules = require('./findModules'); + +const engineMatcher = /^engine-(.*)$/; + +const logger = require('./log'); + +const enginesDirectories = [ + { + displayName: 'the core', + path: path.resolve(__dirname, '..', '..', 'node_modules'), + }, + { + displayName: 'the edition or test directory', + path: path.join(process.cwd(), 'node_modules'), + }, +]; + +/** + * Given a path: return the engine name if the path points to a valid engine + * module directory, or false if it doesn't. + * @param filePath + * @returns Engine name if exists or FALSE + */ +function isEngineModule(filePath) { + const baseName = path.basename(filePath); + const engineMatch = baseName.match(engineMatcher); + + if (engineMatch) { + return engineMatch[1]; + } + return false; +} + +/** + * @name resolveEngines + * @desc Creates an array of all available patternlab engines + * @param {string} dir - The directory to search for engines and scoped engines) + * @return {Array} An array of engine objects + */ +function resolveEngines(dir) { + // Guard against non-existent directories. + if (!existsSync(dir)) { + return []; // Silence is golden … + } + + return findModules(dir, isEngineModule); +} + +function findEngineModulesInDirectory(dir) { + const foundEngines = resolveEngines(dir); + return foundEngines; +} + +// +// PatternEngines: the main export of this module +// +// It's an Object/hash of all loaded pattern engines, empty at first. My +// intention here is to make this return an object that can be used to obtain +// any loaded PatternEngine by addressing them like this: +// +// var PatternEngines = require('./pattern_engines/pattern_engines'); +// var Mustache = PatternEngines['mustache']; +// +// Object.create lets us create an object with a specified prototype. We want +// this here because we would like the object's "own properties" to include +// only the engine names so we can easily iterate over them; all the handy +// methods and properites below should therefore be on its prototype. + +const PatternEngines = Object.create({ + /** + * Load all pattern engines. + * @param patternLabConfig + * @memberof PatternEngines + */ + loadAllEngines: function(patternLabConfig) { + const self = this; + + // Try to load engines! We scan for engines at each path specified above. This + // function is kind of a big deal. + enginesDirectories.forEach(function(engineDirectory) { + const enginesInThisDir = findEngineModulesInDirectory( + engineDirectory.path + ); + + logger.debug(`Loading engines from ${engineDirectory.displayName}...`); + + // find all engine-named things in this directory and try to load them, + // unless it's already been loaded. + enginesInThisDir.forEach(function(engineDiscovery) { + let errorMessage; + const successMessage = 'good to go'; + + try { + // Give it a try! load 'er up. But not if we already have, + // of course. Also pass the pattern lab config object into + // the engine's closure scope so it can know things about + // things. + if (self[engineDiscovery.name]) { + throw new Error('already loaded, skipping.'); + } + self[engineDiscovery.name] = require(engineDiscovery.modulePath); + if ( + typeof self[engineDiscovery.name].usePatternLabConfig === 'function' + ) { + self[engineDiscovery.name].usePatternLabConfig(patternLabConfig); + } + if (typeof self[engineDiscovery.name].spawnMeta === 'function') { + self[engineDiscovery.name].spawnMeta(patternLabConfig); + } + } catch (err) { + errorMessage = err.message; + } finally { + // report on the status of the engine, one way or another! + logger.info( + `Pattern Engine ${engineDiscovery.name}: ${ + errorMessage ? errorMessage : successMessage + }` + ); + } + }); + }); + + // Complain if for some reason we haven't loaded any engines. + if (Object.keys(self).length === 0) { + logger.error('No engines loaded! Something is seriously wrong.'); + } + logger.debug(`Done loading engines`); + }, + + /** + * Get engine name for pattern. + * @memberof PatternEngines + * @param pattern + * @returns engine name matching pattern + */ + getEngineNameForPattern: function(pattern) { + // avoid circular dependency by putting this in here. TODO: is this slow? + const of = require('./object_factory'); + if ( + pattern instanceof of.Pattern && + typeof pattern.fileExtension === 'string' && + pattern.fileExtension + ) { + //loop through known engines and find the one that supports the pattern's fileExtension + const engineNames = Object.keys(this); + for (let i = 0; i < engineNames.length; i++) { + const engine = this[engineNames[i]]; + + if (Array.isArray(engine.engineFileExtension)) { + if (engine.engineFileExtension.includes(pattern.fileExtension)) { + return engine.engineName; + } + } else { + //this likely means the users engines are out of date. todo: tell them to upgrade + if (engine.engineFileExtension === pattern.fileExtension) { + return engine.engineName; + } + } + } + } + + // otherwise, assume it's a plain mustache template string and act + // accordingly + return 'mustache'; + }, + + /** + * Get engine for pattern. + * @memberof PatternEngines + * @param pattern + * @returns name of engine for pattern + */ + getEngineForPattern: function(pattern) { + if (pattern.isPseudoPattern) { + return this.getEngineForPattern(pattern.basePattern); + } else { + const engineName = this.getEngineNameForPattern(pattern); + return this[engineName]; + } + }, + + /** + * Combine all found engines into a single array of supported extensions. + * @memberof PatternEngines + * @returns Array all supported file extensions + */ + getSupportedFileExtensions: function() { + const engineNames = Object.keys(PatternEngines); + const allEnginesExtensions = engineNames.map(engineName => { + return PatternEngines[engineName].engineFileExtension; + }); + return [].concat.apply([], allEnginesExtensions); + }, + + /** + * Check if fileExtension is supported. + * @memberof PatternEngines + * @param fileExtension + * @returns Boolean + */ + isFileExtensionSupported: function(fileExtension) { + const supportedExtensions = PatternEngines.getSupportedFileExtensions(); + return supportedExtensions.lastIndexOf(fileExtension) !== -1; + }, + + /** + * Given a filename, return a boolean: whether or not the filename indicates + * that the file is pseudopattern JSON + * @param filename + * @return boolean + */ + isPseudoPatternJSON: function(filename) { + const extension = path.extname(filename); + return extension === '.json' && filename.indexOf('~') > -1; + }, + + /** + * Takes a filename string, not a full path; a basename (plus extension) + * ignore _underscored patterns, dotfiles, and anything not recognized by a + * loaded pattern engine. Pseudo-pattern .json files ARE considered to be + * pattern files! + * + * @memberof PatternEngines + * @param filename + * @returns boolean + */ + isPatternFile: function(filename) { + // skip hidden patterns/files without a second thought + const extension = path.extname(filename); + if ( + filename.charAt(0) === '.' || + (extension === '.json' && !PatternEngines.isPseudoPatternJSON(filename)) + ) { + return false; + } + + // not a hidden pattern, let's dig deeper + const supportedPatternFileExtensions = PatternEngines.getSupportedFileExtensions(); + return ( + supportedPatternFileExtensions.lastIndexOf(extension) !== -1 || + PatternEngines.isPseudoPatternJSON(filename) + ); + }, +}); + +module.exports = PatternEngines; diff --git a/packages/core/src/lib/pattern_exporter.js b/packages/core/src/lib/pattern_exporter.js new file mode 100644 index 000000000..8eb4e1a88 --- /dev/null +++ b/packages/core/src/lib/pattern_exporter.js @@ -0,0 +1,75 @@ +'use strict'; + +const fs = require('fs-extra'); +const path = require('path'); + +function exportSinglePattern(patternlab, pattern) { + const preserveDirStructure = + patternlab.config.patternExportPreserveDirectoryStructure; + let patternName = pattern.patternPartial; + let patternDir = patternlab.config.patternExportDirectory; + let patternCode = pattern.patternPartialCode; + let patternFileExtension = '.html'; + if (preserveDirStructure) { + // Extract the first part of the pattern partial as the directory in which + // it should go. + patternDir = path.join(patternDir, pattern.patternPartial.split('-')[0]); + patternName = pattern.patternPartial + .split('-') + .slice(1) + .join('-'); + } + + if (patternlab.config.patternExportRaw) { + patternCode = pattern.extendedTemplate; + patternFileExtension = `.${patternlab.config.patternExtension}`; + } + + fs.outputFileSync( + path.join(patternDir, patternName) + patternFileExtension, + patternCode + ); +} + +const pattern_exporter = function() { + /** + * Exports all pattern's final HTML as defined in patternlab-config.json to desired location. + * Originally created to help facilitate easier consumption by jekyll. + * This method is off spec with PL PHP and will change or be augmented some day. + * + * @param patternlab {object} patternlab reference + */ + function exportPatterns(patternlab) { + //read the config export options + const exportPartials = patternlab.config.patternExportPatternPartials; + const exportAll = patternlab.config.patternExportAll; + + if (exportAll) { + for (let i = 0; i < patternlab.patterns.length; i++) { + if (!patternlab.patterns[i].patternPartial.startsWith('-')) { + exportSinglePattern(patternlab, patternlab.patterns[i]); + } + } + + return; + } + + //find the chosen patterns to export + for (let i = 0; i < exportPartials.length; i++) { + for (let j = 0; j < patternlab.patterns.length; j++) { + if (exportPartials[i] === patternlab.patterns[j].patternPartial) { + //write matches to the desired location + exportSinglePattern(patternlab, patternlab.patterns[j]); + } + } + } + } + + return { + export_patterns: function(patternlab) { + exportPatterns(patternlab); + }, + }; +}; + +module.exports = pattern_exporter; diff --git a/packages/core/src/lib/pattern_graph.js b/packages/core/src/lib/pattern_graph.js new file mode 100644 index 000000000..6ad17d012 --- /dev/null +++ b/packages/core/src/lib/pattern_graph.js @@ -0,0 +1,425 @@ +'use strict'; + +const graphlib = require('graphlib'); +const Graph = graphlib.Graph; +const path = require('path'); +const fs = require('fs-extra'); +const Pattern = require('./object_factory').Pattern; +const CompileState = require('./object_factory').CompileState; +const PatternGraphDot = require('./pattern_graph_dot'); +const PatternRegistry = require('./pattern_registry'); + +/** + * The most recent version of the pattern graph. This is used to rebuild the graph when + * the version of a serialized graph does not match the current version. + * @type {number} + */ +const PATTERN_GRAPH_VERSION = 1; + +/** + * Wrapper around a graph library to build a dependency graph of patterns. + * Each node in the graph will maintain a {@link CompileState}. This allows finding all + * changed patterns and their transitive dependencies. + * + * Internally the graph maintains a {@link PatternRegistry} to allow fast lookups of the patterns. + * + * @constructor Constructs a new PatternGraph from a JSON-style JavaScript object or an empty graph + * if no argument is given. + * + * @param {Graph} graph The graphlib graph object + * @param {int} timestamp The unix timestamp + * @param {int} version The graph version. + * + * @returns {{PatternGraph: PatternGraph}} + + * @see PatternGraph#fromJson + * @see #540 + */ +const PatternGraph = function(graph, timestamp, version) { + this.graph = + graph || + new Graph({ + directed: true, + }); + this.graph.setDefaultEdgeLabel({}); + + // Allows faster lookups for patterns by name for each element in the graph + // The idea here is to make a pattern known to the graph as soon as it exists + this.patterns = new PatternRegistry(); + this.timestamp = timestamp || new Date().getTime(); + this.version = version || PATTERN_GRAPH_VERSION; +}; + +// shorthand. Use relPath as it is always unique, even with subPatternType +const nodeName = p => (p instanceof Pattern ? p.relPath : p); + +PatternGraph.prototype = { + /** + * Synchronizes the graph nodes with the set of all known patterns. + * For instance when a pattern is deleted or moved, it might still have a node from the serialized + * JSON, but there is no source pattern. + * + * @see {@link https://github.com/pattern-lab/patternlab-node/issues/580|Issue #580} + */ + sync: function() { + // Remove any patterns that are in the graph data, but that haven't been discovered when + // walking all patterns iteratively + const nodesToRemove = this.nodes().filter(n => !this.patterns.has(n)); + nodesToRemove.forEach(n => this.remove(n)); + return nodesToRemove; + }, + + /** + * Creates an independent copy of the graph where nodes and edges can be modified without + * affecting the source. + */ + clone: function() { + const json = graphlib.json.write(this.graph); + const graph = graphlib.json.read(json); + return new PatternGraph(graph, this.timestamp, this.version); + }, + + /** + * Add a pattern to the graph and copy its {@link Pattern.compileState} to the node's data. + * If the pattern is already known, nothing is done. + * + * @param {Pattern} pattern + */ + add: function(pattern) { + const n = nodeName(pattern); + if (!this.patterns.has(n)) { + this.graph.setNode(n, { + compileState: pattern.compileState, + }); + + this.patterns.put(pattern); + } + }, + + remove: function(pattern) { + const n = nodeName(pattern); + this.graph.removeNode(n); + this.patterns.remove(n); + }, + + /** + * Removes nodes from this graph for which the given predicate function returns false. + * @param {function} fn which takes a node name as argument + */ + filter: function(fn) { + this.graph.nodes().forEach(n => { + if (!fn(n)) { + this.remove(n); + } + }); + }, + + /** + * Creates a directed edge in the graph which indicates pattern inclusion. + * Patterns must be {@link PatternGraph.add added} before using this method. + * + * @param {Pattern} patternFrom The pattern (subject) which includes the other pattern + * @param {Pattern} patternTo The pattern (object) that is included by the subject. + * + * @throws {Error} If the pattern is unknown + */ + link: function(patternFrom, patternTo) { + const nameFrom = nodeName(patternFrom); + const nameTo = nodeName(patternTo); + for (const name of [nameFrom, nameTo]) { + if (!this.patterns.has(name)) { + throw new Error('Pattern not known: ' + name); + } + } + this.graph.setEdge(nameFrom, nameTo); + }, + + /** + * Determines if there is one pattern is included by another. + * @param {Pattern} patternFrom + * @param {Pattern} patternTo + * + * @return {boolean} + */ + hasLink: function(patternFrom, patternTo) { + const nameFrom = nodeName(patternFrom); + const nameTo = nodeName(patternTo); + return this.graph.hasEdge(nameFrom, nameTo); + }, + + /** + * Determines the order in which all changed patterns and there transitive predecessors must + * be rebuild. + * + * This first finds all patterns that must be rebuilt, second marks any patterns that transitively + * include these patterns for rebuilding and finally applies topological sorting to the graph. + * + * @return {Array} An Array of {@link Pattern}s in the order by which the changed patters must be + * compiled. + */ + compileOrder: function() { + const compileStateFilter = function(patterns, n) { + const node = patterns.get(n); + return node.compileState !== CompileState.CLEAN; + }; + + /** + * This graph only contains those nodes that need recompilation + * Edges are added in reverse order for topological sorting(e.g. atom -> molecule -> organism, + * where "->" means "included by"). + */ + const compileGraph = new Graph({ + directed: true, + }); + + const nodes = this.graph.nodes(); + const changedNodes = nodes.filter(n => + compileStateFilter(this.patterns, n) + ); + this.nodes2patterns(changedNodes).forEach(pattern => { + const patternNode = nodeName(pattern); + if (!compileGraph.hasNode(patternNode)) { + compileGraph.setNode(patternNode); + } + this.applyReverse(pattern, (from, to) => { + from.compileState = CompileState.NEEDS_REBUILD; + const fromName = nodeName(from); + const toName = nodeName(to); + for (const name of [fromName, toName]) { + if (!compileGraph.hasNode(name)) { + compileGraph.setNode(name); + } + } + if (!compileGraph.hasNode(toName)) { + compileGraph.setNode(toName); + } + + // reverse! + compileGraph.setEdge({ v: toName, w: fromName }); + }); + }); + + // Apply topological sorting, Start at the leafs of the graphs (e.g. atoms) and go further + // up in the hierarchy + const o = graphlib.alg.topsort(compileGraph); + return this.nodes2patterns(o); + }, + + /** + * Given a node and its predecessor, allows exchanging states between nodes. + * @param pattern + * @param fn A function that takes the currently viewed pattern and node data. Allows synching data + * between patterns and node metadata. + */ + applyReverse: function(pattern, fn) { + for (const p of this.lineageR(pattern)) { + fn(p, pattern); + this.applyReverse(p, fn); + } + }, + + /** + * Find the node fro a pattern + * + * @param {Pattern} pattern + * + * @return [null|Pattern] + */ + node: function(pattern) { + return this.graph.node(nodeName(pattern)); + }, + + /** + * + * @param nodes {Array} + * @return {Array} An Array of Patterns + */ + nodes2patterns: function(nodes) { + return nodes.map(n => this.patterns.get(n)); + }, + + // TODO cache result in a Map[String, Array]? + // We trade the pattern.lineage array - O(pattern.lineage.length << |V|) - vs. O(|V|) of the graph. + // As long as no edges are added or removed, we can cache the result in a Map and just return it. + /** + * Finds all immediate successors of a pattern, i.e. all patterns which the given pattern includes. + * @param pattern + * @return {*|Array} + */ + lineage: function(pattern) { + const nodes = this.graph.successors(nodeName(pattern)); + return this.nodes2patterns(nodes); + }, + + /** + * Returns all patterns that include the given pattern + * @param {Pattern} pattern + * @return {*|Array} + */ + lineageR: function(pattern) { + const nodes = this.graph.predecessors(nodeName(pattern)); + return this.nodes2patterns(nodes); + }, + + /** + * Given a {Pattern}, return all partial names of {Pattern} objects included in this the given pattern + * @param {Pattern} pattern + * + * @see {@link PatternGraph.lineage(pattern)} + */ + lineageIndex: function(pattern) { + const lineage = this.lineage(pattern); + return lineage.map(p => p.patternPartial); + }, + + /** + * Given a {Pattern}, return all partial names of {Pattern} objects which include the given pattern + * @param {Pattern} pattern + * + * @return {Array} + * + * @see {@link PatternGraph.lineageRIndex(pattern)} + */ + lineageRIndex: function(pattern) { + const lineageR = this.lineageR(pattern); + return lineageR.map(p => p.patternPartial); + }, + + /** + * Creates an object representing the graph and meta data. + * @returns {{timestamp: number, graph}} + */ + toJson: function() { + return { + version: this.version, + timestamp: this.timestamp, + graph: graphlib.json.write(this.graph), + }; + }, + + /** + * @return {Array} An array of all node names. + */ + nodes: function() { + return this.graph.nodes(); + }, + + /** + * Updates the version to the most recent one + */ + upgradeVersion: function() { + this.version = PATTERN_GRAPH_VERSION; + }, +}; + +/** + * Creates an empty graph with a unix timestamp of 0 as last compilation date. + * @param {int} [version=PATTERN_GRAPH_VERSION] + * @return {PatternGraph} + */ +PatternGraph.empty = function(version) { + return new PatternGraph(null, 0, version || PATTERN_GRAPH_VERSION); +}; + +/** + * Checks if the version of + * @param {PatternGraph|Object} graphOrJson + * @return {boolean} + */ +PatternGraph.checkVersion = function(graphOrJson) { + return graphOrJson.version === PATTERN_GRAPH_VERSION; +}; + +/** + * Error that is thrown if the given version does not match the current graph version. + * + * @param oldVersion + * @constructor + */ +function VersionMismatch(oldVersion) { + this.message = `Version of graph on disk ${oldVersion} != current version ${PATTERN_GRAPH_VERSION}. Please clean your patterns output directory.`; + this.name = 'VersionMismatch'; +} + +/** + * Parse the graph from a JSON object. + * @param {object} o The JSON object to read from + * @return {PatternGraph} + */ +PatternGraph.fromJson = function(o) { + if (!PatternGraph.checkVersion(o)) { + throw new VersionMismatch(o.version); + } + const graph = graphlib.json.read(o.graph); + return new PatternGraph(graph, o.timestamp, o.version); +}; + +/** + * Resolve the path to the file containing the serialized graph + * @param {string} [filePath='process.cwd()'] Path to the graph file + * @param {string} [fileName='dependencyGraph.json'] Name of the graph file + * @return {string} + */ +PatternGraph.resolveJsonGraphFile = function( + filePath = process.cwd(), + fileName = 'dependencyGraph.json' +) { + return path.resolve(filePath, fileName); +}; + +/** + * Loads a graph from the file. Does not add any patterns from the patternlab object, + * i.e. graph.patterns will be still empty until all patterns have been processed. + * + * @param {string} [filePath] path to the graph json file + * @param {string} [fileName] optional name of the graph json file + * + * @see {@link PatternGraph.fromJson} + * @see {@link PatternGraph.resolveJsonGraphFile} + */ +PatternGraph.loadFromFile = function(filePath, fileName) { + const jsonGraphFile = this.resolveJsonGraphFile(filePath, fileName); + + // File is fresh, so simply construct an empty graph in memory + if (!fs.existsSync(jsonGraphFile)) { + return PatternGraph.empty(); + } + + const obj = fs.readJSONSync(jsonGraphFile); + if (!PatternGraph.checkVersion(obj)) { + return PatternGraph.empty(obj.version); + } + return this.fromJson(obj); +}; + +/** + * Serializes the graph to a file. + * @param patternlab + * @param {string} [file] For unit testing only. + * + * @see {@link PatternGraph.resolveJsonGraphFile} + */ +PatternGraph.storeToFile = function(patternlab) { + if (process.env.PATTERNLAB_ENV === 'CI') { + return; + } + const jsonGraphFile = this.resolveJsonGraphFile(); + patternlab.graph.timestamp = new Date().getTime(); + fs.writeJSONSync(jsonGraphFile, patternlab.graph.toJson()); +}; + +/** + * Exports this graph to a GraphViz file. + * @param patternlab + @ @param {string} fileName Output filename + */ +PatternGraph.exportToDot = function(patternlab, fileName) { + const dotFile = this.resolveJsonGraphFile(undefined, fileName); + const g = PatternGraphDot.generate(patternlab.graph); + fs.outputFileSync(dotFile, g); +}; + +module.exports = { + PatternGraph: PatternGraph, + PATTERN_GRAPH_VERSION: PATTERN_GRAPH_VERSION, +}; diff --git a/packages/core/src/lib/pattern_graph_dot.js b/packages/core/src/lib/pattern_graph_dot.js new file mode 100644 index 000000000..c2e865203 --- /dev/null +++ b/packages/core/src/lib/pattern_graph_dot.js @@ -0,0 +1,147 @@ +'use strict'; + +/** + * Overall settings + * @return {[string,string,string,string,string,string,string]} + */ +function header() { + return [ + 'strict digraph {', + 'graph [fontname = "helvetica" size=20]', + + /*compound=true;*/ + 'concentrate=true;', + 'rankdir=LR;', + 'ranksep="4 equally·";', + 'node [style=filled,color=white];', + 'edge [style=dotted constraint=false]', + ]; +} + +/** + * Graph nodes cannot start with numbers in GrahViz and must not contain dashes. + * @param name + * @return {string} + */ +const niceKey = function(name) { + return 'O' + name.replace('-', ''); +}; + +/** + * Adds the output for defining a node in GraphViz. + * + * @param {Pattern} pattern + * @return {string} + */ +function addNode(pattern) { + let more = ''; + if (pattern.isPseudoPattern) { + more = ' [fillcolor=grey]'; + } + return '"' + pattern.name + '"' + more + ';\n'; +} + +/** + * + * @param {Pattern} from + * @param {Pattern} to + * @param {string} color A valid color, e.g. HTMl or a color name + * @return {string} + */ +function addEdge(from, to, color) { + return `"${from.name}" -> "${to.name}" [color=${color}];\n`; +} + +/** + * Creates a sub-graph which is used to group atoms, molecules, etc. + * @param group + * @param patterns + * @return {[*,*,string,string,*,*,string]} + */ +function subGraph(group, patterns) { + const s = niceKey(group); + return [ + 'subgraph cluster_X' + s + ' {', + 'label=<' + group + '>;', + 'style=filled;', + 'color=lightgrey;', + s + ' [shape=box];', + patterns.map(addNode).join(''), + + //patterns.map(p => "\"" + p.name + "\"").join(" -> ") + "[style=invis]", + '}', + ]; +} + +function footer() { + return ['}']; +} + +const PatternGraphDot = {}; + +/** + * Create the GraphViz representation of the given graph + * @param patternGraph + * @return {string} + */ +PatternGraphDot.generate = function(patternGraph) { + const g = patternGraph.graph; + const patterns = patternGraph.patterns; + const buckets = new Map(); + const colors = [ + 'darkgreen', + 'firebrick', + 'slateblue', + 'darkgoldenrod', + 'black', + ]; + const colorMap = new Map(); + let colIdx = 0; + for (const p of patterns.partials.values()) { + if (p.isPseudoPattern || !p.patternType) { + continue; + } + let bucket = buckets.get(p.patternType); + if (bucket) { + bucket.push(p); + } else { + bucket = [p]; + colorMap.set(p.patternType, colors[colIdx++]); + + // Repeat if there are more categories + colIdx = colIdx % colors.length; + } + buckets.set(p.patternType, bucket); + } + + let res = header(); + const sortedKeys = Array.from(buckets.keys()).sort(); + + const niceKeys = sortedKeys.map(niceKey); + + let subGraphLines = []; + + for (const key of sortedKeys) { + const subPatterns = buckets.get(key); + subGraphLines = subGraphLines.concat(subGraph(key, subPatterns)); + } + res = res.concat(subGraphLines); + res.push('edge[style=solid];'); + + foo: for (const edge of g.edges()) { + const fromTo = patternGraph.nodes2patterns([edge.v, edge.w]); + for (const pattern of fromTo) { + if (pattern.isPseudoPattern || !pattern.patternType) { + continue foo; + } + } + const thisColor = colorMap.get(fromTo[0].patternType); + res.push(addEdge(fromTo[0], fromTo[1], thisColor)); + } + + res.push(niceKeys.reverse().join(' -> ') + '[constraint=true];'); + res = res.concat(footer()); + return res.join('\n') + '\n'; +}; + +module.exports = PatternGraphDot; diff --git a/packages/core/src/lib/pattern_registry.js b/packages/core/src/lib/pattern_registry.js new file mode 100644 index 000000000..95145234c --- /dev/null +++ b/packages/core/src/lib/pattern_registry.js @@ -0,0 +1,100 @@ +'use strict'; + +/** + * Allows lookups for patterns via a central registry. + * @constructor + */ +const PatternRegistry = function() { + this.key2pattern = new Map(); + + /** For lookups by {@link Pattern#partialKey} */ + this.partials = new Map(); +}; + +PatternRegistry.prototype = { + allPatterns: function() { + return Array.from(this.key2pattern.values()); + }, + + has: function(name) { + return this.key2pattern.has(name); + }, + + get: function(name) { + return this.key2pattern.get(name); + }, + + /** + * Adds the given pattern to the registry. If a pattern with the same key exists, it is replaced. + * @param pattern {Pattern|*} + */ + put: function(pattern) { + const name = PatternRegistry.partialName(pattern); + this.partials.set(name, pattern); + const key = PatternRegistry.patternKey(pattern); + this.key2pattern.set(key, pattern); + }, + + remove: function(name) { + this.key2pattern.delete(name); + }, + + getPartial: function(partialName) { + /* + Code in here has been moved from getPartial() to prepare for some refactoring. + There are a few advantages to this method: + - use a map lookup instead of interating through all patterns + - get rid of dependency to the patternlab object + - make code more readable + */ + + // This previously has been a for loop over an array in pattern_ + const byPartialName = this.partials.get(partialName); + if (this.partials.has(partialName)) { + return byPartialName; + } + + const patterns = this.allPatterns(); + + //else look by verbose syntax + for (const thisPattern of patterns) { + switch (partialName) { + case thisPattern.relPath: + case thisPattern.verbosePartial: + return thisPattern; + } + } + + //return the fuzzy match if all else fails + for (const thisPattern of patterns) { + const partialParts = partialName.split('-'), + partialType = partialParts[0], + partialNameEnd = partialParts.slice(1).join('-'); + + const patternPartial = thisPattern.patternPartial; + if ( + patternPartial.split('-')[0] === partialType && + patternPartial.indexOf(partialNameEnd) > -1 + ) { + return thisPattern; + } + } + return undefined; + }, +}; + +PatternRegistry.patternKey = function(pattern) { + return pattern.relPath; +}; + +/** + * Defines how the partial key of a pattern is resolved. + * + * @param pattern {Pattern} + * @return {string} + */ +PatternRegistry.partialName = function(pattern) { + return pattern.patternPartial; +}; + +module.exports = PatternRegistry; diff --git a/packages/core/src/lib/patternlab.js b/packages/core/src/lib/patternlab.js new file mode 100644 index 000000000..cd89c64c4 --- /dev/null +++ b/packages/core/src/lib/patternlab.js @@ -0,0 +1,367 @@ +'use strict'; + +const dive = require('dive'); +const _ = require('lodash'); +const path = require('path'); +const cleanHtml = require('js-beautify').html; + +const inherits = require('util').inherits; +const pm = require('./plugin_manager'); +const plugin_manager = new pm(); +const packageInfo = require('../../package.json'); +const events = require('./events'); +const buildListItems = require('./buildListItems'); +const dataLoader = require('./data_loader')(); +const loaduikits = require('./loaduikits'); +const logger = require('./log'); +const processIterative = require('./processIterative'); +const processRecursive = require('./processRecursive'); + +const loadPattern = require('./loadPattern'); +const sm = require('./starterkit_manager'); + +const patternEngines = require('./pattern_engines'); + +//these are mocked in unit tests, so let them be overridden +let fs = require('fs-extra'); // eslint-disable-line + +const EventEmitter = require('events').EventEmitter; + +function PatternLabEventEmitter() { + EventEmitter.call(this); +} +inherits(PatternLabEventEmitter, EventEmitter); + +module.exports = class PatternLab { + constructor(config) { + // Either use the config we were passed, or load one up from the config file ourselves + this.config = + config || + fs.readJSONSync(path.resolve(__dirname, '../../patternlab-config.json')); + + //register our log events + this.registerLogger(config.logLevel); + + logger.info(`Pattern Lab Node v${packageInfo.version}`); + + // Load up engines please + this.engines = patternEngines; + this.engines.loadAllEngines(config); + this.isBusy = false; + + // + // INITIALIZE EMPTY GLOBAL DATA STRUCTURES + // + this.data = {}; + this.patterns = []; + this.subtypePatterns = {}; + this.partials = {}; + + // Cache the package.json in RAM + this.package = fs.readJSONSync( + path.resolve(__dirname, '../../package.json') + ); + + // Make ye olde event emitter + this.events = new PatternLabEventEmitter(); + + this.hooks = {}; + this.hooks[events.PATTERNLAB_PATTERN_WRITE_END] = []; + + // Make a place for the pattern graph to sit + this.graph = null; + + // Make a place to attach known watchers so we can manage them better during serve and watch + this.watchers = {}; + + // make a place to register any uikits + this.uikits = {}; + loaduikits(this); + + // Verify correctness of configuration (?) + this.checkConfiguration(this); + + this.initializePlugins(this); + } + + checkConfiguration(patternlab) { + //default the output suffixes if not present + const outputFileSuffixes = { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }; + + if (!patternlab.config.outputFileSuffixes) { + logger.warning(''); + logger.warning( + 'Configuration key [outputFileSuffixes] not found, and defaulted to the following:' + ); + logger.info(outputFileSuffixes); + logger.warning( + 'Since Pattern Lab Node Core 2.3.0 this configuration option is required. Suggest you add it to your patternlab-config.json file.' + ); + logger.warning(''); + } + patternlab.config.outputFileSuffixes = _.extend( + outputFileSuffixes, + patternlab.config.outputFileSuffixes + ); + + if (typeof patternlab.config.paths.source.patternlabFiles === 'string') { + logger.warning(''); + logger.warning( + `Configuration key [paths.source.patternlabFiles] inside patternlab-config.json was found as the string '${patternlab.config.paths.source.patternlabFiles}'` + ); + logger.warning( + 'Since Pattern Lab Node Core 3.0.0 this key is an object. Suggest you update this key following this issue: https://github.com/pattern-lab/patternlab-node/issues/683.' + ); + logger.warning(''); + } + + if (typeof patternlab.config.debug === 'boolean') { + logger.warning(''); + logger.warning( + `Configuration key [debug] inside patternlab-config.json was found. As of Pattern Lab Node Core 3.0.0 this key is replaced with a new key, [logLevel]. This is a string with possible values ['debug', 'info', 'warning', 'error', 'quiet'].` + ); + logger.warning( + `Turning on 'info', 'warning', and 'error' levels by default, unless [logLevel] is present. If that is the case, [debug] has no effect.` + ); + logger.warning(''); + } + } + + /** + * Finds and calls the main method of any found plugins. + * @param patternlab - global data store + */ + initializePlugins(patternlab) { + if (!patternlab.config.plugins) { + return; + } + plugin_manager.intialize_plugins(patternlab); + } + + buildGlobalData(additionalData) { + const paths = this.config.paths; + + // + // COLLECT GLOBAL LIBRARY DATA + // + + // data.json + try { + this.data = this.buildPatternData(paths.source.data, fs); // eslint-disable-line no-use-before-define + this.data.link = {}; + } catch (ex) { + logger.error( + 'missing or malformed' + + paths.source.data + + 'data.json Pattern Lab may not work without this file.' + ); + this.data = {}; + } + + // listitems.json + try { + this.listitems = fs.readJSONSync( + path.resolve(paths.source.data, 'listitems.json') + ); + } catch (ex) { + logger.warning( + 'WARNING: missing or malformed ' + + paths.source.data + + 'listitems.json file. Pattern Lab may not work without this file.' + ); + this.listitems = {}; + } + + this.data = Object.assign({}, this.data, additionalData); + + this.setCacheBust(); + + buildListItems(this); + + this.events.emit(events.PATTERNLAB_BUILD_GLOBAL_DATA_END, this); + } + + setCacheBust() { + if (this.config.cacheBust) { + logger.debug('setting cacheBuster value for frontend assets.'); + this.cacheBuster = new Date().getTime(); + } else { + this.cacheBuster = 0; + } + } + + // Starter Kit loading methods + + listStarterkits() { + const starterkit_manager = new sm(this.config); + return starterkit_manager.list_starterkits(); + } + + loadStarterKit(starterkitName, clean) { + const starterkit_manager = new sm(this.config); + starterkit_manager.load_starterkit(starterkitName, clean); + } + + // info methods + getVersion() { + return this.package.version; + } + getSupportedTemplateExtensions() { + return this.engines.getSupportedFileExtensions(); + } + + writePatternFiles(headHTML, pattern, footerHTML, outputBasePath) { + const nullFormatter = str => str; + const defaultFormatter = codeString => + cleanHtml(codeString, { indent_size: 2 }); + const makePath = type => + path.join( + this.config.paths.public.patterns, + pattern.getPatternLink(this, type) + ); + const patternPage = headHTML + pattern.patternPartialCode + footerHTML; + const eng = pattern.engine; + + //beautify the output if configured to do so + const formatters = this.config.cleanOutputHtml + ? { + rendered: eng.renderedCodeFormatter || defaultFormatter, + rawTemplate: eng.rawTemplateCodeFormatter || defaultFormatter, + markupOnly: eng.markupOnlyCodeFormatter || defaultFormatter, + } + : { + rendered: nullFormatter, + rawTemplate: nullFormatter, + markupOnly: nullFormatter, + }; + + //prepare the path and contents of each output file + const outputFiles = [ + { + path: makePath('rendered'), + content: formatters.rendered(patternPage, pattern), + }, + { + path: makePath('rawTemplate'), + content: formatters.rawTemplate(pattern.template, pattern), + }, + { + path: makePath('markupOnly'), + content: formatters.markupOnly(pattern.patternPartialCode, pattern), + }, + ].concat( + eng.addOutputFiles ? eng.addOutputFiles(this.config.paths, this) : [] + ); + + //write the compiled template to the public patterns directory + outputFiles.forEach(outFile => + fs.outputFileSync( + path.join(process.cwd(), outputBasePath, outFile.path), + outFile.content + ) + ); + } + + /** + * Binds console logging to different levels + * + * @param {string} logLevel + * @memberof PatternLab + */ + registerLogger(logLevel) { + if (logLevel === undefined) { + logger.log.on('info', msg => console.info(msg)); + logger.log.on('warning', msg => console.info(msg)); + logger.log.on('error', msg => console.info(msg)); + } else { + if (logLevel === 'quiet') { + return; + } + switch (logLevel) { + case 'debug': + logger.log.on('debug', msg => console.info(msg)); + case 'info': + logger.log.on('info', msg => console.info(msg)); + case 'warning': + logger.log.on('warning', msg => console.info(msg)); + case 'error': + logger.log.on('error', msg => console.info(msg)); + } + } + } + + /** + * Given a path, load info from the folder to compile into a single config object. + * @param dataFilesPath + * @param fsDep + * @returns {{}} + */ + buildPatternData(dataFilesPath, fsDep) { + return dataLoader.loadDataFromFolder(dataFilesPath, 'listitems', fsDep); + } + + // dive once to perform iterative populating of patternlab object + processAllPatternsIterative(patterns_dir) { + const self = this; + const promiseAllPatternFiles = new Promise(function(resolve) { + dive( + patterns_dir, + (err, file) => { + //log any errors + if (err) { + logger.info('error in processAllPatternsIterative():', err); + return; + } + + // We now have the loading and process phases spearated; this + // loads all the patterns before beginning any analysis, so we + // can load them asynchronously and be sure we know about all + // of them before we start lineage hunting, for + // example. Incidentally, this should also allow people to do + // horrifying things like include a page in a atom. But + // please, if you're reading this: don't. + + // NOTE: sync for now + loadPattern(path.relative(patterns_dir, file), self); + }, + resolve + ); + }); + return promiseAllPatternFiles.then(() => { + return Promise.all( + this.patterns.map(pattern => { + return processIterative(pattern, self); + }) + ).then(() => { + // patterns sorted by name so the patterntype and patternsubtype is adhered to for menu building + this.patterns.sort((pattern1, pattern2) => + pattern1.name.localeCompare(pattern2.name) + ); + }); + }); + } + + processAllPatternsRecursive(patterns_dir) { + const self = this; + + const promiseAllPatternFiles = new Promise(function(resolve) { + dive( + patterns_dir, + (err, file) => { + //log any errors + if (err) { + logger.info(err); + return; + } + processRecursive(path.relative(patterns_dir, file), self); + }, + resolve + ); + }); + return promiseAllPatternFiles; + } +}; diff --git a/packages/core/src/lib/plugin_manager.js b/packages/core/src/lib/plugin_manager.js new file mode 100644 index 000000000..9816ff99d --- /dev/null +++ b/packages/core/src/lib/plugin_manager.js @@ -0,0 +1,76 @@ +'use strict'; + +const plugin_manager = function() { + const path = require('path'); + const logger = require('./log'); + + const pluginMatcher = /^plugin-(.*)$/; + + /** + * Loads a plugin + * + * @param modulePath {string} the path to the plugin + * @return {object} the loaded plugin + */ + function loadPlugin(modulePath) { + return require(modulePath); + } + + /** + * Given a path: return the plugin name if the path points to a valid plugin + * module directory, or false if it doesn't. + * @param filePath + * @returns Plugin name if exists or FALSE + */ + function isPlugin(filePath) { + const baseName = path.basename(filePath); + const pluginMatch = baseName.match(pluginMatcher); + + if (pluginMatch) { + return pluginMatch[1]; + } + return false; + } + + /** + * Looks for installed plugins, loads them, and invokes them + * @param {object} patternlab + */ + function initializePlugins(patternlab) { + const foundPlugins = Object.keys(patternlab.config.plugins || {}); + foundPlugins.forEach(plugin => { + logger.info(`Found plugin: ${plugin}`); + logger.info(`Attempting to load and initialize plugin.`); + const pluginModule = loadPlugin(plugin); + pluginModule(patternlab); + }); + } + + async function raiseEvent(patternlab, eventName, args) { + patternlab.events.emit(eventName, args); + await (async function() { + const hookHandlers = (patternlab.hooks[eventName] || []).map(h => + h(args) + ); + + await Promise.all(hookHandlers); + })(); + } + + return { + intialize_plugins: patternlab => { + initializePlugins(patternlab); + }, + load_plugin: modulePath => { + return loadPlugin(modulePath); + }, + is_plugin: filePath => { + return isPlugin(filePath); + }, + raiseEvent: async (patternlab, eventName, ...args) => { + await raiseEvent(patternlab, eventName, args); + }, + }; +}; + +module.exports = plugin_manager; diff --git a/packages/core/src/lib/processIterative.js b/packages/core/src/lib/processIterative.js new file mode 100644 index 000000000..1c65c85ca --- /dev/null +++ b/packages/core/src/lib/processIterative.js @@ -0,0 +1,24 @@ +'use strict'; + +const logger = require('./log'); +const pph = require('./pseudopattern_hunter'); + +// This is now solely for analysis; loading of the pattern file is +// above, in loadPatternIterative() +module.exports = function(pattern, patternlab) { + //look for a pseudo pattern by checking if there is a file + //containing same name, with ~ in it, ending in .json + return pph + .find_pseudopatterns(pattern, patternlab) + .then(() => { + //find any stylemodifiers that may be in the current pattern + pattern.stylePartials = pattern.findPartialsWithStyleModifiers(); + + //find any pattern parameters that may be in the current pattern + pattern.parameteredPartials = pattern.findPartialsWithPatternParameters(); + return Promise.resolve(pattern); + }) + .catch( + logger.reportError('There was an error in processPatternIterative():') + ); +}; diff --git a/packages/core/src/lib/processMetaPattern.js b/packages/core/src/lib/processMetaPattern.js new file mode 100644 index 000000000..f793a103a --- /dev/null +++ b/packages/core/src/lib/processMetaPattern.js @@ -0,0 +1,28 @@ +'use strict'; + +const path = require('path'); + +const Pattern = require('./object_factory').Pattern; +const decompose = require('./decompose'); +const logger = require('./log'); + +//this may be mocked in unit tests, so let it be overridden +let fs = require('fs-extra'); // eslint-disable-line + +module.exports = function(fileName, metaType, patternlab) { + const metaPath = path.resolve(patternlab.config.paths.source.meta, fileName); + const metaPattern = new Pattern(metaPath, null, patternlab); + metaPattern.template = fs.readFileSync(metaPath, 'utf8'); + metaPattern.isPattern = false; + metaPattern.isMetaPattern = true; + return decompose(metaPattern, patternlab, true) + .then(() => { + patternlab[metaType] = metaPattern; + }) + .catch(reason => { + logger.warning( + `Could not find the user-editable template ${fileName}, currently configured to be at ${patternlab.config.paths.source.meta}. Your configured path may be incorrect (check paths.source.meta in your config file), the file may have been deleted, or it may have been left in the wrong place during a migration or update.` + ); + logger.warning(reason); + }); +}; diff --git a/packages/core/src/lib/processRecursive.js b/packages/core/src/lib/processRecursive.js new file mode 100644 index 000000000..97c4ac560 --- /dev/null +++ b/packages/core/src/lib/processRecursive.js @@ -0,0 +1,26 @@ +'use strict'; + +const logger = require('./log'); +const decompose = require('./decompose'); +const getPartial = require('./get'); + +module.exports = function(file, patternlab) { + //find current pattern in patternlab object using file as a partial + const currentPattern = getPartial(file, patternlab, false); + + //return if processing an ignored file + if (typeof currentPattern === 'undefined') { + return Promise.resolve(); + } + + //we are processing a markdown only pattern + if (currentPattern.engine === null) { + return Promise.resolve(); + } + + //call our helper method to actually unravel the pattern with any partials + return decompose(currentPattern, patternlab).catch(reason => { + console.log(reason); + logger.error(reason); + }); +}; diff --git a/packages/core/src/lib/pseudopattern_hunter.js b/packages/core/src/lib/pseudopattern_hunter.js new file mode 100644 index 000000000..b95dd37f1 --- /dev/null +++ b/packages/core/src/lib/pseudopattern_hunter.js @@ -0,0 +1,125 @@ +'use strict'; + +const ch = require('./changes_hunter'); +const glob = require('glob'); +const fs = require('fs-extra'); +const _ = require('lodash'); +const lh = require('./lineage_hunter'); +const Pattern = require('./object_factory').Pattern; +const path = require('path'); +const addPattern = require('./addPattern'); +const logger = require('./log'); +const readDocumentation = require('./readDocumentation'); +const lineage_hunter = new lh(); +const changes_hunter = new ch(); +const yaml = require('js-yaml'); + +const pseudopattern_hunter = function() {}; + +pseudopattern_hunter.prototype.find_pseudopatterns = function( + currentPattern, + patternlab +) { + const paths = patternlab.config.paths; + + //look for a pseudo pattern by checking if there is a file containing same + //name, with ~ in it, ending in .json, .yml or .yaml + const needle = + currentPattern.subdir + + '/' + + currentPattern.fileName + + '~*.{json,yml,yaml}'; + const pseudoPatterns = glob.sync(needle, { + cwd: paths.source.patterns, + debug: false, + nodir: true, + }); + + if (pseudoPatterns.length > 0) { + for (let i = 0; i < pseudoPatterns.length; i++) { + logger.debug( + `Found pseudoPattern variant of ${currentPattern.patternPartial}` + ); + + //we want to do everything we normally would here, except instead read the pseudoPattern data + let variantFileFullPath; + let variantFileData; + try { + variantFileFullPath = path.resolve( + paths.source.patterns, + pseudoPatterns[i] + ); + variantFileData = yaml.safeLoad( + fs.readFileSync(variantFileFullPath, 'utf8') + ); + } catch (err) { + logger.warning( + `There was an error parsing pseudopattern JSON for ${currentPattern.relPath}` + ); + logger.warning(err); + } + + //extend any existing data with variant data + variantFileData = _.merge( + {}, + currentPattern.jsonFileData, + variantFileData + ); + + const variantName = pseudoPatterns[i] + .substring(pseudoPatterns[i].indexOf('~') + 1) + .split('.')[0]; + const variantExtension = pseudoPatterns[i] + .split('.') + .slice(-1) + .pop(); + const variantFilePath = path.join( + currentPattern.subdir, + currentPattern.fileName + '~' + variantName + '.' + variantExtension + ); + const lm = fs.statSync(variantFileFullPath); + const patternVariant = Pattern.create( + variantFilePath, + variantFileData, + { + //use the same template as the non-variant + template: currentPattern.template, + fileExtension: currentPattern.fileExtension, + extendedTemplate: currentPattern.extendedTemplate, + isPseudoPattern: true, + basePattern: currentPattern, + stylePartials: currentPattern.stylePartials, + parameteredPartials: currentPattern.parameteredPartials, + + // Only regular patterns are discovered during iterative walks + // Need to recompile on data change or template change + lastModified: Math.max(currentPattern.lastModified, lm.mtime), + + // use the same template engine as the non-variant + engine: currentPattern.engine, + }, + patternlab + ); + + changes_hunter.checkBuildState(patternVariant, patternlab); + patternlab.graph.add(patternVariant); + patternlab.graph.link(patternVariant, currentPattern); + + //process the companion markdown file if it exists + readDocumentation(patternVariant, patternlab); + + //find pattern lineage + lineage_hunter.find_lineage(patternVariant, patternlab); + + //add to patternlab object so we can look these up later. + addPattern(patternVariant, patternlab); + } + + // GTP: this is to emulate the behavior of the stale asynced + // version; when we have time, we can make all the FS calls in here + // async and see if it helps any, but it didn't when I tried it. + } + return Promise.resolve(); +}; + +module.exports = new pseudopattern_hunter(); diff --git a/packages/core/src/lib/readDocumentation.js b/packages/core/src/lib/readDocumentation.js new file mode 100644 index 000000000..2670bb307 --- /dev/null +++ b/packages/core/src/lib/readDocumentation.js @@ -0,0 +1,72 @@ +'use strict'; + +const path = require('path'); +const _ = require('lodash'); + +const ch = require('./changes_hunter'); +const logger = require('./log'); +const mp = require('./markdown_parser'); + +const changes_hunter = new ch(); +const markdown_parser = new mp(); + +let fs = require('fs-extra'); //eslint-disable-line prefer-const + +module.exports = function(pattern, patternlab) { + try { + const markdownFileName = path.resolve( + patternlab.config.paths.source.patterns, + pattern.subdir, + pattern.fileName + '.md' + ); + changes_hunter.checkLastModified(pattern, markdownFileName); + + const markdownFileContents = fs.readFileSync(markdownFileName, 'utf8'); + + const markdownObject = markdown_parser.parse(markdownFileContents); + if (!_.isEmpty(markdownObject)) { + //set keys and markdown itself + pattern.patternDescExists = true; + pattern.patternDesc = markdownObject.markdown; + + //Add all markdown to the pattern, including frontmatter + pattern.allMarkdown = markdownObject; + + //consider looping through all keys eventually. would need to blacklist some properties and whitelist others + if (markdownObject.state) { + pattern.patternState = markdownObject.state; + } + if (markdownObject.order) { + pattern.order = markdownObject.order; + } + if (markdownObject.hidden) { + pattern.hidden = markdownObject.hidden; + } + if (markdownObject.excludeFromStyleguide) { + pattern.excludeFromStyleguide = markdownObject.excludeFromStyleguide; + } + if (markdownObject.tags) { + pattern.tags = markdownObject.tags; + } + if (markdownObject.title) { + pattern.patternName = markdownObject.title; + } + if (markdownObject.links) { + pattern.links = markdownObject.links; + } + } else { + logger.warning(`error processing markdown for ${pattern.patternPartial}`); + } + logger.debug( + `found pattern-specific markdown for ${pattern.patternPartial}` + ); + } catch (err) { + // do nothing when file not found + if (err.code !== 'ENOENT') { + logger.warning( + `'there was an error setting pattern keys after markdown parsing of the companion file for pattern ${pattern.patternPartial}` + ); + logger.warning(err); + } + } +}; diff --git a/packages/core/src/lib/render.js b/packages/core/src/lib/render.js new file mode 100644 index 000000000..3ba1f2589 --- /dev/null +++ b/packages/core/src/lib/render.js @@ -0,0 +1,14 @@ +'use strict'; + +const logger = require('./log'); + +module.exports = function(pattern, data, partials) { + logger.debug( + `render: ${ + pattern.patternPartial !== '-.' + ? pattern.patternPartial + : 'ad hoc partial with template' + pattern.extendedTemplate + }` + ); + return pattern.render(data, partials); +}; diff --git a/packages/core/src/lib/replaceParameter.js b/packages/core/src/lib/replaceParameter.js new file mode 100644 index 000000000..7b0cd6734 --- /dev/null +++ b/packages/core/src/lib/replaceParameter.js @@ -0,0 +1,40 @@ +'use strict'; + +const logger = require('./log'); + +module.exports = function(template, prop, data) { + let t = template; + + const valueRE = new RegExp(`{{{?\\s*[${prop}]+\\s*}?}}`); + + if (typeof data === 'string') { + return t.replace(valueRE, data); + } + + if (typeof data === 'boolean') { + const startRE = new RegExp(`{{\\s*#[${prop}]+\\s*}}`); + const endRE = new RegExp(`{{\\s*/[${prop}]+\\s*}}`); + + const bIdx = t.search(startRE); + const eIdxStart = t.search(endRE); + + // try to determine if this is a {{#section}} + // if it is, this looks like a boolean value meant to be a mere {{value}} + if (bIdx === -1) { + return t.replace(`{{${prop}}}`, data); + } + + if (data) { + t = t.replace(startRE, ''); + t = t.replace(endRE, ''); + } else { + // data is falsey + const eIdxEnd = t.indexOf('}}', eIdxStart) + 2; + t = t.substring(0, bIdx) + t.substring(eIdxEnd, t.length); + } + return t; + } + + logger.warning(`Could not replace ${prop} with ${data} inside ${template}`); + return t; +}; diff --git a/packages/core/src/lib/server.js b/packages/core/src/lib/server.js new file mode 100644 index 000000000..c55e2c4ab --- /dev/null +++ b/packages/core/src/lib/server.js @@ -0,0 +1,140 @@ +'use strict'; + +const path = require('path'); +const liveServer = require('@pattern-lab/live-server'); + +const events = require('./events'); +const logger = require('./log'); + +const server = patternlab => { + const _module = { + serve: () => { + let serverReady = false; + + // our default liveserver config + const defaults = { + open: true, + file: 'index.html', + logLevel: 0, // errors only + wait: 1000, + port: 3000, + }; + + const servers = Object.keys(patternlab.uikits).map(kit => { + const uikit = patternlab.uikits[kit]; + defaults.root = path.resolve( + path.join( + process.cwd(), + uikit.outputDir, + patternlab.config.paths.public.root + ) + ); + defaults.ignore = path.resolve( + path.join( + process.cwd(), + uikit.outputDir, + patternlab.config.paths.public.root + ) + ); + defaults.assets = [ + path.resolve( + path.join( + process.cwd(), + patternlab.config.paths.source.js, + '**', + '*.js' // prevent preprocessors like typescript from reloading + ) + ), + path.resolve( + path.join(process.cwd(), patternlab.config.paths.source.images) + ), + path.resolve( + path.join(process.cwd(), patternlab.config.paths.source.fonts) + ), + path.resolve( + path.join( + process.cwd(), + patternlab.config.paths.source.css, + '**', + '*.css' // prevent preprocessors from reloading + ) + ), + ]; + + // allow for overrides should they exist inside patternlab-config.json + const liveServerConfig = Object.assign( + {}, + defaults, + patternlab.config.serverOptions + ); + + const setupEventWatchers = () => { + //watch for builds to complete + patternlab.events.on(events.PATTERNLAB_BUILD_END, () => { + if (serverReady) { + _module.reload({ + file: '', + action: 'reload', + }); + } + }); + }; + + //start! + //There is a new server instance for each uikit + const serveKit = new Promise((resolve, reject) => { + let resolveMsg = ''; + setTimeout(() => { + try { + liveServer.start(liveServerConfig); + resolveMsg = `Pattern Lab is being served from http://127.0.0.1:${liveServerConfig.port}`; + logger.info(resolveMsg); + } catch (e) { + const err = `Pattern Lab serve failed to start: ${e}`; + logger.error(`Pattern Lab serve failed to start: ${e}`); + reject(err); + } + setupEventWatchers(); + serverReady = true; + resolve(resolveMsg); + }, liveServerConfig.wait); + }); + return serveKit; + }); + + return Promise.all(servers); + }, + reload: data => { + const _data = data || { + file: '', + action: '', + }; + return new Promise((resolve, reject) => { + let action; + try { + if (!patternlab.isBusy) { + if (_data.file.indexOf('css') > -1 || _data.action === 'refresh') { + action = 'refreshed CSS'; + liveServer.refreshCSS(); + } else { + action = 'reloaded'; + liveServer.reload(); + } + resolve(`Server ${action} successfully`); + } + } catch (e) { + reject(`Server reload or refresh failed: ${e}`); + } + }); + }, + refreshCSS: () => { + return _module.reload({ + file: '', + action: 'refresh', + }); + }, + }; + return _module; +}; + +module.exports = server; diff --git a/packages/core/src/lib/starterkit_manager.js b/packages/core/src/lib/starterkit_manager.js new file mode 100644 index 000000000..d11e5f2a4 --- /dev/null +++ b/packages/core/src/lib/starterkit_manager.js @@ -0,0 +1,140 @@ +'use strict'; + +const starterkit_manager = function(config) { + const path = require('path'); + const fetch = require('node-fetch'); + const fs = require('fs-extra'); + const logger = require('./log'); + const paths = config.paths; + + /** + * Loads npm module identified by the starterkitName parameter. + * + * @param starterkitName {string} Kit name + * @param clean {boolean} Indicates if the directory should be cleaned before loading + */ + function loadStarterKit(starterkitName, clean) { + try { + const kitPath = path.resolve( + path.join( + process.cwd(), + 'node_modules', + starterkitName, + config.starterkitSubDir + ) + ); + logger.debug('Attempting to load starterkit from', kitPath); + let kitDirStats; + try { + kitDirStats = fs.statSync(kitPath); + } catch (ex) { + logger.warning( + `${starterkitName} not found, use npm to install it first.` + ); + logger.warning(`${starterkitName} not loaded.`); + return; + } + const kitPathDirExists = kitDirStats.isDirectory(); + if (kitPathDirExists) { + if (clean) { + logger.info( + `Deleting contents of ${paths.source.root} prior to starterkit load.` + ); + fs.emptyDirSync(paths.source.root); + } else { + logger.info( + `Overwriting contents of ${paths.source.root} during starterkit load.` + ); + } + + try { + fs.copySync(kitPath, paths.source.root); + } catch (ex) { + logger.error(ex); + return; + } + logger.info('Starterkit ' + starterkitName + ' loaded into source/.'); + } + } catch (ex) { + logger.warning( + `An error occurred during starterkit installation for starterkit ${starterkitName}` + ); + logger.warning(ex); + } + } + + /** + * Fetches starterkit repos from GH API that contain 'starterkit' in their name for the user 'pattern-lab' + * + * @return {Promise} Returns an Array<{name,url}> for the starterkit repos + */ + function listStarterkits() { + return fetch( + 'https://api.github.com/search/repositories?q=starterkit+in:name+user:pattern-lab&sort=stars&order=desc', + { + method: 'GET', + headers: { + Accept: 'application/json', + }, + } + ) + .then(function(res) { + const contentType = res.headers.get('content-type'); + if (contentType && contentType.indexOf('application/json') === -1) { + throw new TypeError( + 'StarterkitManager->listStarterkits: Not valid JSON' + ); + } + return res.json(); + }) + .then(function(json) { + if (!json.items || !Array.isArray(json.items)) { + return false; + } + return json.items.map(function(repo) { + return { name: repo.name, url: repo.html_url }; + }); + }) + .catch(function(err) { + logger.error(err); + return false; + }); + } + + function packStarterkit() {} + + /** + * Detects installed starter kits + * + * @return {array} List of starter kits installed + */ + //TODO review for deletion or convert callers to use findModules() + function detectStarterKits() { + const node_modules_path = path.join(process.cwd(), 'node_modules'); + const npm_modules = fs.readdirSync(node_modules_path).filter(function(dir) { + const module_path = path.join(process.cwd(), 'node_modules', dir); + return ( + fs.statSync(module_path).isDirectory() && + dir.indexOf('starterkit-') === 0 + ); + }); + return npm_modules; + } + + return { + load_starterkit: function(starterkitName, clean) { + loadStarterKit(starterkitName, clean); + }, + list_starterkits: function() { + return listStarterkits(); + }, + pack_starterkit: function() { + packStarterkit(); + }, + detect_starterkits: function() { + return detectStarterKits(); + }, + }; +}; + +module.exports = starterkit_manager; diff --git a/core/lib/style_modifier_hunter.js b/packages/core/src/lib/style_modifier_hunter.js similarity index 50% rename from core/lib/style_modifier_hunter.js rename to packages/core/src/lib/style_modifier_hunter.js index 8c0b7413f..13a7ff2d1 100644 --- a/core/lib/style_modifier_hunter.js +++ b/packages/core/src/lib/style_modifier_hunter.js @@ -1,21 +1,34 @@ -"use strict"; - -var style_modifier_hunter = function () { - +'use strict'; + +const logger = require('./log'); + +const style_modifier_hunter = function() { + /** + * Modifies a patterns partial with any styleModifiers found on the supplied partial + * + * @param pattern {object} the pattern to extend + * @param partial {string} partial containing styleModifiers + * @param patternlab {object} the patternlab instance + */ function consumestylemodifier(pattern, partial, patternlab) { //extract the classname from the stylemodifier which comes in the format of :className - var styleModifier = partial.match(/:([\w\-_|])+/g) ? partial.match(/:([\w\-_|])+/g)[0].slice(1) : null; + let styleModifier = partial.match(/:([\w\-_|])+/g) + ? partial.match(/:([\w\-_|])+/g)[0].slice(1) + : null; if (styleModifier) { //replace the special character pipe | used to separate multiple classes with a space styleModifier = styleModifier.replace(/\|/g, ' '); - if (patternlab.config.debug) { - console.log('found partial styleModifier within pattern ' + pattern.patternPartial); - } + logger.debug( + `Found partial styleModifier within pattern ${pattern.patternPartial}` + ); //replace the stylemodifier placeholder with the class name - pattern.extendedTemplate = pattern.extendedTemplate.replace(/{{[ ]?styleModifier[ ]?}}/i, styleModifier); + pattern.extendedTemplate = pattern.extendedTemplate.replace( + /{{[ ]?styleModifier[ ]?}}/i, + styleModifier + ); //update the extendedTemplate in the partials object in case this pattern is consumed later patternlab.partials[pattern.patternPartial] = pattern.extendedTemplate; @@ -23,11 +36,10 @@ var style_modifier_hunter = function () { } return { - consume_style_modifier: function (pattern, partial, patternlab) { + consume_style_modifier: function(pattern, partial, patternlab) { consumestylemodifier(pattern, partial, patternlab); - } + }, }; - }; module.exports = style_modifier_hunter; diff --git a/packages/core/src/lib/ui_builder.js b/packages/core/src/lib/ui_builder.js new file mode 100644 index 000000000..5dcb36986 --- /dev/null +++ b/packages/core/src/lib/ui_builder.js @@ -0,0 +1,916 @@ +'use strict'; + +const path = require('path'); +const _ = require('lodash'); + +const of = require('./object_factory'); +const Pattern = of.Pattern; +const logger = require('./log'); +const uikitExcludePattern = require('./uikitExcludePattern'); + +//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 +let exportData = require('./exportData'); //eslint-disable-line prefer-const + +const ui_builder = function() { + /** + * Registers the pattern to the patternPaths object for the appropriate patternGroup and basename + * patternGroup + patternBaseName are what comprise the patternPartial (atoms-colors) + * @param patternlab - global data store + * @param pattern - the pattern to add + */ + function addToPatternPaths(patternlab, pattern) { + if (!patternlab.patternPaths[pattern.patternGroup]) { + patternlab.patternPaths[pattern.patternGroup] = {}; + } + + //only add real patterns + if (pattern.isPattern && !pattern.isDocPattern) { + patternlab.patternPaths[pattern.patternGroup][pattern.patternBaseName] = + pattern.name; + } + } + + /** + * Registers the pattern with the viewAllPaths object for the appropriate patternGroup and patternSubGroup + * @param patternlab - global data store + * @param pattern - the pattern to add + */ + function addToViewAllPaths(patternlab, pattern) { + if (!patternlab.viewAllPaths[pattern.patternGroup]) { + patternlab.viewAllPaths[pattern.patternGroup] = {}; + } + + if ( + !patternlab.viewAllPaths[pattern.patternGroup][pattern.patternSubGroup] + ) { + patternlab.viewAllPaths[pattern.patternGroup][ + 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; + + //add all if it does not exist yet + if (!patternlab.viewAllPaths[pattern.patternGroup].all) { + patternlab.viewAllPaths[pattern.patternGroup].all = pattern.patternType; + } + } + + /** + * Returns whether or not the pattern should be excluded from direct rendering or navigation on the front end + * @param pattern - the pattern to test for inclusion/exclusion + * @param patternlab - global data store + * @param uikit - the current uikit being built + * @returns boolean - whether or not the pattern is excluded + */ + function isPatternExcluded(pattern, patternlab, uikit) { + let isOmitted; + + // skip patterns that the uikit does not want to render + isOmitted = uikitExcludePattern(pattern, uikit); + if (isOmitted) { + logger.info( + `Omitting ${pattern.patternPartial} from styleguide patterns because its pattern state or tag is excluded within ${uikit.name}.` + ); + return true; + } + + // skip underscore-prefixed files + isOmitted = pattern.isPattern && pattern.fileName.charAt(0) === '_'; + if (isOmitted) { + logger.info( + `Omitting ${pattern.patternPartial} from styleguide patterns because it has an underscore prefix.` + ); + return true; + } + + //this is meant to be a homepage that is not present anywhere else + isOmitted = pattern.patternPartial === patternlab.config.defaultPattern; + if (isOmitted) { + logger.info( + `Omitting ${pattern.patternPartial} from styleguide patterns because it is defined as a defaultPattern.` + ); + patternlab.defaultPattern = pattern; + return true; + } + + //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; + if (isOmitted) { + logger.info( + `Omitting ${pattern.patternPartial} from styleguide patterns because its contained within an underscored directory.` + ); + return true; + } + + //this pattern is a head or foot pattern + isOmitted = pattern.isMetaPattern; + if (isOmitted) { + logger.info( + `Omitting ${pattern.patternPartial} from styleguide patterns because its a meta pattern.` + ); + return true; + } + + //yay, let's include this on the front end + return isOmitted; + } + + /** + * For the given pattern, find or construct the view-all pattern block for the group + * @param pattern - the pattern to derive our documentation pattern from + * @param patternlab - global data store + * @param isSubtypePattern - whether or not this is a subtypePattern or a typePattern (typePatterns not supported yet) + * @returns the found or created pattern object + */ + function injectDocumentationBlock(pattern, patternlab, isSubtypePattern) { + //first see if loadPattern processed one already + let docPattern = + patternlab.subtypePatterns[ + pattern.patternGroup + + (isSubtypePattern ? '-' + pattern.patternSubGroup : '') + ]; + if (docPattern) { + docPattern.isDocPattern = true; + docPattern.order = -Number.MAX_SAFE_INTEGER; + return docPattern; + } + + //if not, create one now + docPattern = new Pattern.createEmpty( + { + name: pattern.flatPatternPath, + patternName: isSubtypePattern + ? pattern.patternSubGroup + : pattern.patternGroup, + patternDesc: '', + patternPartial: + 'viewall-' + + pattern.patternGroup + + (isSubtypePattern ? '-' + pattern.patternSubGroup : ''), + patternSectionSubtype: isSubtypePattern, + patternLink: pattern.flatPatternPath + path.sep + 'index.html', + isPattern: false, + engine: null, + flatPatternPath: pattern.flatPatternPath, + isDocPattern: true, + order: -Number.MAX_SAFE_INTEGER, + }, + patternlab + ); + return docPattern; + } + + /** + * Registers flat patterns with the patternTypes object + * This is a new menu group like atoms + * @param patternlab - global data store + * @param pattern - the pattern to register + */ + function addPatternType(patternlab, pattern) { + patternlab.patternTypes.push({ + patternTypeLC: pattern.patternGroup.toLowerCase(), + patternTypeUC: + pattern.patternGroup.charAt(0).toUpperCase() + + pattern.patternGroup.slice(1), + patternType: pattern.patternType, + patternTypeDash: pattern.patternGroup, //todo verify + patternTypeItems: [], + }); + } + + /** + * Return the patternType object for the given pattern. Exits application if not found. + * @param patternlab - global data store + * @param pattern - the pattern to derive the pattern Type from + * @returns the found pattern type object + */ + function getPatternType(patternlab, pattern) { + const patternType = _.find(patternlab.patternTypes, [ + 'patternType', + pattern.patternType, + ]); + + if (!patternType) { + logger.error( + `Could not find patternType ${pattern.patternType}. This is a critical error.` + ); + } + + return patternType; + } + + /** + * Return the patternSubType object for the given pattern. Exits application if not found. + * @param patternlab - global data store + * @param pattern - the pattern to derive the pattern subType from + * @returns the found patternSubType object + */ + function getPatternSubType(patternlab, pattern) { + const patternType = getPatternType(patternlab, pattern); + const patternSubType = _.find(patternType.patternTypeItems, [ + 'patternSubtype', + pattern.patternSubType, + ]); + + if (!patternSubType) { + logger.error( + `Could not find patternType ${pattern.patternType}-${pattern.patternType}. This is a critical error.` + ); + } + + return patternSubType; + } + + /** + * Registers the pattern with the appropriate patternType.patternTypeItems object + * This is a new menu group like atoms/global + * @param patternlab - global data store + * @param pattern - the pattern to register + */ + function addPatternSubType(patternlab, pattern) { + const newSubType = { + patternSubtypeLC: pattern.patternSubGroup.toLowerCase(), + patternSubtypeUC: + pattern.patternSubGroup.charAt(0).toUpperCase() + + pattern.patternSubGroup.slice(1), + patternSubtype: pattern.patternSubType, + patternSubtypeDash: pattern.patternSubGroup, //todo verify + patternSubtypeItems: [], + }; + const patternType = getPatternType(patternlab, pattern); + const insertIndex = _.sortedIndexBy( + patternType.patternTypeItems, + newSubType, + 'patternSubtype' + ); + patternType.patternTypeItems.splice(insertIndex, 0, newSubType); + } + + /** + * 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}} + */ + 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, + }; + } + + /** + * Registers the pattern with the appropriate patternType.patternSubType.patternSubtypeItems array + * These are the actual menu items you click on + * @param patternlab - global data store + * @param pattern - the pattern to derive the subtypeitem from + * @param createViewAllVariant - whether or not to create the special view all item + */ + function addPatternSubTypeItem( + patternlab, + pattern, + createSubtypeViewAllVarient + ) { + let newSubTypeItem; + + if (createSubtypeViewAllVarient) { + newSubTypeItem = { + patternPartial: + 'viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup, + patternName: 'View All', + patternPath: encodeURI(pattern.flatPatternPath + '/index.html'), + patternType: pattern.patternType, + patternSubtype: pattern.patternSubtype, + order: 0, + }; + } else { + newSubTypeItem = createPatternSubTypeItem(pattern); + } + + const patternSubType = getPatternSubType(patternlab, pattern); + patternSubType.patternSubtypeItems.push(newSubTypeItem); + patternSubType.patternSubtypeItems = _.sortBy( + patternSubType.patternSubtypeItems, + ['order', 'name'] + ); + } + + /** + * Registers flat patterns to the appropriate type + * @param patternlab - global data store + * @param pattern - the pattern to add + */ + function addPatternItem(patternlab, pattern, isViewAllVariant) { + const patternType = getPatternType(patternlab, pattern); + if (!patternType) { + logger.error( + `Could not find patternType ${pattern.patternType}. This is a critical error.` + ); + } + + if (!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, + }); + } + } else { + patternType.patternItems.push(createPatternSubTypeItem(pattern)); + } + patternType.patternItems = _.sortBy(patternType.patternItems, [ + 'order', + 'name', + ]); + } + + // 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 + * @returns sorted patterns + */ + function sortPatterns(patternsArray) { + return patternsArray.sort(function(a, b) { + let aOrder = parseInt(a.order, 10); + const bOrder = parseInt(b.order, 10); + + if (aOrder === NaN) { + aOrder = Number.MAX_SAFE_INTEGER; + } + + if (bOrder === NaN) { + aOrder = Number.MAX_SAFE_INTEGER; + } + + //alwasy return a docPattern first + if (a.isDocPattern && !b.isDocPattern) { + return -1; + } + + if (!a.isDocPattern && b.isDocPattern) { + 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 + if ( + aOrder === Number.MAX_SAFE_INTEGER && + bOrder === Number.MAX_SAFE_INTEGER + ) { + if (a.name > b.name) { + return 1; + } + if (a.name < b.name) { + return -1; + } + } + + //if we get this far, we can sort safely + if (aOrder && bOrder) { + if (aOrder > bOrder) { + return 1; + } + if (aOrder < bOrder) { + return -1; + } + } + return 0; + }); + } + + /** + * 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 + */ + function groupPatterns(patternlab, uikit) { + const groupedPatterns = { + patternGroups: {}, + }; + + _.forEach(patternlab.patterns, function(pattern) { + //ignore patterns we can omit from rendering directly + pattern.omitFromStyleguide = isPatternExcluded( + pattern, + patternlab, + uikit + ); + if (pattern.omitFromStyleguide) { + return; + } + + if (!groupedPatterns.patternGroups[pattern.patternGroup]) { + 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); + } + + //continue building navigation for nested patterns + if (pattern.patternGroup !== pattern.patternSubGroup) { + if ( + !groupedPatterns.patternGroups[pattern.patternGroup][ + pattern.patternSubGroup + ] + ) { + addPatternSubType(patternlab, pattern); + + pattern.isSubtypePattern = !pattern.isPattern; + groupedPatterns.patternGroups[pattern.patternGroup][ + pattern.patternSubGroup + ] = {}; + groupedPatterns.patternGroups[pattern.patternGroup][ + pattern.patternSubGroup + ][ + 'viewall-' + pattern.patternGroup + '-' + pattern.patternSubGroup + ] = injectDocumentationBlock(pattern, patternlab, true); + + addToViewAllPaths(patternlab, pattern); + addPatternSubTypeItem(patternlab, pattern, true); + } + + groupedPatterns.patternGroups[pattern.patternGroup][ + pattern.patternSubGroup + ][pattern.patternBaseName] = pattern; + + addToPatternPaths(patternlab, pattern); + addPatternSubTypeItem(patternlab, pattern); + } else { + addPatternItem(patternlab, pattern); + addToPatternPaths(patternlab, pattern); + } + }); + + return groupedPatterns; + } + + /** + * Takes a set of patterns and builds a viewall HTML page for them + * Used by the type and subtype viewall sets + * @param patternlab - global data store + * @param patterns - the set of patterns to build the viewall page for + * @param patternPartial - a key used to identify the viewall page + * @returns A promise which resolves with the HTML + */ + function buildViewAllHTML(patternlab, patterns, patternPartial, uikit) { + return render( + Pattern.createEmpty({ extendedTemplate: uikit.viewAll }), + { + //data + partials: patterns, + patternPartial: 'viewall-' + patternPartial, + cacheBuster: patternlab.cacheBuster, + }, + { + //templates + patternSection: uikit.patternSection, + patternSectionSubtype: uikit.patternSectionSubType, + } + ).catch(reason => { + console.log(reason); + logger.error('Error building buildViewAllHTML'); + }); + } + + /** + * Constructs viewall pages for each set of grouped patterns + * @param mainPageHeadHtml - the already built main page HTML + * @param patternlab - global data store + * @param styleguidePatterns - the grouped set of patterns + * @returns every built pattern and set of viewall patterns, so the styleguide can use it + */ + function buildViewAllPages( + mainPageHeadHtml, + patternlab, + styleguidePatterns, + uikit + ) { + const paths = patternlab.config.paths; + let patterns = []; + let writeViewAllFile = true; + + //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 subTypePromises = _.map( + _.values(patternGroup), + (patternSubtypes, patternSubtype, originalPatternGroup) => { + let p; + const samplePattern = _.find(patternSubtypes, st => { + return !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 + 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 + return buildFooter(patternlab, `viewall-${patternPartial}`, uikit) + .then(footerHTML => { + //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 + p = _.find(subtypePatterns, function(pat) { + return pat.isDocPattern; + }); + + //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; + }); + if (omitPatternType) { + logger.debug( + `Omitting ${patternType}/${patternName} from building a viewall page because its patternSubGroup is specified in styleguideExcludes.` + ); + } else { + styleguideTypePatterns = styleguideTypePatterns.concat( + subtypePatterns + ); + } + + typePatterns = typePatterns.concat(subtypePatterns); + + //render the viewall template for the subtype + return buildViewAllHTML( + patternlab, + subtypePatterns, + patternPartial, + uikit + ) + .then(viewAllHTML => { + return fs.outputFile( + path.join( + process.cwd(), + uikit.outputDir, + paths.public.patterns + + p.flatPatternPath + + '/index.html' + ), + mainPageHeadHtml + viewAllHTML + footerHTML + ); + }) + .catch(reason => { + console.log(reason); + logger.error('Error building ViewAllHTML'); + }); + }) + .then(() => { + //do not create a viewall page for flat patterns + if (!writeViewAllFile || !p) { + logger.debug( + `skipping ${patternType} as flat patterns do not have view all pages` + ); + return Promise.resolve(); + } + + //render the footer needed for the viewall template + return buildFooter( + patternlab, + 'viewall-' + patternType + '-all', + 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) { + logger.debug( + `skipping ${patternType} as flat patterns do not have view all pages` + ); + return Promise.resolve(); + } + + //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'); + }); + }) + .catch(reason => { + console.log(reason); + logger.error('Error building footerHTML'); + }); + }) + .catch(reason => { + console.log(reason); + logger.error('Error building footer HTML'); + }); + } + ); + + return Promise.all(subTypePromises).catch(reason => { + console.log(reason); + logger.error('Error during buildViewAllPages'); + }); + } + ); + + return Promise.all(allPatternTypePromises).catch(reason => { + console.log(reason); + logger.error('Error during buildViewAllPages'); + }); + } + + /** + * Reset any global data we use between builds to guard against double adding things + */ + function resetUIBuilderState(patternlab) { + patternlab.patternPaths = {}; + patternlab.viewAllPaths = {}; + patternlab.patternTypes = []; + } + + /** + * The main entry point for ui_builder + * @param patternlab - global data store + * @returns {Promise} a promise fulfilled when build is complete + */ + function buildFrontend(patternlab) { + resetUIBuilderState(patternlab); + + const paths = patternlab.config.paths; + + const uikitPromises = _.map(patternlab.uikits, uikit => { + //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 + const headerPromise = render( + Pattern.createEmpty({ extendedTemplate: uikit.header }), + { + cacheBuster: patternlab.cacheBuster, + } + ) + .then(headerPartial => { + const headFootData = patternlab.data; + headFootData.patternLabHead = headerPartial; + headFootData.cacheBuster = patternlab.cacheBuster; + return render(patternlab.userHead, headFootData); + }) + .catch(reason => { + console.log(reason); + 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 + const footerPromise = render( + Pattern.createEmpty({ extendedTemplate: uikit.footer }), + { + patternData: '{}', + cacheBuster: patternlab.cacheBuster, + } + ) + .then(footerPartial => { + const headFootData = patternlab.data; + headFootData.patternLabFoot = footerPartial; + return render(patternlab.userFoot, headFootData); + }) + .catch(reason => { + console.log(reason); + logger.error('error during footer render()'); + }); + + return Promise.all([headerPromise, footerPromise]).then( + headFootPromiseResults => { + //build the viewall pages + + return buildViewAllPages( + headFootPromiseResults[0], + patternlab, + styleguidePatterns, + 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; + }) + ); + + //add the defaultPattern if we found one + if (patternlab.defaultPattern) { + uniquePatterns.push(patternlab.defaultPattern); + addToPatternPaths(patternlab, patternlab.defaultPattern); + } + + //build the main styleguide page + return render( + Pattern.createEmpty({ + extendedTemplate: uikit.viewAll, + }), + { + partials: uniquePatterns, + }, + { + patternSection: uikit.patternSection, + patternSectionSubtype: uikit.patternSectionSubType, + } + ) + .then(styleguideHtml => { + fs.outputFileSync( + path.resolve( + path.join( + process.cwd(), + uikit.outputDir, + paths.public.styleguide, + 'html/styleguide.html' + ) + ), + headFootPromiseResults[0] + + styleguideHtml + + headFootPromiseResults[1] + ); + + logger.info('Built Pattern Lab front end'); + + //move the index file from its asset location into public root + let patternlabSiteHtml; + try { + patternlabSiteHtml = fs.readFileSync( + path.resolve( + path.join( + uikit.modulePath, + paths.source.styleguide, + 'index.html' + ) + ), + 'utf8' + ); + } catch (err) { + logger.error( + `Could not load one or more styleguidekit assets from ${paths.source.styleguide}` + ); + } + fs.outputFileSync( + path.resolve( + path.join( + process.cwd(), + uikit.outputDir, + paths.public.root, + 'index.html' + ) + ), + patternlabSiteHtml + ); + + //write out patternlab.data object to be read by the client + exportData(patternlab); + resolve(); + }) + .catch(reason => { + console.log(reason); + logger.error('error during buildFrontend()'); + }); + }) + .catch(reason => { + console.log(reason); + logger.error('error during buildViewAllPages()'); + }); + } + ); + }); + }); + return Promise.all(uikitPromises); + } + + 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 + ); + }, + }; +}; + +module.exports = ui_builder; diff --git a/packages/core/src/lib/uikitExcludePattern.js b/packages/core/src/lib/uikitExcludePattern.js new file mode 100644 index 000000000..61cddcb57 --- /dev/null +++ b/packages/core/src/lib/uikitExcludePattern.js @@ -0,0 +1,7 @@ +'use strict'; + +const uikitExcludePattern = (pattern, uikit) => { + const state = pattern.patternState; + return uikit.excludedPatternStates.includes(state); +}; +module.exports = uikitExcludePattern; diff --git a/packages/core/src/lib/watchAssets.js b/packages/core/src/lib/watchAssets.js new file mode 100644 index 000000000..f087b8a28 --- /dev/null +++ b/packages/core/src/lib/watchAssets.js @@ -0,0 +1,70 @@ +'use strict'; + +const path = require('path'); +const _ = require('lodash'); +const chokidar = require('chokidar'); + +const logger = require('./log'); + +let copyFile = require('./copyFile'); // eslint-disable-line prefer-const + +function onWatchTripped(patternlab, p, assetBase, basePath, dir, copyOptions) { + const subPath = p.replace(assetBase, ''); + _.each(patternlab.uikits, uikit => { + const destination = path.resolve( + basePath, + uikit.outputDir, + dir.public + '/' + subPath + ); + copyFile(p, destination, copyOptions); + }); +} + +const watchAssets = ( + patternlab, + basePath, + dir, + key, + copyOptions, + watchOnce +) => { + const assetBase = path.resolve(basePath, dir.source); + const assetsToIgnore = patternlab.config.transformedAssetTypes + ? patternlab.config.transformedAssetTypes.join('|') + : ''; + logger.debug(`Pattern Lab is watching ${assetBase} for changes`); + + if (patternlab.watchers[key]) { + patternlab.watchers[key].close(); + } + const assetWatcher = chokidar.watch(assetBase, { + // *ignored* combines file types that the wrapper is watching, passed to pl config + // regex string escapes backslashes for JS + // second part of regex is holdover from existing ignore regex for '/index.html' and other + // files meant to be ignored, not based on file type + ignored: new RegExp( + `(?:(?:.*\\.(?:${assetsToIgnore})$)|(?:(^|[\\/\\\\])\\..))`, + 'i' + ), + // ignored: /(^|[\/\\])\../, //old version + ignoreInitial: false, + awaitWriteFinish: { + stabilityThreshold: 200, + pollInterval: 100, + }, + persistent: !watchOnce, + }); + + //watch for changes and copy + assetWatcher + .on('add', p => { + onWatchTripped(patternlab, p, assetBase, basePath, dir, copyOptions); + }) + .on('change', p => { + onWatchTripped(patternlab, p, assetBase, basePath, dir, copyOptions); + }); + + patternlab.watchers[key] = assetWatcher; +}; + +module.exports = watchAssets; diff --git a/packages/core/src/lib/watchPatternLabFiles.js b/packages/core/src/lib/watchPatternLabFiles.js new file mode 100644 index 000000000..f5ab0c1aa --- /dev/null +++ b/packages/core/src/lib/watchPatternLabFiles.js @@ -0,0 +1,145 @@ +'use strict'; +const _ = require('lodash'); +const path = require('path'); + +const logger = require('./log'); +const events = require('./events'); +const pm = require('./plugin_manager'); +const pluginMananger = new pm(); + +let chokidar = require('chokidar'); // eslint-disable-line prefer-const + +const watchPatternLabFiles = ( + patternlab, + assetDirectories, + basePath, + watchOnce +) => { + // watch global structures, such as _data/* and _meta/ + const globalSources = [ + assetDirectories.source.data, + assetDirectories.source.meta, + ]; + const globalPaths = globalSources.map(globalSource => + path.join(path.resolve(basePath, globalSource), '*') + ); + + _.each(globalPaths, globalPath => { + logger.debug(`Pattern Lab is watching ${globalPath} for changes!`); + + if (patternlab.watchers[globalPath]) { + patternlab.watchers[globalPath].close(); + } + + const globalWatcher = chokidar.watch(path.resolve(globalPath), { + ignored: /(^|[\/\\])\../, + ignoreInitial: true, + awaitWriteFinish: { + stabilityThreshold: 200, + pollInterval: 100, + }, + persistent: !watchOnce, + }); + + //watch for changes and rebuild + globalWatcher + .on('addDir', async p => { + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_GLOBAL_CHANGE, + { + file: p, + } + ); + }) + .on('add', async p => { + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_GLOBAL_CHANGE, + { + file: p, + } + ); + }) + .on('change', async p => { + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_GLOBAL_CHANGE, + { + file: p, + } + ); + }); + + patternlab.watchers[globalPath] = globalWatcher; + }); + + // watch patterns + const baseFileExtensions = ['.json', '.yml', '.yaml', '.md']; + const patternWatches = baseFileExtensions + .concat(patternlab.engines.getSupportedFileExtensions()) + .map(dotExtension => + path.join( + path.resolve(basePath, assetDirectories.source.patterns), + `/**/*${dotExtension}` + ) + ); + _.each(patternWatches, patternWatchPath => { + logger.debug( + `Pattern Lab is watching ${patternWatchPath} for changes - local!` + ); + + if (patternlab.watchers[patternWatchPath]) { + patternlab.watchers[patternWatchPath].close(); + } + + const patternWatcher = chokidar.watch(path.resolve(patternWatchPath), { + ignored: /(^|[\/\\])\../, + ignoreInitial: true, + awaitWriteFinish: { + stabilityThreshold: 200, + pollInterval: 100, + }, + persistent: !watchOnce, + }); + + //watch for changes and rebuild + patternWatcher + .on('addDir', async p => { + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_PATTERN_CHANGE, + { + file: p, + } + ); + }) + .on('add', async p => { + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_PATTERN_CHANGE, + { + file: p, + } + ); + }) + .on('change', async p => { + await pluginMananger.raiseEvent( + patternlab, + events.PATTERNLAB_PATTERN_CHANGE, + { + file: p, + } + ); + }); + + patternlab.watchers[patternWatchPath] = patternWatcher; + }); + + logger.info( + `Pattern Lab is watching for changes to files under ${assetDirectories.source.root}` + ); + return Promise.resolve(); +}; + +module.exports = watchPatternLabFiles; diff --git a/packages/core/test/addPattern_tests.js b/packages/core/test/addPattern_tests.js new file mode 100644 index 000000000..0957e43e0 --- /dev/null +++ b/packages/core/test/addPattern_tests.js @@ -0,0 +1,51 @@ +'use strict'; + +const tap = require('tap'); + +const addPattern = require('../src/lib/addPattern'); +var Pattern = require('../src/lib/object_factory').Pattern; +const util = require('./util/test_utils.js'); + +const patterns_dir = './test/files/_patterns'; + +tap.test( + 'addPattern - adds pattern extended template to patternlab partial object', + function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + var pattern = new Pattern('00-test/01-bar.mustache'); + pattern.extendedTemplate = 'barExtended'; + pattern.template = 'bar'; + + //act + addPattern(pattern, patternlab); + + //assert + test.equals(patternlab.patterns.length, 1); + test.equals(patternlab.partials['test-bar'] !== undefined, true); + test.equals(patternlab.partials['test-bar'], 'barExtended'); + test.end(); + } +); + +tap.test( + 'addPattern - adds pattern template to patternlab partial object if extendedtemplate does not exist yet', + function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + var pattern = new Pattern('00-test/01-bar.mustache'); + pattern.extendedTemplate = undefined; + pattern.template = 'bar'; + + //act + addPattern(pattern, patternlab); + + //assert + test.equals(patternlab.patterns.length, 1); + test.equals(patternlab.partials['test-bar'] !== undefined, true); + test.equals(patternlab.partials['test-bar'], 'bar'); + test.end(); + } +); diff --git a/packages/core/test/annotation_exporter_tests.js b/packages/core/test/annotation_exporter_tests.js new file mode 100644 index 000000000..68c7b2f48 --- /dev/null +++ b/packages/core/test/annotation_exporter_tests.js @@ -0,0 +1,85 @@ +'use strict'; + +var tap = require('tap'); + +var extend = require('util')._extend; +var anPath = `${__dirname}/files/`; + +function createFakePatternLab(anPath, customProps) { + var pl = { + config: { + paths: { + source: { + annotations: anPath, + }, + }, + }, + }; + + return extend(pl, customProps); +} + +var patternlab = createFakePatternLab(anPath); +var ae = require('../src/lib/annotation_exporter')(patternlab); + +tap.test('converts old JS annotations into new format', function(test) { + //arrange + //act + var annotations = ae.gatherJS(); + + //assert + test.equals(annotations.length, 2); + test.equals(annotations[1].el, '.logo'); + test.equals(annotations[1].title, 'Logo'); + test.equals( + annotations[1].comment, + 'The logo image is an SVG file, which ensures that the logo displays crisply even on high resolution displays. A PNG fallback is provided for browsers that don\'t support SVG images.

Further reading: Optimizing Web Experiences for High Resolution Screens

' + ); + + test.end(); +}); + +tap.test('converts new markdown annotations into an array', function(test) { + //arrange + //act + var annotations = ae.gatherMD(); + + //assert + test.equals(annotations.length, 3); + test.equals(annotations[1].el, '.logo'); + test.equals(annotations[1].title, 'Logo'); + test.equals( + annotations[1].comment.replace(/\r?\n|\r/gm, ''), + '

The logo image is an SVG file.

' + ); + + test.end(); +}); + +tap.test('merges both annotation methods into one array', function(test) { + //arrange + + //act + var annotations = ae.gather(); + + //assert + test.equals(annotations.length, 3); + test.equals(annotations[2].el, '#nav'); + test.equals(annotations[2].title, 'Navigation'); + test.equals( + annotations[2].comment.replace(/\r?\n|\r/gm, ''), + '

Navigation for adaptive web experiences can be tricky. Refer to these repsonsive patterns when evaluating solutions.

' + ); + + test.end(); +}); + +tap.test('when there are 0 annotation files', function(test) { + var emptyAnPath = './test/files/empty/'; + var patternlab2 = createFakePatternLab(emptyAnPath); + var ae2 = require('../src/lib/annotation_exporter')(patternlab2); + + var annotations = ae2.gather(); + test.equals(annotations.length, 0); + test.end(); +}); diff --git a/packages/core/test/buildListItems_tests.js b/packages/core/test/buildListItems_tests.js new file mode 100644 index 000000000..757c8f1aa --- /dev/null +++ b/packages/core/test/buildListItems_tests.js @@ -0,0 +1,85 @@ +'use strict'; + +const tap = require('tap'); +const rewire = require('rewire'); + +const listItems = require('./files/_data/listitems.json'); +const buildlistItems = rewire('../src/lib/buildListItems'); + +const _Mock = { + shuffle: function(list) { + return list; + }, +}; + +//set our mocks in place of usual require() +buildlistItems.__set__({ + _: _Mock, +}); + +tap.test( + 'buildlistItems transforms container of listItems with one value', + test => { + // do this to avoid the shuffling for now + const container = Object.assign({}, { listitems: { '1': listItems['1'] } }); + buildlistItems(container); + test.same(container.listitems, { + 'listItems-one': [ + { + title: 'tA', + description: 'dA', + message: 'mA', + }, + ], + }); + test.end(); + } +); + +tap.test( + 'buildlistItems transforms container of listItems with three values', + test => { + // do this to avoid the shuffling for now + const container = { listitems: listItems }; + buildlistItems(container); + test.same(container.listitems, { + 'listItems-one': [ + { + title: 'tA', + description: 'dA', + message: 'mA', + }, + ], + 'listItems-two': [ + { + title: 'tA', + description: 'dA', + message: 'mA', + }, + { + title: 'tB', + description: 'dB', + message: 'mB', + }, + ], + 'listItems-three': [ + { + title: 'tA', + description: 'dA', + message: 'mA', + }, + { + title: 'tB', + description: 'dB', + message: 'mB', + }, + { + title: 'tC', + description: 'dC', + message: 'mC', + }, + ], + }); + test.end(); + } +); diff --git a/packages/core/test/changes_hunter_tests.js b/packages/core/test/changes_hunter_tests.js new file mode 100644 index 000000000..68b6c054d --- /dev/null +++ b/packages/core/test/changes_hunter_tests.js @@ -0,0 +1,67 @@ +'use strict'; + +const tap = require('tap'); +const rewire = require('rewire'); + +const ch = rewire('../src/lib/changes_hunter'); + +const fsMock = { + statSync: function() { + return { + mtime: { + getTime: () => { + return 100; + }, + }, + }; + }, + pathExistsSync: () => { + return true; + }, +}; + +//set our mocks in place of usual require() +ch.__set__({ + fs: fsMock, +}); + +const changes_hunter = new ch(); + +tap.test('checkLastModified - sets lastModified to fileTime ', function(test) { + //arrange + const mockPattern = { lastModified: 0 }; + //act + changes_hunter.checkLastModified(mockPattern, {}); + + //assert + test.equals(mockPattern.lastModified, 100); + test.end(); +}); + +tap.test( + 'checkLastModified - does not alter pattern if file not found', + function(test) { + //arrange + const mockPattern = { lastModified: 1010 }; + //act + changes_hunter.checkLastModified(mockPattern, null); + + //assert + test.equals(mockPattern.lastModified, 1010); + test.end(); + } +); + +tap.test( + 'checkLastModified - uses pattern.lastModified if greater than file time', + function(test) { + //arrange + const mockPattern = { lastModified: 101 }; + //act + changes_hunter.checkLastModified(mockPattern, {}); + + //assert + test.equals(mockPattern.lastModified, 101); + test.end(); + } +); diff --git a/packages/core/test/copier_tests.js b/packages/core/test/copier_tests.js new file mode 100644 index 000000000..388300a01 --- /dev/null +++ b/packages/core/test/copier_tests.js @@ -0,0 +1,69 @@ +'use strict'; + +var tap = require('tap'); +var rewire = require('rewire'); +var _ = require('lodash'); +var eol = require('os').EOL; +var Pattern = require('../src/lib/object_factory').Pattern; +var extend = require('util')._extend; +var c = rewire('../src/lib/copier'); +var path = require('path'); +var config = require('./util/patternlab-config.json'); + +var engineLoader = require('../src/lib/pattern_engines'); +engineLoader.loadAllEngines(config); + +//set up a global mocks - we don't want to be writing/rendering any files right now +// var chokidarMock = { +// watch: function (path, data, cb) { } +// }; + +// c.__set__({ +// 'chokidar': chokidarMock, +// }); + +const copier = c(); + +function createFakePatternLab(customProps) { + var pl = { + config: { + paths: { + source: { + img: './test/img', + css: './test/css', + }, + public: { + img: './test/output/img', + css: './test/output/css', + }, + }, + styleGuideExcludes: [], + logLevel: 'quiet', + outputFileSuffixes: { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }, + }, + data: {}, + }; + return extend(pl, customProps); +} + +tap.test( + 'transformConfigPaths takes configuration.paths() and maps to a better key store', + function(test) { + //arrange + var patternlab = createFakePatternLab({}); + + //act + var result = copier.transformConfigPaths(patternlab.config.paths); + + //assert + test.equals(result.img.source, './test/img'); + test.equals(result.img.public, './test/output/img'); + test.equals(result.css.source, './test/css'); + test.equals(result.css.public, './test/output/css'); + test.end(); + } +); diff --git a/packages/core/test/data_loader_tests.js b/packages/core/test/data_loader_tests.js new file mode 100644 index 000000000..9eaa8864b --- /dev/null +++ b/packages/core/test/data_loader_tests.js @@ -0,0 +1,13 @@ +'use strict'; + +const tap = require('tap'); + +tap.test('loadDataFromFile - Load ', function(test) { + const fs = require('fs-extra'), + dataLoader = require('../src/lib/data_loader')(), + data_dir = `${__dirname}/files/_data/`; + + let data = dataLoader.loadDataFromFile(data_dir + 'foo', fs); + test.equals(data.foo, 'bar'); + test.end(); +}); diff --git a/packages/core/test/engine_handlebars_tests.js b/packages/core/test/engine_handlebars_tests.js new file mode 100644 index 000000000..d268c1c63 --- /dev/null +++ b/packages/core/test/engine_handlebars_tests.js @@ -0,0 +1,405 @@ +'use strict'; +/*eslint-disable no-shadow*/ + +const tap = require('tap'); +const path = require('path'); +const eol = require('os').EOL; + +const util = require('./util/test_utils.js'); +const loadPattern = require('../src/lib/loadPattern'); +const Pattern = require('../src/lib/object_factory').Pattern; +const PatternGraph = require('../src/lib/pattern_graph').PatternGraph; +const processIterative = require('../src/lib/processIterative'); +const processRecursive = require('../src/lib/processRecursive'); + +const testPatternsPath = path.resolve( + __dirname, + 'files', + '_handlebars-test-patterns' +); + +const config = require('./util/patternlab-config.json'); +const engineLoader = require('../src/lib/pattern_engines'); +engineLoader.loadAllEngines(config); + +// don't run these tests unless handlebars is installed +if (!engineLoader.handlebars) { + tap.test('Handlebars engine not installed, skipping tests.', function(test) { + test.end(); + }); + return; +} + +// fake pattern lab constructor: +// sets up a fake patternlab object, which is needed by the pattern processing +// apparatus. +function fakePatternLab() { + var fpl = { + graph: PatternGraph.empty(), + partials: {}, + patterns: [], + footer: '', + header: '', + listitems: {}, + data: { + link: {}, + }, + config: require('../patternlab-config.json'), + package: {}, + }; + + // patch the pattern source so the pattern assembler can correctly determine + // the "subdir" + fpl.config.paths.source.patterns = testPatternsPath; + + return fpl; +} + +// function for testing sets of partials +function testFindPartials(test, partialTests) { + test.plan(partialTests.length + 1); + + // setup current pattern from what we would have during execution + // docs on partial syntax are here: + // http://patternlab.io/docs/pattern-including.html + var currentPattern = Pattern.create( + '01-molecules/00-testing/00-test-mol.hbs', // relative path now + null, // data + { + template: partialTests.join(), + } + ); + + // act + var results = currentPattern.findPartials(); + + // assert + test.equals(results.length, partialTests.length); + partialTests.forEach(function(testString, index) { + test.equals(results[index], testString); + }); + + test.end(); +} + +tap.test('hello world handlebars pattern renders', function(test) { + test.plan(1); + + var patternPath = path.join('00-atoms', '00-global', '00-helloworld.hbs'); + + // do all the normal processing of the pattern + var patternlab = new fakePatternLab(); + var helloWorldPattern = loadPattern(patternPath, patternlab); + + processIterative(helloWorldPattern, patternlab).then(helloWorldPattern => { + processRecursive(patternPath, patternlab).then(() => { + helloWorldPattern.render().then(results => { + test.equals(results, 'Hello world!' + eol); + test.end(); + }); + }); + }); +}); + +tap.test( + 'hello worlds handlebars pattern can see the atoms-helloworld partial and renders it twice', + function(test) { + test.plan(1); + + // pattern paths + var pattern1Path = path.join('00-atoms', '00-global', '00-helloworld.hbs'); + var pattern2Path = path.join( + '00-molecules', + '00-global', + '00-helloworlds.hbs' + ); + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal loading and processing of the pattern + const pattern1 = loadPattern(pattern1Path, patternlab); + const pattern2 = loadPattern(pattern2Path, patternlab); + + Promise.all([ + processIterative(pattern1, patternlab), + processIterative(pattern2, patternlab), + ]).then(() => { + processRecursive(pattern1Path, patternlab).then(() => { + processRecursive(pattern2Path, patternlab).then(() => { + // test + pattern2.render().then(results => { + test.equals( + results, + 'Hello world!' + eol + ' and Hello world!' + eol + eol + ); + test.end(); + }); + }); + }); + }); + } +); + +tap.test('handlebars partials can render JSON values', function(test) { + test.plan(1); + + // pattern paths + var pattern1Path = path.join( + '00-atoms', + '00-global', + '00-helloworld-withdata.hbs' + ); + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal processing of the pattern + var helloWorldWithData = loadPattern(pattern1Path, patternlab); + + processIterative(helloWorldWithData, patternlab).then(() => { + processRecursive(pattern1Path, patternlab).then(() => { + // test + helloWorldWithData.render().then(results => { + test.equals( + results, + 'Hello world!' + + eol + + 'Yeah, we got the subtitle from the JSON.' + + eol + ); + test.end(); + }); + }); + }); +}); + +tap.test( + 'handlebars partials use the JSON environment from the calling pattern and can accept passed parameters', + function(test) { + test.plan(1); + + // pattern paths + var atomPath = path.join( + '00-atoms', + '00-global', + '00-helloworld-withdata.hbs' + ); + var molPath = path.join( + '00-molecules', + '00-global', + '00-call-atom-with-molecule-data.hbs' + ); + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal processing of the pattern + const atom = loadPattern(atomPath, patternlab); + const mol = loadPattern(molPath, patternlab); + + Promise.all([ + processIterative(atom, patternlab), + processIterative(mol, patternlab), + processRecursive(atomPath, patternlab), + processRecursive(molPath, patternlab), + ]).then(() => { + mol.render().then(results => { + // test + test.equals( + results, + '

Call with default JSON environment:

' + + eol + + 'This is Hello world!' + + eol + + 'from the default JSON.' + + eol + + eol + + eol + + '

Call with passed parameter:

' + + eol + + 'However, this is Hello world!' + + eol + + 'from a totally different blob.' + + eol + + eol + ); + }); + }); + } +); + +tap.only('find_pattern_partials finds partials', function(test) { + testFindPartials(test, [ + '{{> molecules-comment-header}}', + '{{> molecules-comment-header}}', + '{{> ' + eol + ' molecules-comment-header' + eol + '}}', + '{{> molecules-weird-spacing }}', + '{{> molecules-ba_d-cha*rs }}', + ]); +}); + +tap.test('find_pattern_partials finds verbose partials', function(test) { + testFindPartials(test, [ + '{{> 01-molecules/06-components/03-comment-header.hbs }}', + "{{> 01-molecules/06-components/02-single-comment.hbs(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }}", + '{{> molecules-single-comment:foo }}', + "{{>atoms-error(message: 'That's no moon...')}}", + "{{> atoms-error(message: 'That's no moon...') }}", + '{{> 00-atoms/00-global/06-test }}', + ]); +}); + +tap.test( + 'find_pattern_partials finds simple partials with parameters', + function(test) { + testFindPartials(test, [ + "{{> molecules-single-comment(description: 'A life isn't like a garden. Perfect moments can be had, but not preserved, except in memory.') }}", + '{{> molecules-single-comment(description:"A life is like a "garden". Perfect moments can be had, but not preserved, except in memory.") }}', + ]); + } +); + +tap.test( + 'find_pattern_partials finds simple partials with style modifiers', + function(test) { + testFindPartials(test, ['{{> molecules-single-comment:foo }}']); + } +); + +tap.test( + 'find_pattern_partials finds partials with handlebars parameters', + function(test) { + testFindPartials(test, [ + '{{> atoms-title title="bravo" headingLevel="2" headingSize="bravo" position="left"}}', + '{{> atoms-title title="bravo"' + + eol + + ' headingLevel="2"' + + eol + + ' headingSize="bravo"' + + eol + + ' position="left"}}', + '{{> atoms-title title="color  midnight blue" headingSize="charlie"}}', + '{{> atoms-input label="city" required=true}}', + '{{> organisms-product-filter filterData}}', + '{{> atoms-input email required=true}}', + '{{> molecules-storycard variants.flex }}', + '{{> myPartial name=../name }}', + ]); + } +); + +tap.test('find_pattern_partials finds handlebars block partials', function( + test +) { + testFindPartials(test, ['{{#> myPartial }}']); +}); + +tap.only( + 'hidden handlebars patterns can be called by their nice names', + function(test) { + //arrange + const testPatternsPath = path.resolve( + __dirname, + 'files', + '_handlebars-test-patterns' + ); + const pl = util.fakePatternLab(testPatternsPath); + + var hiddenPatternPath = path.join( + '00-atoms', + '00-global', + '_00-hidden.hbs' + ); + var testPatternPath = path.join( + '00-molecules', + '00-global', + '00-hidden-pattern-tester.hbs' + ); + + var hiddenPattern = loadPattern(hiddenPatternPath, pl); + var testPattern = loadPattern(testPatternPath, pl); + + Promise.all([ + processIterative(hiddenPattern, pl), + processIterative(testPattern, pl), + processRecursive(hiddenPatternPath, pl), + processRecursive(testPatternPath, pl), + ]).then(() => { + testPattern.render().then(results => { + //act + test.equals( + util.sanitized(results), + util.sanitized("Here's the hidden atom: [I'm the hidden atom\n]\n") + ); + test.end(); + }); + }); + } +); + +tap.test( + '@partial-block template should render without throwing (@geoffp repo issue #3)', + function(test) { + test.plan(1); + + var patternPath = path.join( + '00-atoms', + '00-global', + '10-at-partial-block.hbs' + ); + + // do all the normal processing of the pattern + var patternlab = new fakePatternLab(); + var atPartialBlockPattern = loadPattern(patternPath, patternlab); + + processIterative(atPartialBlockPattern, patternlab).then(() => { + processRecursive(patternPath, patternlab).then(() => { + atPartialBlockPattern.render().then(results => { + var expectedResults = + '{{> @partial-block }}' + eol + 'It worked!' + eol; + test.equal(results, expectedResults); + }); + }); + }); + } +); + +tap.test( + 'A template calling a @partial-block template should render correctly', + function(test) { + test.plan(1); + + // pattern paths + var pattern1Path = path.join( + '00-atoms', + '00-global', + '10-at-partial-block.hbs' + ); + var pattern2Path = path.join( + '00-molecules', + '00-global', + '10-call-at-partial-block.hbs' + ); + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal processing of the pattern + const pattern1 = loadPattern(pattern1Path, patternlab); + const callAtPartialBlockPattern = loadPattern(pattern2Path, patternlab); + + Promise.all([ + processIterative(pattern1, patternlab), + processIterative(callAtPartialBlockPattern, patternlab), + processRecursive(pattern1Path, patternlab), + processRecursive(pattern2Path, patternlab), + ]).then(() => { + callAtPartialBlockPattern.render().then(results => { + // test + var expectedResults = 'Hello World!' + eol + 'It worked!' + eol; + test.equals(results, expectedResults); + }); + }); + } +); diff --git a/packages/core/test/engine_liquid_tests.js b/packages/core/test/engine_liquid_tests.js new file mode 100644 index 000000000..9036dbca1 --- /dev/null +++ b/packages/core/test/engine_liquid_tests.js @@ -0,0 +1,261 @@ +'use strict'; +/*eslint-disable dot-notation*/ +/*eslint-disable no-shadow*/ + +var tap = require('tap'); +var path = require('path'); +var loadPattern = require('../src/lib/loadPattern'); +var Pattern = require('../src/lib/object_factory').Pattern; +var PatternGraph = require('../src/lib/pattern_graph').PatternGraph; +var eol = require('os').EOL; + +// don't run these tests unless liquid is installed +var engineLoader = require('../src/lib/pattern_engines'); +if (!engineLoader.liquid) { + tap.test('Liquid engine not installed, skipping tests.', function(test) { + test.end(); + }); + return; +} + +// fake pattern lab constructor: +// sets up a fake patternlab object, which is needed by the pattern processing +// apparatus. +function fakePatternLab() { + var fpl = { + graph: PatternGraph.empty(), + partials: {}, + patterns: [], + footer: '', + header: '', + listitems: {}, + data: { + link: {}, + }, + config: require('../patternlab-config.json'), + package: {}, + }; + + // patch the pattern source so the pattern assembler can correctly determine + // the "subdir" + fpl.config.paths.source.patterns = './test/files/_liquid-test-patterns'; + + return fpl; +} + +// function for testing sets of partials +function testFindPartials(test, partialTests) { + test.plan(partialTests.length + 1); + + // setup current pattern from what we would have during execution + // docs on partial syntax are here: + // http://patternlab.io/docs/pattern-including.html + var currentPattern = Pattern.create( + '01-molecules/00-testing/00-test-mol.liquid', // relative path now + null, // data + { + template: partialTests.join(), + } + ); + + // act + var results = currentPattern.findPartials(); + + // assert + test.equals(results.length, partialTests.length); + partialTests.forEach(function(testString, index) { + test.equals(results[index], testString); + }); + + test.end(); +} + +tap.test('button liquid pattern renders', function(test) { + test.plan(1); + + var patternPath = path.join('00-atoms', '00-general', '08-button.liquid'); + var expectedValue = + '' + + eol + + eol + + 'Button' + + eol; + + // do all the normal processing of the pattern + var patternlab = new fakePatternLab(); + + var helloWorldPattern = loadPattern(patternPath, patternlab); + + return assembler + .process_pattern_iterative(helloWorldPattern, patternlab) + .then(() => { + assembler.process_pattern_recursive(patternPath, patternlab); + + test.equals(helloWorldPattern.render(), expectedValue); + }); +}); + +tap.test( + 'media object liquid pattern can see the atoms-button and atoms-image partials and renders them', + function(test) { + test.plan(1); + + // pattern paths + var buttonPatternPath = path.join( + '00-atoms', + '00-general', + '08-button.liquid' + ); + var imagePatternPath = path.join( + '00-atoms', + '00-general', + '09-image.liquid' + ); + var mediaObjectPatternPath = path.join( + '00-molecules', + '00-general', + '00-media-object.liquid' + ); + + var expectedValue = + '\n\n\n\n\n
\n \n\n \n\nButton\n\n\n
\n\n \n \n\n

Oh, hello world!

\n
\n
\n'; + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal processing of the pattern + const buttonPattern = loadPattern(buttonPatternPath, patternlab); + const imagePattern = loadPattern(imagePatternPath, patternlab); + const mediaObjectPattern = loadPattern(mediaObjectPatternPath, patternlab); + + return Promise.all([ + assembler.process_pattern_iterative(buttonPattern, patternlab), + assembler.process_pattern_iterative(imagePattern, patternlab), + assembler.process_pattern_iterative(mediaObjectPattern, patternlab), + ]).then(() => { + assembler.process_pattern_recursive(buttonPatternPath, patternlab); + assembler.process_pattern_recursive(imagePatternPath, patternlab); + assembler.process_pattern_recursive(mediaObjectPatternPath, patternlab); + + // test + // this pattern is too long - so just remove line endings on both sides and compare output + test.equals( + mediaObjectPattern.render().replace(/\r?\n|\r/gm, ''), + expectedValue.replace(/\r?\n|\r/gm, '') + ); + }); + } +); + +tap.test('liquid partials can render JSON values', { skip: true }, function( + test +) { + test.plan(1); + + // pattern paths + var pattern1Path = path.resolve( + testPatternsPath, + '00-atoms', + '00-global', + '00-helloworld-withdata.hbs' + ); + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal processing of the pattern + var helloWorldWithData = assembler.process_pattern_iterative( + pattern1Path, + patternlab + ); + assembler.process_pattern_recursive(pattern1Path, patternlab); + + // test + test.equals( + helloWorldWithData.render(), + 'Hello world!\nYeah, we got the subtitle from the JSON.\n' + ); + test.end(); +}); + +tap.test( + 'liquid partials use the JSON environment from the calling pattern and can accept passed parameters', + { skip: true }, + function(test) { + test.plan(1); + + // pattern paths + var atomPath = path.resolve( + testPatternsPath, + '00-atoms', + '00-global', + '00-helloworld-withdata.hbs' + ); + var molPath = path.resolve( + testPatternsPath, + '00-molecules', + '00-global', + '00-call-atom-with-molecule-data.hbs' + ); + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal processing of the pattern + var atom = assembler.process_pattern_iterative(atomPath, patternlab); + var mol = assembler.process_pattern_iterative(molPath, patternlab); + assembler.process_pattern_recursive(atomPath, patternlab); + assembler.process_pattern_recursive(molPath, patternlab); + + // test + test.equals( + mol.render(), + '

Call with default JSON environment:

\nThis is Hello world!\nfrom the default JSON.\n\n\n

Call with passed parameter:

\nHowever, this is Hello world!\nfrom a totally different blob.\n\n' + ); + test.end(); + } +); + +tap.test('find_pattern_partials finds partials', function(test) { + testFindPartials(test, [ + '{% include "atoms-image" %}', + "{% include 'atoms-image' %}", + "{%include 'atoms-image'%}", + "{% include 'molecules-template' only %}", + "{% include 'organisms-sidebar' ignore missing %}", + "{% include 'organisms-sidebar' ignore missing only %}", + ]); +}); + +tap.test('find_pattern_partials finds verbose partials', function(test) { + testFindPartials(test, [ + "{% include '01-molecules/06-components/03-comment-header.liquid' %}", + "{% include '00-atoms/00-global/06-test' %}", + ]); +}); + +tap.test( + 'find_pattern_partials finds partials with liquid parameters', + function(test) { + testFindPartials(test, [ + "{% include 'molecules-template' with {'foo': 'bar'} %}", + "{% include 'molecules-template' with vars %}", + "{% include 'molecules-template.liquid' with {'foo': 'bar'} only %}", + "{% include 'organisms-sidebar' ignore missing with {'foo': 'bar'} %}", + ]); + } +); diff --git a/test/engine_mustache_tests.js b/packages/core/test/engine_mustache_tests.js similarity index 51% rename from test/engine_mustache_tests.js rename to packages/core/test/engine_mustache_tests.js index da811be0e..8f0241d24 100644 --- a/test/engine_mustache_tests.js +++ b/packages/core/test/engine_mustache_tests.js @@ -1,27 +1,40 @@ -"use strict"; +'use strict'; +/*eslint-disable no-shadow*/ +var tap = require('tap'); var path = require('path'); -var pa = require('../core/lib/pattern_assembler'); -var Pattern = require('../core/lib/object_factory').Pattern; +var Pattern = require('../src/lib/object_factory').Pattern; +var PatternGraph = require('../src/lib/pattern_graph').PatternGraph; var testPatternsPath = path.resolve(__dirname, 'files', '_patterns'); var eol = require('os').EOL; +var config = require('./util/patternlab-config.json'); + +// don't run these tests unless mustache is installed +var engineLoader = require('../src/lib/pattern_engines'); +engineLoader.loadAllEngines(config); +if (!engineLoader.mustache) { + tap.test('Mustache engine not installed, skipping tests.', function(test) { + test.end(); + }); + return; +} // fake pattern lab constructor: // sets up a fake patternlab object, which is needed by the pattern processing // apparatus. function fakePatternLab() { var fpl = { + graph: PatternGraph.empty(), partials: {}, patterns: [], footer: '', header: '', listitems: {}, - listItemArray: [], data: { - link: {} + link: {}, }, - config: require('../patternlab-config.json'), - package: {} + config: config, + package: {}, }; // patch the pattern source so the pattern assembler can correctly determine @@ -33,7 +46,7 @@ function fakePatternLab() { // function for testing sets of partials function testFindPartials(test, partialTests) { - test.expect(partialTests.length + 1); + test.plan(partialTests.length + 1); // setup current pattern from what we would have during execution // docs on partial syntax are here: @@ -42,7 +55,7 @@ function testFindPartials(test, partialTests) { '01-molecules/00-testing/00-test-mol.mustache', // relative path now null, // data { - template: partialTests.join(eol) + template: partialTests.join(eol), } ); @@ -51,15 +64,15 @@ function testFindPartials(test, partialTests) { // assert test.equals(results.length, partialTests.length); - partialTests.forEach(function (testString, index) { + partialTests.forEach(function(testString, index) { test.equals(results[index], testString); }); - test.done(); + test.end(); } function testFindPartialsWithStyleModifiers(test, partialTests) { - test.expect(partialTests.length + 1); + test.plan(partialTests.length + 1); // setup current pattern from what we would have during execution // docs on partial syntax are here: @@ -68,7 +81,7 @@ function testFindPartialsWithStyleModifiers(test, partialTests) { '01-molecules/00-testing/00-test-mol.mustache', // relative path now null, // data { - template: partialTests.join(eol) + template: partialTests.join(eol), } ); @@ -77,15 +90,15 @@ function testFindPartialsWithStyleModifiers(test, partialTests) { // assert test.equals(results.length, partialTests.length); - partialTests.forEach(function (testString, index) { + partialTests.forEach(function(testString, index) { test.equals(results[index], testString); }); - test.done(); + test.end(); } function testFindPartialsWithPatternParameters(test, partialTests) { - test.expect(partialTests.length + 1); + test.plan(partialTests.length + 1); // setup current pattern from what we would have during execution // docs on partial syntax are here: @@ -94,7 +107,7 @@ function testFindPartialsWithPatternParameters(test, partialTests) { '01-molecules/00-testing/00-test-mol.mustache', // relative path now null, // data { - template: partialTests.join(eol) + template: partialTests.join(eol), } ); @@ -103,114 +116,136 @@ function testFindPartialsWithPatternParameters(test, partialTests) { // assert test.equals(results.length, partialTests.length); - partialTests.forEach(function (testString, index) { + partialTests.forEach(function(testString, index) { test.equals(results[index], testString); }); - test.done(); + test.end(); } -exports['engine_mustache'] = { - 'find_pattern_partials finds one simple partial': function (test) { - testFindPartials(test, [ - "{{> molecules-comment-header}}" - ]); - }, - - 'find_pattern_partials finds simple partials under stressed circumstances': function (test) { - testFindPartials(test, [ - "{{>molecules-comment-header}}", - "{{> " + eol + " molecules-comment-header" + eol + "}}", - "{{> molecules-weird-spacing }}" - ]); - }, +tap.test('find_pattern_partials finds one simple partial', function(test) { + testFindPartials(test, ['{{> molecules-comment-header}}']); +}); - 'find_pattern_partials finds one simple verbose partial': function (test) { +tap.test( + 'find_pattern_partials finds simple partials under stressed circumstances', + function(test) { testFindPartials(test, [ - '{{> 00-atoms/00-global/06-test }}' + '{{>molecules-comment-header}}', + '{{> ' + eol + ' molecules-comment-header' + eol + '}}', + '{{> molecules-weird-spacing }}', ]); - }, - - 'find_pattern_partials finds partials with parameters': function (test) { - testFindPartials(test, [ - "{{> molecules-single-comment(description: true) }}", - "{{> molecules-single-comment(description: 42) }}", - "{{> molecules-single-comment(description: '42') }}", - "{{> molecules-single-comment(description: \"42\") }}", - "{{> molecules-single-comment(description: 'test', anotherThing: 'retest') }}", - "{{> molecules-single-comment(description: false, anotherThing: \"retest\") }}", - '{{> molecules-single-comment(description:"A life is like a \"garden\". Perfect moments can be had, but not preserved, except in memory.") }}' - ]); - }, - - 'find_pattern_partials finds simple partials with style modifiers': function (test) { + } +); + +tap.test('find_pattern_partials finds one simple verbose partial', function( + test +) { + testFindPartials(test, ['{{> 00-atoms/00-global/06-test }}']); +}); + +tap.test('find_pattern_partials finds partials with parameters', function( + test +) { + testFindPartials(test, [ + '{{> molecules-single-comment(description: true) }}', + '{{> molecules-single-comment(description: 42) }}', + "{{> molecules-single-comment(description: '42') }}", + '{{> molecules-single-comment(description: "42") }}', + "{{> molecules-single-comment(description: 'test', anotherThing: 'retest') }}", + '{{> molecules-single-comment(description: false, anotherThing: "retest") }}', + '{{> molecules-single-comment(description:"A life is like a "garden". Perfect moments can be had, but not preserved, except in memory.") }}', + ]); +}); + +tap.test( + 'find_pattern_partials finds simple partials with style modifiers', + function(test) { testFindPartials(test, [ '{{> molecules-single-comment:foo }}', - '{{> molecules-single-comment:foo|bar }}' - ]); - }, - 'find_pattern_partials finds mixed partials': function (test) { - testFindPartials(test, [ - '{{> molecules-single-comment:foo(description: "test", anotherThing: true) }}', - '{{> molecules-single-comment:foo|bar(description: true) }}' + '{{> molecules-single-comment:foo|bar }}', ]); - }, - - 'find_pattern_partials finds one simple partial with styleModifier': function (test) { + } +); + +tap.test('find_pattern_partials finds mixed partials', function(test) { + testFindPartials(test, [ + '{{> molecules-single-comment:foo(description: "test", anotherThing: true) }}', + '{{> molecules-single-comment:foo|bar(description: true) }}', + ]); +}); + +tap.test( + 'find_pattern_partials finds one simple partial with styleModifier', + function(test) { testFindPartialsWithStyleModifiers(test, [ - "{{> molecules-comment-header:test}}" + '{{> molecules-comment-header:test}}', ]); - }, - 'find_pattern_partials finds partial with many styleModifiers': function (test) { + } +); + +tap.test( + 'find_pattern_partials finds partial with many styleModifiers', + function(test) { testFindPartialsWithStyleModifiers(test, [ - "{{> molecules-comment-header:test|test2|test3}}" + '{{> molecules-comment-header:test|test2|test3}}', ]); - }, - 'find_pattern_partials finds partials with differing styleModifiers': function (test) { + } +); + +tap.test( + 'find_pattern_partials finds partials with differing styleModifiers', + function(test) { testFindPartialsWithStyleModifiers(test, [ - "{{> molecules-comment-header:test|test2|test3}}", - "{{> molecules-comment-header:foo-1}}", - "{{> molecules-comment-header:bar_1}}" + '{{> molecules-comment-header:test|test2|test3}}', + '{{> molecules-comment-header:foo-1}}', + '{{> molecules-comment-header:bar_1}}', ]); - }, - 'find_pattern_partials finds partials with styleModifiers when parameters present': function (test) { + } +); + +tap.test( + 'find_pattern_partials finds partials with styleModifiers when parameters present', + function(test) { testFindPartialsWithStyleModifiers(test, [ - "{{> molecules-comment-header:test|test2|test3(description: true)}}", + '{{> molecules-comment-header:test|test2|test3(description: true)}}', "{{> molecules-comment-header:foo-1(description: 'foo')}}", - "{{> molecules-comment-header:bar_1(descrition: 'bar', anotherThing: 10102010) }}" + "{{> molecules-comment-header:bar_1(descrition: 'bar', anotherThing: 10102010) }}", ]); - }, + } +); - 'find_pattern_partials_with_parameters finds one simple partial with parameters': function (test) { +tap.test( + 'find_pattern_partials_with_parameters finds one simple partial with parameters', + function(test) { testFindPartialsWithPatternParameters(test, [ - "{{> molecules-comment-header(description: 'test')}}" + "{{> molecules-comment-header(description: 'test')}}", ]); - }, - 'find_pattern_partials_with_parameters finds partials with parameters': function (test) { + } +); + +tap.test( + 'find_pattern_partials_with_parameters finds partials with parameters', + function(test) { testFindPartialsWithPatternParameters(test, [ - "{{> molecules-single-comment(description: true) }}", - "{{> molecules-single-comment(description: 42) }}", + '{{> molecules-single-comment(description: true) }}', + '{{> molecules-single-comment(description: 42) }}', "{{> molecules-single-comment(description: '42') }}", - "{{> molecules-single-comment(description: \"42\") }}", + '{{> molecules-single-comment(description: "42") }}', "{{> molecules-single-comment(description: 'test', anotherThing: 'retest') }}", - "{{> molecules-single-comment(description: false, anotherThing: \"retest\") }}", - '{{> molecules-single-comment(description:"A life is like a \"garden\". Perfect moments can be had, but not preserved, except in memory.") }}' + '{{> molecules-single-comment(description: false, anotherThing: "retest") }}', + '{{> molecules-single-comment(description:"A life is like a "garden". Perfect moments can be had, but not preserved, except in memory.") }}', ]); - }, - 'find_pattern_partials finds partials with parameters when styleModifiers present': function (test) { + } +); + +tap.test( + 'find_pattern_partials finds partials with parameters when styleModifiers present', + function(test) { testFindPartialsWithPatternParameters(test, [ - "{{> molecules-comment-header:test|test2|test3(description: true)}}", + '{{> molecules-comment-header:test|test2|test3(description: true)}}', "{{> molecules-comment-header:foo-1(description: 'foo')}}", - "{{> molecules-comment-header:bar_1(descrition: 'bar', anotherThing: 10102010) }}" + "{{> molecules-comment-header:bar_1(descrition: 'bar', anotherThing: 10102010) }}", ]); } - -}; - - -// don't run these tests unless mustache is installed -var engineLoader = require('../core/lib/pattern_engines'); -if (!engineLoader.mustache) { - console.log("Mustache engine not installed, skipping tests."); - delete exports.engine_mustache; -} +); diff --git a/packages/core/test/engine_react_tests.js b/packages/core/test/engine_react_tests.js new file mode 100644 index 000000000..2d271bb9b --- /dev/null +++ b/packages/core/test/engine_react_tests.js @@ -0,0 +1,47 @@ +const path = require('path'); +const fs = require('fs'); +const tap = require('tap'); +const loadPattern = require('../src/lib/loadPattern'); +const testUtils = require('./util/test_utils.js'); +const config = require('./util/patternlab-config.json'); +const engineLoader = require('../src/lib/pattern_engines'); +const testPatternsPath = path.resolve( + __dirname, + 'files', + '_react-test-patterns' +); + +engineLoader.loadAllEngines(config); + +// don't run these tests unless the react engine is installed +if (!engineLoader.react) { + tap.test('React engine not installed, skipping tests.', test => { + test.end(); + }); +} else { + const fpl = testUtils.fakePatternLab(testPatternsPath); + + tap.test('Load the hello world pattern and verify contents', test => { + const patternPath = path.join( + testPatternsPath, + '00-atoms/00-general/HelloWorld.jsx' + ); + const patternContent = fs.readFileSync(patternPath, { encoding: 'utf8' }); + const pattern = loadPattern(patternPath, fpl); + + test.equals(pattern.template, patternContent); + test.end(); + }); + + tap.test('Load the hello world pattern and verify output', test => { + const patternPath = path.join( + testPatternsPath, + '00-atoms/00-general/HelloWorld.jsx' + ); + const pattern = loadPattern(patternPath, fpl); + + return pattern.render().then(output => { + test.equals(output, '
Hello world!
\n'); + }); + }); +} diff --git a/packages/core/test/engine_twig_tests.js b/packages/core/test/engine_twig_tests.js new file mode 100644 index 000000000..33a49bbe9 --- /dev/null +++ b/packages/core/test/engine_twig_tests.js @@ -0,0 +1,256 @@ +'use strict'; +/*eslint-disable dot-notation*/ +/*eslint-disable no-shadow*/ + +var tap = require('tap'); +var path = require('path'); +var loadPattern = require('../src/lib/loadPattern'); +var Pattern = require('../src/lib/object_factory').Pattern; +var PatternGraph = require('../src/lib/pattern_graph').PatternGraph; +var eol = require('os').EOL; + +// don't run these tests unless twig is installed +var engineLoader = require('../src/lib/pattern_engines'); +if (!engineLoader.twig) { + tap.test('Twig engine not installed, skipping tests.', function(test) { + test.end(); + }); + return; +} + +// fake pattern lab constructor: +// sets up a fake patternlab object, which is needed by the pattern processing +// apparatus. +function fakePatternLab() { + var fpl = { + graph: PatternGraph.empty(), + partials: {}, + patterns: [], + footer: '', + header: '', + listitems: {}, + data: { + link: {}, + }, + config: require('../patternlab-config.json'), + package: {}, + }; + + // patch the pattern source so the pattern assembler can correctly determine + // the "subdir" + fpl.config.paths.source.patterns = './test/files/_twig-test-patterns'; + + return fpl; +} + +// function for testing sets of partials +function testFindPartials(test, partialTests) { + test.plan(partialTests.length + 1); + + // setup current pattern from what we would have during execution + // docs on partial syntax are here: + // http://patternlab.io/docs/pattern-including.html + var currentPattern = Pattern.create( + '01-molecules/00-testing/00-test-mol.twig', // relative path now + null, // data + { + template: partialTests.join(), + } + ); + + // act + var results = currentPattern.findPartials(); + + // assert + test.equals(results.length, partialTests.length); + partialTests.forEach(function(testString, index) { + test.equals(results[index], testString); + }); + + test.end(); +} + +tap.test('button twig pattern renders', function(test) { + test.plan(1); + + var patternPath = path.join('00-atoms', '00-general', '08-button.twig'); + var expectedValue = + '' + + eol + + eol + + 'Button' + + eol; + + // do all the normal processing of the pattern + var patternlab = new fakePatternLab(); + + var helloWorldPattern = loadPattern(patternPath, patternlab); + + return assembler + .process_pattern_iterative(helloWorldPattern, patternlab) + .then(() => { + assembler.process_pattern_recursive(patternPath, patternlab); + + test.equals(helloWorldPattern.render(), expectedValue); + }); +}); + +tap.test( + 'media object twig pattern can see the atoms-button and atoms-image partials and renders them', + function(test) { + test.plan(1); + + // pattern paths + var buttonPatternPath = path.join( + '00-atoms', + '00-general', + '08-button.twig' + ); + var imagePatternPath = path.join('00-atoms', '00-general', '09-image.twig'); + var mediaObjectPatternPath = path.join( + '00-molecules', + '00-general', + '00-media-object.twig' + ); + + var expectedValue = + '\n\n\n\n\n
\n \n\n \n\nButton\n\n\n
\n\n \n \n\n

Oh, hello world!

\n
\n
\n'; + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal processing of the pattern + const buttonPattern = loadPattern(buttonPatternPath, patternlab); + const imagePattern = loadPattern(imagePatternPath, patternlab); + const mediaObjectPattern = loadPattern(mediaObjectPatternPath, patternlab); + + return Promise.all([ + assembler.process_pattern_iterative(buttonPattern, patternlab), + assembler.process_pattern_iterative(imagePattern, patternlab), + assembler.process_pattern_iterative(mediaObjectPattern, patternlab), + ]).then(() => { + assembler.process_pattern_recursive(buttonPatternPath, patternlab); + assembler.process_pattern_recursive(imagePatternPath, patternlab); + assembler.process_pattern_recursive(mediaObjectPatternPath, patternlab); + + // test + // this pattern is too long - so just remove line endings on both sides and compare output + test.equals( + mediaObjectPattern.render().replace(/\r?\n|\r/gm, ''), + expectedValue.replace(/\r?\n|\r/gm, '') + ); + }); + } +); + +tap.test('twig partials can render JSON values', { skip: true }, function( + test +) { + test.plan(1); + + // pattern paths + var pattern1Path = path.resolve( + testPatternsPath, + '00-atoms', + '00-global', + '00-helloworld-withdata.hbs' + ); + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal processing of the pattern + var helloWorldWithData = assembler.process_pattern_iterative( + pattern1Path, + patternlab + ); + assembler.process_pattern_recursive(pattern1Path, patternlab); + + // test + test.equals( + helloWorldWithData.render(), + 'Hello world!\nYeah, we got the subtitle from the JSON.\n' + ); + test.end(); +}); + +tap.test( + 'twig partials use the JSON environment from the calling pattern and can accept passed parameters', + { skip: true }, + function(test) { + test.plan(1); + + // pattern paths + var atomPath = path.resolve( + testPatternsPath, + '00-atoms', + '00-global', + '00-helloworld-withdata.hbs' + ); + var molPath = path.resolve( + testPatternsPath, + '00-molecules', + '00-global', + '00-call-atom-with-molecule-data.hbs' + ); + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal processing of the pattern + var atom = assembler.process_pattern_iterative(atomPath, patternlab); + var mol = assembler.process_pattern_iterative(molPath, patternlab); + assembler.process_pattern_recursive(atomPath, patternlab); + assembler.process_pattern_recursive(molPath, patternlab); + + // test + test.equals( + mol.render(), + '

Call with default JSON environment:

\nThis is Hello world!\nfrom the default JSON.\n\n\n

Call with passed parameter:

\nHowever, this is Hello world!\nfrom a totally different blob.\n\n' + ); + test.end(); + } +); + +tap.test('find_pattern_partials finds partials', function(test) { + testFindPartials(test, [ + '{% include "atoms-image" %}', + "{% include 'atoms-image' %}", + "{%include 'atoms-image'%}", + "{% include 'molecules-template' only %}", + "{% include 'organisms-sidebar' ignore missing %}", + "{% include 'organisms-sidebar' ignore missing only %}", + ]); +}); + +tap.test('find_pattern_partials finds verbose partials', function(test) { + testFindPartials(test, [ + "{% include '01-molecules/06-components/03-comment-header.twig' %}", + "{% include '00-atoms/00-global/06-test' %}", + ]); +}); + +tap.test('find_pattern_partials finds partials with twig parameters', function( + test +) { + testFindPartials(test, [ + "{% include 'molecules-template' with {'foo': 'bar'} %}", + "{% include 'molecules-template' with vars %}", + "{% include 'molecules-template.twig' with {'foo': 'bar'} only %}", + "{% include 'organisms-sidebar' ignore missing with {'foo': 'bar'} %}", + ]); +}); diff --git a/packages/core/test/engine_underscore_tests.js b/packages/core/test/engine_underscore_tests.js new file mode 100644 index 000000000..fbdbb5617 --- /dev/null +++ b/packages/core/test/engine_underscore_tests.js @@ -0,0 +1,163 @@ +'use strict'; + +var tap = require('tap'); +var path = require('path'); +var loadPattern = require('../src/lib/loadPattern'); +var PatternGraph = require('../src/lib/pattern_graph').PatternGraph; +var testPatternsPath = path.resolve( + __dirname, + 'files', + '_underscore-test-patterns' +); +var eol = require('os').EOL; + +// don't run tests unless underscore is installed +var engineLoader = require('../src/lib/pattern_engines'); +if (!engineLoader.underscore) { + tap.test('Underscore engine not installed, skipping tests', function(test) { + test.end(); + }); + return; +} + +// fake pattern lab constructor: +// sets up a fake patternlab object, which is needed by the pattern processing +// apparatus. +function fakePatternLab() { + var fpl = { + graph: PatternGraph.empty(), + partials: {}, + patterns: [], + footer: '', + header: '', + listitems: {}, + data: { + link: {}, + }, + config: require('../patternlab-config.json'), + package: {}, + }; + + // patch the pattern source so the pattern assembler can correctly determine + // the "subdir" + fpl.config.paths.source.patterns = testPatternsPath; + + return fpl; +} + +tap.test('hello world underscore pattern renders', function(test) { + test.plan(1); + + var patternPath = path.resolve( + testPatternsPath, + '00-atoms', + '00-global', + '00-helloworld.html' + ); + + // do all the normal processing of the pattern + var patternlab = new fakePatternLab(); + + const helloWorldPattern = loadPattern(patternPath, patternlab); + + return assembler + .process_pattern_iterative(helloWorldPattern, patternlab) + .then(() => { + assembler.process_pattern_recursive(patternPath, patternlab); + + test.equals(helloWorldPattern.render(), 'Hello world!' + eol); + }); +}); + +tap.test('underscore partials can render JSON values', function(test) { + test.plan(1); + + // pattern paths + var pattern1Path = path.resolve( + testPatternsPath, + '00-atoms', + '00-global', + '00-helloworld-withdata.html' + ); + + // set up environment + var patternlab = new fakePatternLab(); // environment + + // do all the normal processing of the pattern + const helloWorldWithData = loadPattern(pattern1Path, patternlab); + return assembler + .process_pattern_iterative(helloWorldWithData, patternlab) + .then(() => { + assembler.process_pattern_recursive(pattern1Path, patternlab); + + // test + test.equals( + helloWorldWithData.render(), + 'Hello world!' + eol + 'Yeah, we got the subtitle from the JSON.' + eol + ); + }); +}); + +tap.test( + 'findPartial return the ID of the partial, given a whole partial call', + function(test) { + var engineLoader = require('../src/lib/pattern_engines'); + var underscoreEngine = engineLoader.underscore; + + test.plan(1); + + // do all the normal processing of the pattern + // test + test.equals( + underscoreEngine.findPartial( + "<%= _.renderNamedPartial('molecules-details', obj) %>" + ), + 'molecules-details' + ); + test.end(); + } +); + +tap.test( + 'hidden underscore patterns can be called by their nice names', + function(test) { + const util = require('./util/test_utils.js'); + + //arrange + const testPatternsPath = path.resolve( + __dirname, + 'files', + '_underscore-test-patterns' + ); + const pl = util.fakePatternLab(testPatternsPath); + + var hiddenPatternPath = path.join( + '00-atoms', + '00-global', + '_00-hidden.html' + ); + var testPatternPath = path.join( + '00-molecules', + '00-global', + '00-hidden-pattern-tester.html' + ); + + var hiddenPattern = loadPattern(hiddenPatternPath, pl); + var testPattern = loadPattern(testPatternPath, pl); + + return Promise.all([ + pattern_assembler.process_pattern_iterative(hiddenPattern, pl), + pattern_assembler.process_pattern_iterative(testPattern, pl), + ]).then(() => { + pattern_assembler.process_pattern_recursive(hiddenPatternPath, pl); + pattern_assembler.process_pattern_recursive(testPatternPath, pl); + + //act + test.equals( + util.sanitized(testPattern.render()), + util.sanitized("Here's the hidden atom: [I'm the hidden atom\n]\n") + ); + test.end(); + }); + } +); diff --git a/packages/core/test/exportData_tests.js b/packages/core/test/exportData_tests.js new file mode 100644 index 000000000..93cd9573d --- /dev/null +++ b/packages/core/test/exportData_tests.js @@ -0,0 +1,68 @@ +'use strict'; + +const path = require('path'); +const tap = require('tap'); +const rewire = require('rewire'); + +const exportData = rewire('../src/lib/exportData'); +const util = require('./util/test_utils.js'); + +const testPatternsPath = path.resolve(__dirname, 'files', '_patterns'); + +const fsMock = { + outputFileSync: function(path, content) { + /* INTENTIONAL NOOP */ + }, +}; + +//set our mocks in place of usual require() +exportData.__set__({ + fs: fsMock, +}); + +const patternlab = util.fakePatternLab(testPatternsPath); +const result = exportData(patternlab); + +tap.test('exportData exports config', function(test) { + test.equals(result.indexOf('config') > -1, true); + test.equals(result.indexOf('paths') > -1, true); + test.equals(result.indexOf('theme') > -1, true); + test.end(); +}); + +tap.test('exportData exports ishControls', function(test) { + test.equals(result.indexOf('ishControlsHide') > -1, true); + test.end(); +}); + +tap.test('exportData exports navItems', function(test) { + test.equals(result.indexOf('patternTypes') > -1, true); + test.end(); +}); + +tap.test('exportData exports patternPaths', function(test) { + test.equals(result.indexOf('patternPaths') > -1, true); + test.end(); +}); + +tap.test('exportData exports viewAllPaths', function(test) { + test.equals(result.indexOf('viewAllPaths') > -1, true); + test.end(); +}); + +tap.test('exportData exports plugins', function(test) { + test.equals(result.indexOf('plugins') > -1, true); + test.end(); +}); + +tap.test('exportData exports defaultShowPatternInfo', function(test) { + test.equals(result.indexOf('defaultShowPatternInfo') > -1, true); + test.equals(result.indexOf('"defaultShowPatternInfo":false') > -1, true); + test.end(); +}); + +tap.test('exportData exports defaultPattern', function(test) { + test.equals(result.indexOf('defaultPattern') > -1, true); + test.equals(result.indexOf('"defaultPattern":"all"') > -1, true); + test.end(); +}); diff --git a/packages/core/test/files/_data/data.json b/packages/core/test/files/_data/data.json new file mode 100644 index 000000000..1e3cc53b3 --- /dev/null +++ b/packages/core/test/files/_data/data.json @@ -0,0 +1,2 @@ +{ "data" : "test", "from_json" : "from_json" } + diff --git a/packages/core/test/files/_data/data.yaml b/packages/core/test/files/_data/data.yaml new file mode 100644 index 000000000..50c5fd0cd --- /dev/null +++ b/packages/core/test/files/_data/data.yaml @@ -0,0 +1 @@ +from_yaml: "from_yaml" diff --git a/packages/core/test/files/_data/data.yml b/packages/core/test/files/_data/data.yml new file mode 100644 index 000000000..e64d69624 --- /dev/null +++ b/packages/core/test/files/_data/data.yml @@ -0,0 +1 @@ +from_yml: "from_yml" diff --git a/packages/core/test/files/_data/foo-other.json b/packages/core/test/files/_data/foo-other.json new file mode 100644 index 000000000..7b6c4a3c4 --- /dev/null +++ b/packages/core/test/files/_data/foo-other.json @@ -0,0 +1,3 @@ +{ + "foo": "wrong" +} \ No newline at end of file diff --git a/test/files/_data/foo.json b/packages/core/test/files/_data/foo.json similarity index 100% rename from test/files/_data/foo.json rename to packages/core/test/files/_data/foo.json diff --git a/packages/core/test/files/_data/listitems.json b/packages/core/test/files/_data/listitems.json new file mode 100644 index 000000000..0cb4c328d --- /dev/null +++ b/packages/core/test/files/_data/listitems.json @@ -0,0 +1,17 @@ +{ + "1": { + "title": "tA", + "description": "dA", + "message": "mA" + }, + "2": { + "title": "tB", + "description": "dB", + "message": "mB" + }, + "3": { + "title": "tC", + "description": "dC", + "message": "mC" + } +} diff --git a/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.hbs b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.hbs similarity index 100% rename from test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.hbs rename to packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.hbs diff --git a/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.json b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.json similarity index 100% rename from test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.json rename to packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld-withdata.json diff --git a/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld.hbs b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld.hbs similarity index 100% rename from test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld.hbs rename to packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/00-helloworld.hbs diff --git a/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/10-at-partial-block.hbs b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/10-at-partial-block.hbs new file mode 100644 index 000000000..59e0522cd --- /dev/null +++ b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/10-at-partial-block.hbs @@ -0,0 +1,2 @@ +{{> @partial-block }} +It worked! diff --git a/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/_00-hidden.hbs b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/_00-hidden.hbs new file mode 100644 index 000000000..b2c93a13e --- /dev/null +++ b/packages/core/test/files/_handlebars-test-patterns/00-atoms/00-global/_00-hidden.hbs @@ -0,0 +1 @@ +I'm the hidden atom diff --git a/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.hbs b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.hbs similarity index 100% rename from test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.hbs rename to packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.hbs diff --git a/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json similarity index 100% rename from test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json rename to packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json diff --git a/test/files/_handlebars-test-patterns/00-molecules/00-global/00-helloworlds.hbs b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-helloworlds.hbs similarity index 100% rename from test/files/_handlebars-test-patterns/00-molecules/00-global/00-helloworlds.hbs rename to packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-helloworlds.hbs diff --git a/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.hbs b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.hbs new file mode 100644 index 000000000..ce4a0c864 --- /dev/null +++ b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.hbs @@ -0,0 +1 @@ +Here's the hidden atom: [{{> atoms-hidden}}] diff --git a/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/10-call-at-partial-block.hbs b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/10-call-at-partial-block.hbs new file mode 100644 index 000000000..d653c1196 --- /dev/null +++ b/packages/core/test/files/_handlebars-test-patterns/00-molecules/00-global/10-call-at-partial-block.hbs @@ -0,0 +1,3 @@ +{{#> atoms-at-partial-block }} +Hello World! +{{/atoms-at-partial-block}} diff --git a/test/files/_twig-test-patterns/00-atoms/00-general/08-button.twig b/packages/core/test/files/_liquid_test-patterns/00-atoms/00-general/08-button.liquid similarity index 100% rename from test/files/_twig-test-patterns/00-atoms/00-general/08-button.twig rename to packages/core/test/files/_liquid_test-patterns/00-atoms/00-general/08-button.liquid diff --git a/test/files/_twig-test-patterns/00-atoms/00-general/09-image.twig b/packages/core/test/files/_liquid_test-patterns/00-atoms/00-general/09-image.liquid similarity index 100% rename from test/files/_twig-test-patterns/00-atoms/00-general/09-image.twig rename to packages/core/test/files/_liquid_test-patterns/00-atoms/00-general/09-image.liquid diff --git a/test/files/_twig-test-patterns/00-molecules/00-general/00-media-object.twig b/packages/core/test/files/_liquid_test-patterns/00-molecules/00-general/00-media-object.liquid similarity index 100% rename from test/files/_twig-test-patterns/00-molecules/00-general/00-media-object.twig rename to packages/core/test/files/_liquid_test-patterns/00-molecules/00-general/00-media-object.liquid diff --git a/packages/core/test/files/_meta/_00-head.hbs b/packages/core/test/files/_meta/_00-head.hbs new file mode 100644 index 000000000..b1f5c1ce0 --- /dev/null +++ b/packages/core/test/files/_meta/_00-head.hbs @@ -0,0 +1,16 @@ + + + + {{ title }} + + + + + + + + {{{ patternLabHead }}} + + + + diff --git a/packages/core/test/files/_meta/_00-head.html b/packages/core/test/files/_meta/_00-head.html new file mode 100644 index 000000000..b1f5c1ce0 --- /dev/null +++ b/packages/core/test/files/_meta/_00-head.html @@ -0,0 +1,16 @@ + + + + {{ title }} + + + + + + + + {{{ patternLabHead }}} + + + + diff --git a/test/files/empty/.gitkeep b/packages/core/test/files/_meta/_00-head.mustache similarity index 100% rename from test/files/empty/.gitkeep rename to packages/core/test/files/_meta/_00-head.mustache diff --git a/packages/core/test/files/_meta/_01-foot.hbs b/packages/core/test/files/_meta/_01-foot.hbs new file mode 100644 index 000000000..797d9418d --- /dev/null +++ b/packages/core/test/files/_meta/_01-foot.hbs @@ -0,0 +1,6 @@ + + +{{{ patternLabFoot }}} + + + diff --git a/packages/core/test/files/_meta/_01-foot.html b/packages/core/test/files/_meta/_01-foot.html new file mode 100644 index 000000000..797d9418d --- /dev/null +++ b/packages/core/test/files/_meta/_01-foot.html @@ -0,0 +1,6 @@ + + +{{{ patternLabFoot }}} + + + diff --git a/packages/core/test/files/_meta/_01-foot.mustache b/packages/core/test/files/_meta/_01-foot.mustache new file mode 100644 index 000000000..e69de29bb diff --git a/test/files/_patterns/00-test/00-foo.md b/packages/core/test/files/_patterns/00-test/00-foo.md similarity index 84% rename from test/files/_patterns/00-test/00-foo.md rename to packages/core/test/files/_patterns/00-test/00-foo.md index 4c8bbf296..b284e8103 100644 --- a/test/files/_patterns/00-test/00-foo.md +++ b/packages/core/test/files/_patterns/00-test/00-foo.md @@ -1,3 +1,6 @@ +--- +state: inreview +--- ## A Simple Include This pattern contains an include of `test-bar`. It also has this markdown file, which does not have frontmatter. diff --git a/test/files/_patterns/00-test/00-foo.mustache b/packages/core/test/files/_patterns/00-test/00-foo.mustache similarity index 100% rename from test/files/_patterns/00-test/00-foo.mustache rename to packages/core/test/files/_patterns/00-test/00-foo.mustache diff --git a/test/files/_patterns/00-test/01-bar.md b/packages/core/test/files/_patterns/00-test/01-bar.md similarity index 57% rename from test/files/_patterns/00-test/01-bar.md rename to packages/core/test/files/_patterns/00-test/01-bar.md index d4fd865a2..a811dacd7 100644 --- a/test/files/_patterns/00-test/01-bar.md +++ b/packages/core/test/files/_patterns/00-test/01-bar.md @@ -1,5 +1,7 @@ --- -status: complete +state: complete +title: An Atom Walks Into a Bar +joke: bad --- ## A Simple Bit of Markup diff --git a/test/files/_patterns/00-test/01-bar.mustache b/packages/core/test/files/_patterns/00-test/01-bar.mustache similarity index 100% rename from test/files/_patterns/00-test/01-bar.mustache rename to packages/core/test/files/_patterns/00-test/01-bar.mustache diff --git a/packages/core/test/files/_patterns/00-test/02-baz.md b/packages/core/test/files/_patterns/00-test/02-baz.md new file mode 100644 index 000000000..f6f78b399 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/02-baz.md @@ -0,0 +1 @@ +### Only baz \ No newline at end of file diff --git a/packages/core/test/files/_patterns/00-test/02-baz.mustache b/packages/core/test/files/_patterns/00-test/02-baz.mustache new file mode 100644 index 000000000..3f9538666 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/02-baz.mustache @@ -0,0 +1 @@ +baz \ No newline at end of file diff --git a/packages/core/test/files/_patterns/00-test/03-styled-atom.json b/packages/core/test/files/_patterns/00-test/03-styled-atom.json new file mode 100644 index 000000000..475247e1a --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/03-styled-atom.json @@ -0,0 +1,3 @@ +{ + "message": "baseMessage" +} diff --git a/packages/core/test/files/_patterns/00-test/03-styled-atom.md b/packages/core/test/files/_patterns/00-test/03-styled-atom.md new file mode 100644 index 000000000..58c494066 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/03-styled-atom.md @@ -0,0 +1,3 @@ +--- +state: inprogress +--- diff --git a/test/files/_patterns/00-test/03-styled-atom.mustache b/packages/core/test/files/_patterns/00-test/03-styled-atom.mustache similarity index 100% rename from test/files/_patterns/00-test/03-styled-atom.mustache rename to packages/core/test/files/_patterns/00-test/03-styled-atom.mustache diff --git a/test/files/_patterns/00-test/03-styled-atom~alt.json b/packages/core/test/files/_patterns/00-test/03-styled-atom~alt.json similarity index 100% rename from test/files/_patterns/00-test/03-styled-atom~alt.json rename to packages/core/test/files/_patterns/00-test/03-styled-atom~alt.json diff --git a/test/files/_patterns/00-test/04-group.mustache b/packages/core/test/files/_patterns/00-test/04-group.mustache similarity index 100% rename from test/files/_patterns/00-test/04-group.mustache rename to packages/core/test/files/_patterns/00-test/04-group.mustache diff --git a/test/files/_patterns/00-test/05-group2.mustache b/packages/core/test/files/_patterns/00-test/05-group2.mustache similarity index 100% rename from test/files/_patterns/00-test/05-group2.mustache rename to packages/core/test/files/_patterns/00-test/05-group2.mustache diff --git a/test/files/_patterns/00-test/06-mixed.mustache b/packages/core/test/files/_patterns/00-test/06-mixed.mustache similarity index 100% rename from test/files/_patterns/00-test/06-mixed.mustache rename to packages/core/test/files/_patterns/00-test/06-mixed.mustache diff --git a/test/files/_patterns/00-test/07-mixed-params.mustache b/packages/core/test/files/_patterns/00-test/07-mixed-params.mustache similarity index 100% rename from test/files/_patterns/00-test/07-mixed-params.mustache rename to packages/core/test/files/_patterns/00-test/07-mixed-params.mustache diff --git a/test/files/_patterns/00-test/08-bookend-params.mustache b/packages/core/test/files/_patterns/00-test/08-bookend-params.mustache similarity index 100% rename from test/files/_patterns/00-test/08-bookend-params.mustache rename to packages/core/test/files/_patterns/00-test/08-bookend-params.mustache diff --git a/test/files/_patterns/00-test/09-bookend.mustache b/packages/core/test/files/_patterns/00-test/09-bookend.mustache similarity index 100% rename from test/files/_patterns/00-test/09-bookend.mustache rename to packages/core/test/files/_patterns/00-test/09-bookend.mustache diff --git a/test/files/_patterns/00-test/10-multiple-classes-numeric.mustache b/packages/core/test/files/_patterns/00-test/10-multiple-classes-numeric.mustache similarity index 100% rename from test/files/_patterns/00-test/10-multiple-classes-numeric.mustache rename to packages/core/test/files/_patterns/00-test/10-multiple-classes-numeric.mustache diff --git a/test/files/_patterns/00-test/11-bookend-listitem.mustache b/packages/core/test/files/_patterns/00-test/11-bookend-listitem.mustache similarity index 100% rename from test/files/_patterns/00-test/11-bookend-listitem.mustache rename to packages/core/test/files/_patterns/00-test/11-bookend-listitem.mustache diff --git a/test/files/_patterns/00-test/12-another-styled-atom.mustache b/packages/core/test/files/_patterns/00-test/12-another-styled-atom.mustache similarity index 100% rename from test/files/_patterns/00-test/12-another-styled-atom.mustache rename to packages/core/test/files/_patterns/00-test/12-another-styled-atom.mustache diff --git a/test/files/_patterns/00-test/13-listitem.mustache b/packages/core/test/files/_patterns/00-test/13-listitem.mustache similarity index 100% rename from test/files/_patterns/00-test/13-listitem.mustache rename to packages/core/test/files/_patterns/00-test/13-listitem.mustache diff --git a/test/files/_patterns/00-test/14-inception.mustache b/packages/core/test/files/_patterns/00-test/14-inception.mustache similarity index 100% rename from test/files/_patterns/00-test/14-inception.mustache rename to packages/core/test/files/_patterns/00-test/14-inception.mustache diff --git a/packages/core/test/files/_patterns/00-test/15-hidden-pattern-tester.mustache b/packages/core/test/files/_patterns/00-test/15-hidden-pattern-tester.mustache new file mode 100644 index 000000000..5598f783f --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/15-hidden-pattern-tester.mustache @@ -0,0 +1,2 @@ +Hello there! +Here's the hidden atom: [{{> test-hidden-pattern}}] diff --git a/test/files/_patterns/00-test/474-pseudomodifier.mustache b/packages/core/test/files/_patterns/00-test/474-pseudomodifier.mustache similarity index 100% rename from test/files/_patterns/00-test/474-pseudomodifier.mustache rename to packages/core/test/files/_patterns/00-test/474-pseudomodifier.mustache diff --git a/test/files/_patterns/00-test/474-pseudomodifier~test.json b/packages/core/test/files/_patterns/00-test/474-pseudomodifier~test.json similarity index 100% rename from test/files/_patterns/00-test/474-pseudomodifier~test.json rename to packages/core/test/files/_patterns/00-test/474-pseudomodifier~test.json diff --git a/packages/core/test/files/_patterns/00-test/539-a.mustache b/packages/core/test/files/_patterns/00-test/539-a.mustache new file mode 100644 index 000000000..ba154f442 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/539-a.mustache @@ -0,0 +1,4 @@ +a +{{ #a }} +a! +{{ /a }} diff --git a/packages/core/test/files/_patterns/00-test/539-b.mustache b/packages/core/test/files/_patterns/00-test/539-b.mustache new file mode 100644 index 000000000..70997abb3 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/539-b.mustache @@ -0,0 +1,5 @@ +b +{{ #b }} +b! +{{ /b }} +{{> test-a(a: true) }} diff --git a/packages/core/test/files/_patterns/00-test/539-c.mustache b/packages/core/test/files/_patterns/00-test/539-c.mustache new file mode 100644 index 000000000..6098102c8 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/539-c.mustache @@ -0,0 +1,2 @@ +c +{{> test-b(b: true) }} diff --git a/packages/core/test/files/_patterns/00-test/553-repeatedListItems.mustache b/packages/core/test/files/_patterns/00-test/553-repeatedListItems.mustache new file mode 100644 index 000000000..8362b6d31 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/553-repeatedListItems.mustache @@ -0,0 +1,2 @@ +{{# listItems.three }}A{{/ listItems.three }} +{{# listItems.three }}B{{/ listItems.three }} diff --git a/packages/core/test/files/_patterns/00-test/685-list.mustache b/packages/core/test/files/_patterns/00-test/685-list.mustache new file mode 100644 index 000000000..1b3623c07 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/685-list.mustache @@ -0,0 +1,3 @@ +{{#listItems.three}} +{{title}} +{{/listItems.three}} diff --git a/packages/core/test/files/_patterns/00-test/_00-hidden-pattern.mustache b/packages/core/test/files/_patterns/00-test/_00-hidden-pattern.mustache new file mode 100644 index 000000000..0f5831922 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/_00-hidden-pattern.mustache @@ -0,0 +1 @@ +This is the hidden atom \ No newline at end of file diff --git a/test/files/_patterns/00-test/_ignored-pattern.mustache b/packages/core/test/files/_patterns/00-test/_ignored-pattern.mustache similarity index 100% rename from test/files/_patterns/00-test/_ignored-pattern.mustache rename to packages/core/test/files/_patterns/00-test/_ignored-pattern.mustache diff --git a/packages/core/test/files/_patterns/00-test/comment-tag.mustache b/packages/core/test/files/_patterns/00-test/comment-tag.mustache new file mode 100644 index 000000000..c78688163 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/comment-tag.mustache @@ -0,0 +1 @@ +

{{{ tag1 }}}

{{{ tag2 }}}

{{{ tag3 }}}

diff --git a/packages/core/test/files/_patterns/00-test/comment.mustache b/packages/core/test/files/_patterns/00-test/comment.mustache new file mode 100644 index 000000000..0dc3d3608 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/comment.mustache @@ -0,0 +1 @@ +

{{foo}}

{{description}}

diff --git a/test/files/_patterns/00-test/link.mustache b/packages/core/test/files/_patterns/00-test/link.mustache similarity index 100% rename from test/files/_patterns/00-test/link.mustache rename to packages/core/test/files/_patterns/00-test/link.mustache diff --git a/packages/core/test/files/_patterns/00-test/linkInParameter.mustache b/packages/core/test/files/_patterns/00-test/linkInParameter.mustache new file mode 100644 index 000000000..7cba618ce --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/linkInParameter.mustache @@ -0,0 +1 @@ +{{> test-link(url: 'link.test-comment') }} diff --git a/packages/core/test/files/_patterns/00-test/listWithListItems.listitems.json b/packages/core/test/files/_patterns/00-test/listWithListItems.listitems.json new file mode 100644 index 000000000..01497948f --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/listWithListItems.listitems.json @@ -0,0 +1,11 @@ +{ + "1": { + "title": "tX" + }, + "2": { + "title": "tY" + }, + "3": { + "title": "tZ" + } +} diff --git a/packages/core/test/files/_patterns/00-test/listWithListItems.mustache b/packages/core/test/files/_patterns/00-test/listWithListItems.mustache new file mode 100644 index 000000000..34699f54e --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/listWithListItems.mustache @@ -0,0 +1,3 @@ +{{#listItems.three}} + {{> test-mirror }} +{{/listItems.three}} diff --git a/packages/core/test/files/_patterns/00-test/listWithPartial.mustache b/packages/core/test/files/_patterns/00-test/listWithPartial.mustache new file mode 100644 index 000000000..5a734227a --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/listWithPartial.mustache @@ -0,0 +1,3 @@ +{{#listItems.two}} +{{> test-comment }} +{{/listItems.two}} diff --git a/packages/core/test/files/_patterns/00-test/mirror.mustache b/packages/core/test/files/_patterns/00-test/mirror.mustache new file mode 100644 index 000000000..31ea13421 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/mirror.mustache @@ -0,0 +1 @@ +{{title}}{{description}} diff --git a/test/files/_patterns/00-test/nav.json b/packages/core/test/files/_patterns/00-test/nav.json similarity index 100% rename from test/files/_patterns/00-test/nav.json rename to packages/core/test/files/_patterns/00-test/nav.json diff --git a/test/files/_patterns/00-test/nav.mustache b/packages/core/test/files/_patterns/00-test/nav.mustache similarity index 100% rename from test/files/_patterns/00-test/nav.mustache rename to packages/core/test/files/_patterns/00-test/nav.mustache diff --git a/packages/core/test/files/_patterns/00-test/paramMiddle.mustache b/packages/core/test/files/_patterns/00-test/paramMiddle.mustache new file mode 100644 index 000000000..31169fb7e --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/paramMiddle.mustache @@ -0,0 +1,3 @@ +
+ {{> test-link }} +
diff --git a/packages/core/test/files/_patterns/00-test/paramParent.json b/packages/core/test/files/_patterns/00-test/paramParent.json new file mode 100644 index 000000000..d913cc535 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/paramParent.json @@ -0,0 +1,3 @@ +{ + "url" : "link.test-foo" +} diff --git a/packages/core/test/files/_patterns/00-test/paramParent.mustache b/packages/core/test/files/_patterns/00-test/paramParent.mustache new file mode 100644 index 000000000..bf4e84562 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/paramParent.mustache @@ -0,0 +1 @@ +{{> test-paramMiddle(styleModifier: "foo") }} diff --git a/packages/core/test/files/_patterns/00-test/parameterTags.mustache b/packages/core/test/files/_patterns/00-test/parameterTags.mustache new file mode 100644 index 000000000..d8d3955f4 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/parameterTags.mustache @@ -0,0 +1 @@ +{{> test-comment-tag(tag1: 'Single-quoted', tag2: \"Double-quoted\", tag3: 'With attributes') }} diff --git a/packages/core/test/files/_patterns/00-test/sticky-comment-verbose.mustache b/packages/core/test/files/_patterns/00-test/sticky-comment-verbose.mustache new file mode 100644 index 000000000..55b21011d --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/sticky-comment-verbose.mustache @@ -0,0 +1 @@ +{{> 00-test/comment(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }} diff --git a/packages/core/test/files/_patterns/00-test/sticky-comment.mustache b/packages/core/test/files/_patterns/00-test/sticky-comment.mustache new file mode 100644 index 000000000..4bc1899e9 --- /dev/null +++ b/packages/core/test/files/_patterns/00-test/sticky-comment.mustache @@ -0,0 +1 @@ +{{> test-comment(description: 'A life is like a garden. Perfect moments can be had, but not preserved, except in memory.') }} diff --git a/packages/core/test/files/_patterns/patternType1/patternSubType1.md b/packages/core/test/files/_patterns/patternType1/patternSubType1.md new file mode 100644 index 000000000..c34a320f2 --- /dev/null +++ b/packages/core/test/files/_patterns/patternType1/patternSubType1.md @@ -0,0 +1,5 @@ +--- +title: Colors +--- + +Colors diff --git a/test/files/_patterns/patternType1/patternSubType1/blue.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType1/blue.mustache similarity index 100% rename from test/files/_patterns/patternType1/patternSubType1/blue.mustache rename to packages/core/test/files/_patterns/patternType1/patternSubType1/blue.mustache diff --git a/test/files/_patterns/patternType1/patternSubType1/red.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType1/red.mustache similarity index 100% rename from test/files/_patterns/patternType1/patternSubType1/red.mustache rename to packages/core/test/files/_patterns/patternType1/patternSubType1/red.mustache diff --git a/test/files/_patterns/patternType1/patternSubType1/yellow.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType1/yellow.mustache similarity index 100% rename from test/files/_patterns/patternType1/patternSubType1/yellow.mustache rename to packages/core/test/files/_patterns/patternType1/patternSubType1/yellow.mustache diff --git a/test/files/_patterns/patternType1/patternType2/black.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType2/black.mustache similarity index 100% rename from test/files/_patterns/patternType1/patternType2/black.mustache rename to packages/core/test/files/_patterns/patternType1/patternSubType2/black.mustache diff --git a/test/files/_patterns/patternType1/patternType2/grey.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType2/grey.mustache similarity index 100% rename from test/files/_patterns/patternType1/patternType2/grey.mustache rename to packages/core/test/files/_patterns/patternType1/patternSubType2/grey.mustache diff --git a/test/files/_patterns/patternType1/patternType2/white.mustache b/packages/core/test/files/_patterns/patternType1/patternSubType2/white.mustache similarity index 100% rename from test/files/_patterns/patternType1/patternType2/white.mustache rename to packages/core/test/files/_patterns/patternType1/patternSubType2/white.mustache diff --git a/packages/core/test/files/_react-test-patterns/00-atoms/00-general/HelloWorld.jsx b/packages/core/test/files/_react-test-patterns/00-atoms/00-general/HelloWorld.jsx new file mode 100644 index 000000000..9790e5af0 --- /dev/null +++ b/packages/core/test/files/_react-test-patterns/00-atoms/00-general/HelloWorld.jsx @@ -0,0 +1,5 @@ +import React from 'react'; + +const HelloWorld = () =>
Hello world!
; + +export default HelloWorld; diff --git a/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/08-button.twig b/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/08-button.twig new file mode 100644 index 000000000..cf0041685 --- /dev/null +++ b/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/08-button.twig @@ -0,0 +1,10 @@ + + +Button diff --git a/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/09-image.twig b/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/09-image.twig new file mode 100644 index 000000000..01c4af9f8 --- /dev/null +++ b/packages/core/test/files/_twig-test-patterns/00-atoms/00-general/09-image.twig @@ -0,0 +1,5 @@ + diff --git a/packages/core/test/files/_twig-test-patterns/00-molecules/00-general/00-media-object.twig b/packages/core/test/files/_twig-test-patterns/00-molecules/00-general/00-media-object.twig new file mode 100644 index 000000000..09af05118 --- /dev/null +++ b/packages/core/test/files/_twig-test-patterns/00-molecules/00-general/00-media-object.twig @@ -0,0 +1,32 @@ + + + +{% set foo = "world!" %} + + +
+ {% include "atoms-image" %} + {% include "atoms-button" %} + +
+ + {# Testing if we can also pull in non-twig templates without breaking anything (it works!). Good idea though? Eh... #} + {# GTP: let's not for now. It's messin' up the unit testing. #} + +

Oh, hello {{ foo }}

+
+
diff --git a/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.html b/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.html similarity index 100% rename from test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.html rename to packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.html diff --git a/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.json b/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.json similarity index 100% rename from test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.json rename to packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld-withdata.json diff --git a/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld.html b/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld.html similarity index 100% rename from test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld.html rename to packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/00-helloworld.html diff --git a/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/_00-hidden.html b/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/_00-hidden.html new file mode 100644 index 000000000..b2c93a13e --- /dev/null +++ b/packages/core/test/files/_underscore-test-patterns/00-atoms/00-global/_00-hidden.html @@ -0,0 +1 @@ +I'm the hidden atom diff --git a/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.html b/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.html similarity index 100% rename from test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.html rename to packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.html diff --git a/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json b/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json similarity index 100% rename from test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json rename to packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-call-atom-with-molecule-data.json diff --git a/test/files/_underscore-test-patterns/00-molecules/00-global/00-helloworlds.html b/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-helloworlds.html similarity index 100% rename from test/files/_underscore-test-patterns/00-molecules/00-global/00-helloworlds.html rename to packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-helloworlds.html diff --git a/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.html b/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.html new file mode 100644 index 000000000..fe3beef68 --- /dev/null +++ b/packages/core/test/files/_underscore-test-patterns/00-molecules/00-global/00-hidden-pattern-tester.html @@ -0,0 +1 @@ +Here's the hidden atom: [<%=_.renderNamedPartial('atoms-hidden', obj)%>] diff --git a/test/files/annotations.js b/packages/core/test/files/annotations.js similarity index 91% rename from test/files/annotations.js rename to packages/core/test/files/annotations.js index f1077ed02..6bc8c1432 100644 --- a/test/files/annotations.js +++ b/packages/core/test/files/annotations.js @@ -1,13 +1,13 @@ var comments = { - "comments" : [ + "comments": [ { "el": "header[role=banner]", - "title" : "Masthead", + "title": "Masthead", "comment": "The main header of the site doesn't take up too much screen real estate in order to keep the focus on the core content. It's using a linear CSS gradient instead of a background image to give greater design flexibility and reduce HTTP requests." }, { "el": ".logo", - "title" : "Logo", + "title": "Logo", "comment": "The logo image is an SVG file, which ensures that the logo displays crisply even on high resolution displays. A PNG fallback is provided for browsers that don't support SVG images.

Further reading: Optimizing Web Experiences for High Resolution Screens

" } ] diff --git a/test/files/annotations.md b/packages/core/test/files/annotations.md similarity index 100% rename from test/files/annotations.md rename to packages/core/test/files/annotations.md diff --git a/packages/core/test/files/empty/.gitkeep b/packages/core/test/files/empty/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/test/files/nav.md b/packages/core/test/files/nav.md similarity index 100% rename from test/files/nav.md rename to packages/core/test/files/nav.md diff --git a/packages/core/test/files/partials/general-footer.mustache b/packages/core/test/files/partials/general-footer.mustache new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/test/files/partials/general-header.mustache b/packages/core/test/files/partials/general-header.mustache new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/test/files/partials/patternSection.mustache b/packages/core/test/files/partials/patternSection.mustache new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/test/files/partials/patternSectionSubtype.mustache b/packages/core/test/files/partials/patternSectionSubtype.mustache new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/test/files/viewall.mustache b/packages/core/test/files/viewall.mustache new file mode 100644 index 000000000..e69de29bb diff --git a/packages/core/test/get_tests.js b/packages/core/test/get_tests.js new file mode 100644 index 000000000..5e954182c --- /dev/null +++ b/packages/core/test/get_tests.js @@ -0,0 +1,84 @@ +'use strict'; + +const tap = require('tap'); + +const util = require('./util/test_utils.js'); +const getPartial = require('../src/lib/get'); + +const patterns_dir = './test/files/_patterns'; + +tap.test('getPartial - returns the fuzzy result when no others found', function( + test +) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + patternlab.patterns = []; + + patternlab.patterns.push({ + patternPartial: 'character-han-solo', + subdir: 'character', + fileName: 'han-solo', + verbosePartial: 'character/han-solo', + }); + + //act + var result = getPartial('character-han', patternlab); + + //assert + test.equals(result, patternlab.patterns[0]); + test.end(); +}); + +tap.test('getPartial - returns the verbose result if found', function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + patternlab.patterns = []; + + patternlab.patterns.push( + { + patternPartial: 'molecules-primary-nav-jagged', + subdir: 'molecules', + fileName: 'primary-nav-jagged', + verbosePartial: 'molecules/primary-nav-jagged', + }, + { + patternPartial: 'molecules-primary-nav', + subdir: 'molecules', + fileName: 'molecules-primary-nav', + verbosePartial: 'molecules/primary-nav', + } + ); + + //act + var result = getPartial('molecules/primary-nav', patternlab); + + //assert + test.equals(result, patternlab.patterns[1]); + test.end(); +}); + +tap.test('getPartial - returns the exact key if found', function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + patternlab.patterns = []; + + patternlab.patterns.push( + { + patternPartial: 'molecules-primary-nav-jagged', + subdir: 'molecules', + fileName: 'primary-nav-jagged', + }, + { + patternPartial: 'molecules-primary-nav', + subdir: 'molecules', + fileName: 'molecules-primary-nav', + } + ); + + //act + var result = getPartial('molecules-primary-nav', patternlab); + + //assert + test.equals(result, patternlab.patterns[1]); + test.end(); +}); diff --git a/packages/core/test/index_tests.js b/packages/core/test/index_tests.js new file mode 100644 index 000000000..d2e98895e --- /dev/null +++ b/packages/core/test/index_tests.js @@ -0,0 +1,337 @@ +const tap = require('tap'); +const rewire = require('rewire'); +const _ = require('lodash'); +const fs = require('fs-extra'); +const get = require('../src/lib/get'); +const events = require('../src/lib/events'); + +const util = require('./util/test_utils.js'); +const entry = rewire('../src/index'); +const defaultConfig = require('../patternlab-config.json'); +const testConfig = require('./util/patternlab-config.json'); +const packageInfo = require('./../package'); + +process.env.PATTERNLAB_ENV = 'CI'; + +//set up a global mocks - we don't want to be writing/rendering any files right now + +const copierMock = function() { + return { + copyAndWatch: function() { + return Promise.resolve(); + }, + }; +}; + +const uiBuilderMock = function() { + return { + buildFrontend: function() { + return Promise.resolve(); + }, + }; +}; + +const fsMock = { + outputFileSync: function(path, content) { + /* INTENTIONAL NOOP */ + }, + readJSONSync: function(path, encoding) { + return fs.readJSONSync(path, encoding); + }, + emptyDir: function(path) { + return fs.emptyDir(path); + }, + readFileSync: function(path, encoding) { + return fs.readFileSync(path, encoding); + }, +}; + +const buildPatternsMock = () => { + return Promise.resolve(); +}; + +//set our mocks in place of usual require() +entry.__set__({ + ui_builder: uiBuilderMock, + fs: fsMock, + copier: copierMock, +}); + +tap.test('version - should call patternlab.getVersion', test => { + //arrange + const pl = new entry(testConfig); + + //act + //assert + test.equals(pl.version(), packageInfo.version); + test.end(); +}); + +tap.test( + 'getDefaultConfig - static method should return the default config object', + test => { + const requestedConfig = entry.getDefaultConfig(); + test.type(requestedConfig, 'object'); + test.equals(requestedConfig, defaultConfig); + test.end(); + } +); + +tap.test( + 'getDefaultConfig - instance method should return the default config object', + test => { + //arrange + const pl = new entry(testConfig); + + //act + //assert + const requestedConfig = pl.getDefaultConfig(); + test.type(requestedConfig, 'object'); + test.equals(requestedConfig, defaultConfig); + test.end(); + } +); + +tap.test('patternsonly a promise', test => { + //arrange + const revert = entry.__set__('buildPatterns', buildPatternsMock); + const pl = new entry(testConfig); + + //act + test.resolves(pl.patternsonly({})).then(() => { + revert(); + test.end(); + }); +}); + +tap.test('patternsonly calls buildPatterns', test => { + //arrange + const revert = entry.__set__( + 'buildPatterns', + (cleanPublic, patternlab, data) => { + test.type(cleanPublic, 'boolean'); + test.ok(cleanPublic); + test.type(patternlab, 'object'); + test.type(data, 'object'); + test.equals(data.foo, 'bar'); + return Promise.resolve(); + } + ); + const pl = new entry(testConfig); + + //act + test + .resolves(pl.patternsonly({ cleanPublic: true, data: { foo: 'bar' } })) + .then(() => { + revert(); + test.end(); + }); +}); + +tap.test('serve calls serve', test => { + //arrange + const revert = entry.__set__('serverModule', patternlab => { + return { + serve: () => { + test.ok(1); + test.type(patternlab, 'object'); + }, + reload: () => {}, + }; + }); + + const pl = new entry(testConfig); + + //act + test.resolves(pl.server.serve({})).then(() => { + revert(); + test.end(); + }); +}); + +tap.test('buildPatterns suite', test => { + //arrange + + const patternExporterMock = { + /* + In this suite, we actually take advantage of the pattern export functionality post-build to inspect what + the contents of the patterns look like. This, coupled with a mocking of fs and the ui_builder, allow us to focus + only on the order of events within build. + */ + export_patterns: function(patternlab) { + tap.test( + 'replace data link even when pattern parameter present', + function(test) { + var pattern = get('test-paramParent', patternlab); + test.equals( + util.sanitized(pattern.extendedTemplate), + '', + 'partial inclusion completes' + ); + test.equals( + pattern.patternPartialCode.indexOf('00-test-00-foo.rendered.html') > + -1, + true, + 'data link should be replaced properly' + ); + test.end(); + } + ); + + tap.test( + 'finds partials with their own parameters and renders them too', + function(test) { + var pattern = get('test-c', patternlab); + test.equals( + util.sanitized(pattern.patternPartialCode), + util.sanitized(`c + b + b! + a + a!`) + ); + test.end(); + } + ); + + tap.test( + 'finds and extends templates with mixed parameter and global data', + function(test) { + var pattern = get('test-sticky-comment', patternlab); + test.equals( + util.sanitized(pattern.patternPartialCode), + util.sanitized( + `

Bar

A life is like a garden. Perfect moments can be had, but not preserved, except in memory.

` + ) + ); + test.end(); + } + ); + + tap.test('expands links inside parameters', function(test) { + var pattern = get('test-linkInParameter', patternlab); + test.equals( + util.sanitized(pattern.patternPartialCode), + util.sanitized( + `Cool Dude` + ) + ); + test.end(); + }); + + tap.test('uses global listItem property', test => { + var pattern = get('test-listWithPartial', patternlab); + let assertionCount = 0; + ['dA', 'dB', 'dC'].forEach(d => { + if (pattern.patternPartialCode.indexOf(d) > -1) { + assertionCount++; + } + }); + test.ok(assertionCount === 2); + test.end(); + }); + + tap.test( + 'overwrites listItem property if that property is in local .listitem.json', + test => { + var pattern = get('test-listWithListItems', patternlab); + test.ok(pattern.patternPartialCode.indexOf('tX') > -1); + test.ok(pattern.patternPartialCode.indexOf('tY') > -1); + test.ok(pattern.patternPartialCode.indexOf('tZ') > -1); + + test.end(); + } + ); + + tap.test( + 'uses global listItem property after merging local .listitem.json', + test => { + var pattern = get('test-listWithListItems', patternlab); + test.ok(pattern.patternPartialCode.indexOf('dA') > -1); + test.ok(pattern.patternPartialCode.indexOf('dB') > -1); + test.ok(pattern.patternPartialCode.indexOf('dC') > -1); + test.end(); + } + ); + + tap.test( + 'correctly ignores bookended partials without a style modifier when the same partial has a style modifier between', + test => { + var pattern = get('test-bookend-listitem', patternlab); + test.equals( + util.sanitized(pattern.extendedTemplate), + util.sanitized(`
+ {{#listItems-two}} + + {{message}} + + + + {{message}} + + + + {{message}} + + + {{/listItems-two}} +
+ `) + ); + test.end(); + } + ); + + tap.test( + 'listItems keys (`one` through `twelve`) can be used more than once per pattern', + test => { + var pattern = get('test-repeatedListItems', patternlab); + test.equals( + util.sanitized(pattern.patternPartialCode), + util.sanitized(`AAA BBB`) + ); + test.end(); + } + ); + + /////////////// FAILING /////////////////// + // todo + // From issue #145 https://github.com/pattern-lab/patternlab-node/issues/145 + // tap.test(' parses parameters containing html tags', function (test) { + // var pattern = get('test-parameterTags', patternlab); + // test.equals(util.sanitized(pattern.patternPartialCode), util.sanitized(`

Single-quoted

Double-quoted

With attributes

`)); + // test.end(); + // }); + + process.env.PATTERNLAB_ENV = ''; + }, + }; + + entry.__set__({ + pattern_exporter: patternExporterMock, + }); + + testConfig.patternExportPatternPartials = ['test-paramParent']; + const pl = new entry(testConfig); + + test.equals(pl.events.eventNames().length, 0); + + //act + return pl + .build({ + cleanPublic: true, + data: { + foo: 'Bar', + description: 'Baz', + }, + }) + .then(() => { + test.equals( + pl.events.eventNames().length, + 2, + 'should register two events' + ); + test.equals(pl.events.listenerCount(events.PATTERNLAB_PATTERN_CHANGE), 1); + test.equals(pl.events.listenerCount(events.PATTERNLAB_GLOBAL_CHANGE), 1); + }); +}); diff --git a/packages/core/test/lineage_hunter_tests.js b/packages/core/test/lineage_hunter_tests.js new file mode 100644 index 000000000..b19157a28 --- /dev/null +++ b/packages/core/test/lineage_hunter_tests.js @@ -0,0 +1,694 @@ +'use strict'; + +const tap = require('tap'); +const fs = require('fs-extra'); +const path = require('path'); +const extend = require('util')._extend; + +const lh = require('../src/lib/lineage_hunter'); +const loadPattern = require('../src/lib/loadPattern'); +const of = require('../src/lib/object_factory'); +const Pattern = require('../src/lib/object_factory').Pattern; +const PatternGraph = require('../src/lib/pattern_graph').PatternGraph; +const config = require('./util/patternlab-config.json'); +const addPattern = require('../src/lib/addPattern'); +const getPartial = require('../src/lib/get'); + +const engineLoader = require('../src/lib/pattern_engines'); +engineLoader.loadAllEngines(config); + +const lineage_hunter = new lh(); + +// fake pattern creators +function createFakeEmptyErrorPattern() { + return new Pattern( + '01-molecules/01-toast/00-error.mustache', // relative path now + null // data + ); +} + +function createBasePatternLabObject() { + var patterns_dir = `${__dirname}/files/_patterns/`; + var pl = {}; + (pl.graph = PatternGraph.empty()), + (pl.config = { + paths: { + source: { + patterns: patterns_dir, + }, + public: { + patterns: `${__dirname}/public/_patterns`, + }, + }, + outputFileSuffixes: { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }, + patternStateCascade: ['inprogress', 'inreview', 'complete'], + }); + pl.data = {}; + pl.data.link = {}; + pl.config.logLevel = 'quiet'; + pl.patterns = []; + pl.partials = {}; + pl.patternGroups = {}; + pl.subtypePatterns = {}; + + return pl; +} + +tap.test('find_lineage - finds lineage', function(test) { + //setup current pattern from what we would have during execution + var currentPattern = new Pattern( + '02-organisms/00-global/00-header.mustache', // relative path now + null // data + ); + extend(currentPattern, { + template: + '\r\n\r\n\r\n', + patternPartialCode: + '\r\n\r\n\r\n', + }); + + var patternlab = { + graph: new PatternGraph(null, 0), + patterns: [ + Pattern.createEmpty({ + name: '00-atoms-03-images-00-logo', + subdir: '00-atoms\\03-images', + filename: '00-logo.mustache', + data: null, + template: + '', + patternPartialCode: + '', + patternBaseName: 'logo', + patternLink: + '00-atoms-03-images-00-logo/00-atoms-03-images-00-logo.html', + patternGroup: 'atoms', + patternSubGroup: 'atoms\\03-images', + flatPatternPath: '00-atoms\\03-images', + patternPartial: 'atoms-logo', + patternState: '', + lineage: [], + lineageIndex: [], + lineageR: [], + lineageRIndex: [], + }), + Pattern.createEmpty({ + name: '01-molecules-05-navigation-00-primary-nav', + subdir: '01-molecules\\05-navigation', + filename: '00-primary-nav.mustache', + data: null, + template: + '\r\n', + patternPartialCode: + '\r\n', + patternBaseName: 'primary-nav', + patternLink: + '01-molecules-05-navigation-00-primary-nav/01-molecules-05-navigation-00-primary-nav.html', + patternGroup: 'molecules', + patternSubGroup: 'molecules\\05-navigation', + flatPatternPath: '01-molecules\\05-navigation', + patternPartial: 'molecules-primary-nav', + patternState: '', + lineage: [], + lineageIndex: [], + lineageR: [], + lineageRIndex: [], + }), + Pattern.createEmpty({ + name: '01-molecules-04-forms-00-search', + subdir: '01-molecules\\04-forms', + filename: '00-search.mustache', + data: null, + template: + '
\r\n
\r\n\t Search\r\n\t \r\n\t \r\n\t \r\n
\r\n
', + patternPartialCode: + '
\r\n
\r\n\t Search\r\n\t \r\n\t \r\n\t \r\n
\r\n
', + patternBaseName: 'search', + patternLink: + '01-molecules-04-forms-00-search/01-molecules-04-forms-00-search.html', + patternGroup: 'molecules', + patternSubGroup: 'molecules\\04-forms', + flatPatternPath: '01-molecules\\04-forms', + patternPartial: 'molecules-search', + patternState: '', + lineage: [], + lineageIndex: [], + lineageR: [], + lineageRIndex: [], + }), + ], + config: { + outputFileSuffixes: { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }, + }, + }; + // BAD: This "patches" the relative path which is unset when using "createEmpty" + patternlab.patterns.forEach(p => (p.relPath = p.patternLink)); + + lineage_hunter.find_lineage(currentPattern, patternlab); + + var graphLineageIndex = patternlab.graph.lineageIndex(currentPattern); + + // Ensure compatibility + for (let i of [currentPattern.lineageIndex, graphLineageIndex]) { + test.equals(i.length, 3); + test.equals(i[0], 'atoms-logo'); + test.equals(i[1], 'molecules-primary-nav'); + test.equals(i[2], 'molecules-search'); + } + + test.end(); +}); + +tap.test( + 'find_lineage - finds lineage with spaced pattern parameters', + function(test) { + //setup current pattern from what we would have during execution + var currentPattern = createFakeEmptyErrorPattern(); + extend(currentPattern, { + template: "{{> atoms-error(message: 'That\\'s no moon...') }}", + extendedTemplate: "{{> atoms-error(message: 'That\\'s no moon...') }}", + }); + + var patternlab = { + graph: new PatternGraph(null, 0), + patterns: [ + Pattern.create('00-atoms/05-alerts/00-error.mustache', null, { + template: '

{{message}}

', + extendedTemplate: '

{{message}}

', + }), + ], + config: { + outputFileSuffixes: { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }, + }, + }; + + lineage_hunter.find_lineage(currentPattern, patternlab); + + test.equals(currentPattern.lineageIndex.length, 1); + test.equals(currentPattern.lineageIndex[0], 'atoms-error'); + test.equals(patternlab.patterns[0].lineageRIndex.length, 1); + test.equals( + patternlab.patterns[0].lineageR[0].lineagePattern, + 'molecules-error' + ); + + // Same as above, but as graph based variant + var graph = patternlab.graph; + // Test if there is an edge from molecule-toast-error to atoms-alerts-error + test.same( + graph.hasLink(currentPattern, patternlab.patterns[0]), + true, + 'There is an edge from the test-error molecule to the alerts-error atom' + ); + var currentPatternLineageIndex = graph.lineageIndex(currentPattern); + + test.equals(currentPatternLineageIndex.length, 1); + test.equals(currentPatternLineageIndex[0], 'atoms-error'); + + var patternlabPattern0_lineageRIndex = graph.lineageRIndex( + patternlab.patterns[0] + ); + test.equals(patternlabPattern0_lineageRIndex.length, 1); + test.equals(patternlabPattern0_lineageRIndex[0], 'molecules-error'); + + test.end(); + } +); + +tap.test( + 'cascade_pattern_states promotes a lower pattern state up to the consumer', + function(test) { + //arrange + var pl = createBasePatternLabObject(); + + var atomPattern = new of.Pattern('00-test/01-bar.mustache'); + atomPattern.template = fs.readFileSync( + pl.config.paths.source.patterns + '00-test/01-bar.mustache', + 'utf8' + ); + atomPattern.extendedTemplate = atomPattern.template; + atomPattern.patternState = 'inreview'; + + addPattern(atomPattern, pl); + + var consumerPattern = new of.Pattern('00-test/00-foo.mustache'); + consumerPattern.template = fs.readFileSync( + pl.config.paths.source.patterns + '00-test/00-foo.mustache', + 'utf8' + ); + consumerPattern.extendedTemplate = consumerPattern.template; + consumerPattern.patternState = 'complete'; + addPattern(consumerPattern, pl); + + lineage_hunter.find_lineage(consumerPattern, pl); + + //act + lineage_hunter.cascade_pattern_states(pl); + + //assert + var consumerPatternReturned = getPartial('test-foo', pl); + test.equals(consumerPatternReturned.patternState, 'inreview'); + test.end(); + } +); + +tap.test( + 'cascade_pattern_states promotes a lower pattern state up to the consumers lineage', + function(test) { + //arrange + var pl = createBasePatternLabObject(); + + var atomPattern = new of.Pattern('00-test/01-bar.mustache'); + atomPattern.template = fs.readFileSync( + pl.config.paths.source.patterns + '00-test/01-bar.mustache', + 'utf8' + ); + atomPattern.extendedTemplate = atomPattern.template; + atomPattern.patternState = 'inreview'; + + addPattern(atomPattern, pl); + + var consumerPattern = new of.Pattern('00-test/00-foo.mustache'); + consumerPattern.template = fs.readFileSync( + pl.config.paths.source.patterns + '00-test/00-foo.mustache', + 'utf8' + ); + consumerPattern.extendedTemplate = consumerPattern.template; + consumerPattern.patternState = 'complete'; + addPattern(consumerPattern, pl); + + lineage_hunter.find_lineage(consumerPattern, pl); + + //act + lineage_hunter.cascade_pattern_states(pl); + + //assert + var consumerPatternReturned = getPartial('test-foo', pl); + const lineage = pl.graph.lineage(consumerPatternReturned); + test.equals(lineage[0].lineageState, 'inreview'); + test.end(); + } +); + +tap.test( + 'cascade_pattern_states sets the pattern state on any lineage patterns reverse lineage', + function(test) { + //arrange + var pl = createBasePatternLabObject(); + + var atomPattern = loadPattern('00-test/01-bar.mustache', pl); + var consumerPattern = loadPattern('00-test/00-foo.mustache', pl); + + lineage_hunter.find_lineage(consumerPattern, pl); + + //act + lineage_hunter.cascade_pattern_states(pl); + + //assert + var consumedPatternReturned = getPartial('test-bar', pl); + let lineageR = pl.graph.lineageR(consumedPatternReturned); + test.equals(lineageR[0].lineageState, 'inreview'); + + test.end(); + } +); + +tap.test( + 'cascade_pattern_states promotes lower pattern state when consumer does not have its own state', + function(test) { + //arrange + var pl = createBasePatternLabObject(); + + var atomPattern = new of.Pattern('00-test/01-bar.mustache'); + atomPattern.template = fs.readFileSync( + path.resolve(pl.config.paths.source.patterns, '00-test/01-bar.mustache'), + 'utf8' + ); + atomPattern.extendedTemplate = atomPattern.template; + atomPattern.patternState = 'inreview'; + + addPattern(atomPattern, pl); + + var consumerPattern = new of.Pattern('00-test/00-foo.mustache'); + consumerPattern.template = fs.readFileSync( + path.resolve(pl.config.paths.source.patterns, '00-test/00-foo.mustache'), + 'utf8' + ); + consumerPattern.extendedTemplate = consumerPattern.template; + addPattern(consumerPattern, pl); + + lineage_hunter.find_lineage(consumerPattern, pl); + + //act + lineage_hunter.cascade_pattern_states(pl); + + //assert + var consumerPatternReturned = getPartial('test-foo', pl); + test.equals(consumerPatternReturned.lineage.length, 1); + test.equals(consumerPatternReturned.lineage[0].lineageState, 'inreview'); + test.equals(consumerPatternReturned.patternState, 'inreview'); + test.end(); + } +); + +tap.test( + 'find_lineage - finds lineage with unspaced pattern parameters', + function(test) { + //setup current pattern from what we would have during execution + var currentPattern = createFakeEmptyErrorPattern(); + extend(currentPattern, { + template: "{{>atoms-error(message: 'That\\'s no moon...')}}", + extendedTemplate: "{{>atoms-error(message: 'That\\'s no moon...')}}", + }); + + var patternlab = { + graph: PatternGraph.empty(), + patterns: [ + Pattern.createEmpty({ + name: '01-atoms-05-alerts-00-error', + subdir: '01-atoms\\05-alerts', + filename: '00-error.mustache', + data: null, + template: '

{{message}}

', + extendedTemplate: '

{{message}}

', + patternBaseName: 'error', + patternLink: + '01-atoms-05-alerts-00-error/01-atoms-05-alerts-00-error.html', + patternGroup: 'atoms', + patternSubGroup: 'atoms\\05-alerts', + flatPatternPath: '01-atoms\\05-alerts', + patternPartial: 'atoms-error', + patternState: '', + lineage: [], + currentPatternLineageIndex: [], + lineageR: [], + lineageRIndex: [], + }), + ], + config: { + outputFileSuffixes: { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }, + }, + }; + + lineage_hunter.find_lineage(currentPattern, patternlab); + + test.equals(currentPattern.lineageIndex.length, 1); + test.equals(currentPattern.lineageIndex[0], 'atoms-error'); + test.equals(patternlab.patterns[0].lineageRIndex.length, 1); + test.equals( + patternlab.patterns[0].lineageR[0].lineagePattern, + 'molecules-error' + ); + + var currentPatternLineageIndex = patternlab.graph.lineageIndex( + currentPattern + ); + test.equals(currentPatternLineageIndex.length, 1); + test.equals(currentPatternLineageIndex[0], 'atoms-error'); + + var pattern0LineageRIndex = patternlab.graph.lineageRIndex( + patternlab.patterns[0] + ); + test.equals(pattern0LineageRIndex.length, 1); + test.equals(pattern0LineageRIndex[0], 'molecules-error'); + + test.end(); + } +); + +tap.test('find_lineage - finds lineage with spaced styleModifier', function( + test +) { + //setup current pattern from what we would have during execution + var currentPattern = Pattern.createEmpty({ + name: '01-molecules-01-toast-00-error', + subdir: '01-molecules\\01-toast', + filename: '00-error.mustache', + data: null, + template: '{{> atoms-error:foo }}', + extendedTemplate: '{{> atoms-error:foo }}', + patternBaseName: 'error', + patternLink: + '01-molecules-01-toast-00-error/01-molecules-01-toast-00-error.html', + patternGroup: 'molecules', + patternSubGroup: 'molecules\\01-toast', + flatPatternPath: '01-molecules\\01-toast', + patternPartial: 'molecules-error', + patternState: '', + lineage: [], + lineageIndex: [], + lineageR: [], + lineageRIndex: [], + }); + var patternlab = { + graph: new PatternGraph(null, 0), + patterns: [ + Pattern.createEmpty({ + name: '01-atoms-05-alerts-00-error', + subdir: '01-atoms\\05-alerts', + filename: '00-error.mustache', + data: null, + template: '

{{message}}

', + extendedTemplate: '

{{message}}

', + patternBaseName: 'error', + patternLink: + '01-atoms-05-alerts-00-error/01-atoms-05-alerts-00-error.html', + patternGroup: 'atoms', + patternSubGroup: 'atoms\\05-alerts', + flatPatternPath: '01-atoms\\05-alerts', + patternPartial: 'atoms-error', + patternState: '', + lineage: [], + lineageIndex: [], + lineageR: [], + lineageRIndex: [], + }), + ], + config: { + outputFileSuffixes: { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }, + }, + }; + + lineage_hunter.find_lineage(currentPattern, patternlab); + + test.equals(currentPattern.lineageIndex.length, 1); + test.equals(currentPattern.lineageIndex[0], 'atoms-error'); + + test.end(); +}); + +tap.test('find_lineage - finds lineage with unspaced styleModifier', function( + test +) { + //setup current pattern from what we would have during execution + var currentPattern = Pattern.createEmpty({ + name: '01-molecules-01-toast-00-error', + subdir: '01-molecules\\01-toast', + filename: '00-error.mustache', + data: null, + template: '{{> atoms-error:foo }}', + extendedTemplate: '{{>atoms-error:foo}}', + patternBaseName: 'error', + patternLink: + '01-molecules-01-toast-00-error/01-molecules-01-toast-00-error.html', + patternGroup: 'molecules', + patternSubGroup: 'molecules\\01-toast', + flatPatternPath: '01-molecules\\01-toast', + patternPartial: 'molecules-error', + patternState: '', + lineage: [], + lineageIndex: [], + lineageR: [], + lineageRIndex: [], + }); + var patternlab = { + graph: PatternGraph.empty(), + patterns: [ + Pattern.createEmpty({ + name: '01-atoms-05-alerts-00-error', + subdir: '01-atoms\\05-alerts', + filename: '00-error.mustache', + data: null, + template: '

{{message}}

', + extendedTemlpate: '

{{message}}

', + patternBaseName: 'error', + patternLink: + '01-atoms-05-alerts-00-error/01-atoms-05-alerts-00-error.html', + patternGroup: 'atoms', + patternSubGroup: 'atoms\\05-alerts', + flatPatternPath: '01-atoms\\05-alerts', + patternPartial: 'atoms-error', + patternState: '', + lineage: [], + lineageIndex: [], + lineageR: [], + lineageRIndex: [], + }), + ], + config: { + outputFileSuffixes: { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }, + }, + }; + + lineage_hunter.find_lineage(currentPattern, patternlab); + + test.equals(currentPattern.lineageIndex.length, 1); + test.equals(currentPattern.lineageIndex[0], 'atoms-error'); + + test.end(); +}); + +tap.test( + 'find_lineage - finds lineage with fuzzy partial with styleModifier', + function(test) { + //setup current pattern from what we would have during execution + var currentPattern = Pattern.createEmpty({ + name: '01-molecules-01-toast-00-error', + subdir: '01-molecules\\01-toast', + filename: '00-error.mustache', + data: null, + template: '{{> atoms-e:foo }}', + extendedTemplate: '{{>atoms-e:foo}}', + patternBaseName: 'error', + patternLink: + '01-molecules-01-toast-00-error/01-molecules-01-toast-00-error.html', + patternGroup: 'molecules', + patternSubGroup: 'molecules\\01-toast', + flatPatternPath: '01-molecules\\01-toast', + patternPartial: 'molecules-error', + patternState: '', + lineage: [], + lineageIndex: [], + lineageR: [], + lineageRIndex: [], + }); + var patternlab = { + graph: PatternGraph.empty(), + patterns: [ + Pattern.createEmpty({ + name: '01-atoms-05-alerts-00-error', + subdir: '01-atoms\\05-alerts', + filename: '00-error.mustache', + data: null, + template: '

{{message}}

', + extendedTemplate: '

{{message}}

', + patternBaseName: 'error', + patternLink: + '01-atoms-05-alerts-00-error/01-atoms-05-alerts-00-error.html', + patternGroup: 'atoms', + patternSubGroup: 'atoms\\05-alerts', + flatPatternPath: '01-atoms\\05-alerts', + patternPartial: 'atoms-error', + patternState: '', + lineage: [], + lineageIndex: [], + lineageR: [], + lineageRIndex: [], + }), + ], + config: { + outputFileSuffixes: { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }, + }, + }; + + var lineage_hunter = new lh(); + lineage_hunter.find_lineage(currentPattern, patternlab); + + test.equals(currentPattern.lineageIndex.length, 1); + test.equals(currentPattern.lineageIndex[0], 'atoms-error'); + + test.end(); + } +); + +tap.test('find_lineage - does not apply lineage twice', function(test) { + //setup current pattern from what we would have during execution + var currentPattern = createFakeEmptyErrorPattern(); + extend(currentPattern, { + template: "{{>atoms-error(message: 'That\\'s no moon...')}}", + extendedTemplate: "{{>atoms-error(message: 'That\\'s no moon...')}}", + }); + var patternlab = { + graph: PatternGraph.empty(), + patterns: [ + Pattern.createEmpty({ + name: '01-atoms-05-alerts-00-error', + subdir: '01-atoms\\05-alerts', + filename: '00-error.mustache', + data: null, + template: '

{{message}}

', + extendedTemplate: '

{{message}}

', + patternBaseName: 'error', + patternLink: + '01-atoms-05-alerts-00-error/01-atoms-05-alerts-00-error.html', + patternGroup: 'atoms', + patternSubGroup: 'atoms\\05-alerts', + flatPatternPath: '01-atoms\\05-alerts', + patternPartial: 'atoms-error', + patternState: '', + lineage: [], + lineageIndex: [], + lineageR: [], + lineageRIndex: [], + }), + ], + config: { + outputFileSuffixes: { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }, + }, + }; + + var lineage_hunter = new lh(); + lineage_hunter.find_lineage(currentPattern, patternlab); + lineage_hunter.find_lineage(currentPattern, patternlab); + + test.equals(currentPattern.lineageIndex.length, 1); + test.equals(currentPattern.lineageIndex[0], 'atoms-error'); + test.equals(patternlab.patterns[0].lineageRIndex.length, 1); + test.equals( + patternlab.patterns[0].lineageR[0].lineagePattern, + 'molecules-error' + ); + + var graph = patternlab.graph; + + var currentPatternLineageIndex = graph.lineageIndex(currentPattern); + test.equals(currentPatternLineageIndex.length, 1); + test.equals(currentPatternLineageIndex[0], 'atoms-error'); + var patternZeroLineageR = graph.lineageR(patternlab.patterns[0]); + test.equals(patternZeroLineageR.length, 1); + test.equals(patternZeroLineageR[0].patternPartial, 'molecules-error'); + + test.end(); +}); diff --git a/packages/core/test/list_item_hunter_tests.js b/packages/core/test/list_item_hunter_tests.js new file mode 100644 index 000000000..278de7dcc --- /dev/null +++ b/packages/core/test/list_item_hunter_tests.js @@ -0,0 +1,69 @@ +'use strict'; + +const tap = require('tap'); +const path = require('path'); + +const lih = require('../src/lib/list_item_hunter'); +const list_item_hunter = new lih(); +const util = require('./util/test_utils.js'); +const loadPattern = require('../src/lib/loadPattern'); + +const testPatternsPath = path.resolve(__dirname, 'files', '_patterns'); + +const config = require('./util/patternlab-config.json'); +const engineLoader = require('../src/lib/pattern_engines'); +engineLoader.loadAllEngines(config); + +tap.test( + 'process_list_item_partials converts partial to simpler format', + test => { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + const listPath = path.join('00-test', '685-list.mustache'); + const testPattern = loadPattern(listPath, pl); + + //usually decompose does this + testPattern.extendedTemplate = testPattern.template; + + //act + list_item_hunter.process_list_item_partials(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized(` + {{#listItems-three}} + {{title}} + {{/listItems-three}} + `) + ); + test.end(); + }); + } +); + +tap.test( + 'process_list_item_partials converts partial with includes to simpler format', + test => { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + const listPath = path.join('00-test', 'listWithPartial.mustache'); + const testPattern = loadPattern(listPath, pl); + + //usually decompose does this + testPattern.extendedTemplate = testPattern.template; + + //act + list_item_hunter.process_list_item_partials(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized(` + {{#listItems-two}} + {{> test-comment }} + {{/listItems-two}} + `) + ); + test.end(); + }); + } +); diff --git a/packages/core/test/loadPattern_tests.js b/packages/core/test/loadPattern_tests.js new file mode 100644 index 000000000..2ebec760f --- /dev/null +++ b/packages/core/test/loadPattern_tests.js @@ -0,0 +1,109 @@ +'use strict'; + +const path = require('path'); +const tap = require('tap'); + +const loadPattern = require('../src/lib/loadPattern'); +const util = require('./util/test_utils.js'); +const patternEngines = require('../src/lib/pattern_engines'); +var config = require('./util/patternlab-config.json'); + +patternEngines.loadAllEngines(config); + +const patterns_dir = `${__dirname}/files/_patterns`; + +tap.test('loadPattern - returns null if file is not a pattern', function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + var patternPath = path.join('00-test', '03-styled-atom.json'); + + //act + var result = loadPattern(patternPath, patternlab); + + //assert + test.equals(result, null); + test.end(); +}); + +tap.test('loadPattern - loads pattern sibling json if found', function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + var patternPath = path.join('00-test', '03-styled-atom.mustache'); + + //act + var result = loadPattern(patternPath, patternlab); + + //assert + test.equals(result.jsonFileData.message, 'baseMessage'); + test.end(); +}); + +tap.test( + 'loadPattern - adds the pattern to the patternlab.partials object', + function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + var fooPatternPath = path.join('00-test', '01-bar.mustache'); + + //act + var result = loadPattern(fooPatternPath, patternlab); + + //assert + test.equals(util.sanitized(patternlab.partials['test-bar']), 'bar'); + test.end(); + } +); + +tap.test('loadPattern - returns pattern with template populated', function( + test +) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + var fooPatternPath = path.join('00-test', '01-bar.mustache'); + + //act + var result = loadPattern(fooPatternPath, patternlab); + + //assert + test.equals(util.sanitized(result.template), util.sanitized('bar')); + test.end(); +}); + +tap.test('loadPattern - adds a markdown pattern if encountered', function( + test +) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + var colorsMarkDownPath = path.join('patternType1', 'patternSubType1.md'); + + //act + var result = loadPattern(colorsMarkDownPath, patternlab); + + //assert + const subTypePattern = + patternlab.subtypePatterns['patternType1-patternSubType1']; + test.equals(subTypePattern.patternSectionSubtype, true); + test.equals(subTypePattern.isPattern, false); + test.equals(subTypePattern.patternDesc, '

Colors

\n'); + test.equals(subTypePattern.engine, null); + test.equals(subTypePattern.flatPatternPath, 'patternType1-patternSubType1'); + test.equals(result, subTypePattern); + test.end(); +}); + +tap.test( + 'loadPattern - does not load pseudopattern data on the base pattern', + test => { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + const basePatternPath = path.join('00-test', '474-pseudomodifier.mustache'); + + //act + const result = loadPattern(basePatternPath, patternlab); + + //assert + test.same(result.jsonFileData, {}); + + test.end(); + } +); diff --git a/packages/core/test/loaduitkits_tests.js b/packages/core/test/loaduitkits_tests.js new file mode 100644 index 000000000..23bdcd656 --- /dev/null +++ b/packages/core/test/loaduitkits_tests.js @@ -0,0 +1,150 @@ +'use strict'; + +const tap = require('tap'); +const rewire = require('rewire'); + +const logger = require('../src/lib/log'); +const loaduikits = rewire('../src/lib/loaduikits'); + +const testConfig = require('./util/patternlab-config.json'); + +const findModulesMock = function() { + return [ + { + name: 'foo', + modulePath: 'node_modules/@pattern-lab/uikit-foo', + }, + { + name: 'bar', + modulePath: 'node_modules/@pattern-lab/uikit-bar', + }, + { + name: 'baz', + modulePath: 'node_modules/@pattern-lab/uikit-baz', + }, + { + name: 'polyfills', + modulePath: 'node_modules/@pattern-lab/uikit-polyfills', + }, + ]; +}; + +const fsMock = { + readFileSync: function(path, encoding) { + return 'file'; + }, +}; + +loaduikits.__set__({ + findModules: findModulesMock, + fs: fsMock, +}); + +logger; + +tap.test('loaduitkits - does not warn on uikit-polyfills', test => { + //arrange + const patternlab = { + config: testConfig, + uikits: {}, + }; + + patternlab.config.logLevel = 'warning'; + logger.log.on('warning', msg => test.notOk(msg.includes('uikit-polyfills'))); + + const uikitFoo = { + name: 'uikit-foo', + enabled: true, + outputDir: 'foo', + excludedPatternStates: ['legacy'], + excludedTags: ['baz'], + }; + + patternlab.config.uikits = [uikitFoo]; + + //act + loaduikits(patternlab).then(() => { + logger.warning = () => {}; + test.done(); + }); +}); + +tap.test('loaduikits - maps fields correctly', function(test) { + //arrange + const patternlab = { + config: testConfig, + uikits: {}, + }; + + const uikitFoo = { + name: 'uikit-foo', + enabled: true, + outputDir: 'foo', + excludedPatternStates: ['legacy'], + excludedTags: ['baz'], + }; + + patternlab.config.uikits = [uikitFoo]; + + //act + loaduikits(patternlab).then(() => { + //assert + test.equals(patternlab.uikits['uikit-foo'].name, uikitFoo.name); + test.equals( + patternlab.uikits['uikit-foo'].modulePath, + 'node_modules/@pattern-lab/uikit-foo' + ); + test.ok(patternlab.uikits['uikit-foo'].enabled); + test.equals(patternlab.uikits['uikit-foo'].outputDir, uikitFoo.outputDir); + test.equals( + patternlab.uikits['uikit-foo'].excludedPatternStates, + uikitFoo.excludedPatternStates + ); + test.equals( + patternlab.uikits['uikit-foo'].excludedTags, + uikitFoo.excludedTags + ); + test.end(); + }); +}); + +tap.test('loaduikits - only adds files for enabled uikits', function(test) { + //arrange + const patternlab = { + config: testConfig, + uikits: {}, + }; + + patternlab.config.uikits = [ + { + name: 'uikit-foo', + enabled: true, + outputDir: 'foo', + excludedPatternStates: ['legacy'], + excludedTags: ['baz'], + }, + { + name: 'uikit-bar', + enabled: true, + outputDir: 'bar', + excludedPatternStates: ['development'], + excludedTags: ['baz', 'foo'], + }, + { + name: 'uikit-baz', + enabled: false, + outputDir: 'baz', + excludedPatternStates: [''], + excludedTags: [], + }, + ]; + + //act + loaduikits(patternlab).then(() => { + //assert + test.ok(patternlab.uikits['uikit-foo']); + test.ok(patternlab.uikits['uikit-bar']); + test.notOk(patternlab.uikits['uikit-baz']); + test.end(); + }); +}); diff --git a/packages/core/test/markModifiedPatterns_tests.js b/packages/core/test/markModifiedPatterns_tests.js new file mode 100644 index 000000000..173e0283f --- /dev/null +++ b/packages/core/test/markModifiedPatterns_tests.js @@ -0,0 +1,119 @@ +'use strict'; + +var tap = require('tap'); +var rewire = require('rewire'); + +var Pattern = require('../src/lib/object_factory').Pattern; +var CompileState = require('../src/lib/object_factory').CompileState; +var PatternGraph = require('../src/lib/pattern_graph').PatternGraph; +var engineLoader = require('../src/lib/pattern_engines'); + +const markModifiedPatterns = rewire('../src/lib/markModifiedPatterns'); + +const config = require('./util/patternlab-config.json'); + +const fsMock = { + readFileSync: function(path, encoding, cb) { + return ''; + }, +}; + +function emptyPatternLab() { + return { + graph: PatternGraph.empty(), + }; +} + +const public_dir = './test/public'; + +tap.only( + 'markModifiedPatterns - finds patterns modified since a given date', + function(test) { + //arrange + markModifiedPatterns.__set__('fs', fsMock); + + var patternlab = emptyPatternLab(); + patternlab.config = config; + patternlab.config.paths.public.patterns = public_dir + '/patterns'; + patternlab.config.outputFileSuffixes = { + rendered: '', + markupOnly: '.markup-only', + }; + + var pattern = new Pattern('00-test/01-bar.mustache'); + + pattern.extendedTemplate = undefined; + pattern.template = 'bar'; + pattern.lastModified = new Date('2016-01-31').getTime(); + + // Initially the compileState is clean, + // but we would change this after detecting that the file was modified + pattern.compileState = CompileState.CLEAN; + patternlab.patterns = [pattern]; + + var lastCompilationRun = new Date('2016-01-01').getTime(); + var modifiedOrNot = markModifiedPatterns(lastCompilationRun, patternlab); + + test.same( + modifiedOrNot.modified.length, + 1, + 'The pattern was modified after the last compilation' + ); + + // Reset the compile state as it was previously set by pattern_assembler.mark_modified_patterns + pattern.compileState = CompileState.CLEAN; + lastCompilationRun = new Date('2016-12-31').getTime(); + modifiedOrNot = markModifiedPatterns(lastCompilationRun, patternlab); + test.same( + modifiedOrNot.notModified.length, + 1, + "Pattern was already compiled and hasn't been modified since last compile" + ); + test.end(); + } +); + +tap.test( + 'markModifiedPatterns - finds patterns when modification date is missing', + function(test) { + //arrange + var patternlab = emptyPatternLab(); + patternlab.partials = {}; + patternlab.data = { link: {} }; + patternlab.config = { logLevel: 'quiet' }; + patternlab.config.outputFileSuffixes = { rendered: '' }; + + var pattern = new Pattern('00-test/01-bar.mustache'); + pattern.extendedTemplate = undefined; + pattern.template = 'bar'; + pattern.lastModified = undefined; + patternlab.patterns = [pattern]; + + let p = markModifiedPatterns(1000, patternlab); + test.same(p.modified.length, 1); + test.end(); + } +); + +// This is the case when we want to force recompilation +tap.test('markModifiedPatterns - finds patterns via compile state', function( + test +) { + //arrange + var patternlab = emptyPatternLab(); + patternlab.partials = {}; + patternlab.data = { link: {} }; + patternlab.config = { logLevel: 'quiet' }; + patternlab.config.outputFileSuffixes = { rendered: '' }; + + var pattern = new Pattern('00-test/01-bar.mustache'); + pattern.extendedTemplate = undefined; + pattern.template = 'bar'; + pattern.lastModified = 100000; + pattern.compileState = CompileState.NEEDS_REBUILD; + patternlab.patterns = [pattern]; + + let p = markModifiedPatterns(1000, patternlab); + test.same(p.modified.length, 1); + test.end(); +}); diff --git a/packages/core/test/markdown_parser_tests.js b/packages/core/test/markdown_parser_tests.js new file mode 100644 index 000000000..3e25a5934 --- /dev/null +++ b/packages/core/test/markdown_parser_tests.js @@ -0,0 +1,64 @@ +'use strict'; + +var tap = require('tap'); + +var path = require('path'); +var fs = require('fs-extra'); +var mp = require('../src/lib/markdown_parser'); +var markdown_parser = new mp(); + +tap.test( + 'parses pattern description block correctly when frontmatter not present', + function(test) { + //arrange + var markdownFileName = path.resolve( + `${__dirname}/files/_patterns/00-test/02-baz.md` + ); + var markdownFileContents = fs.readFileSync(markdownFileName, 'utf8'); + + //act + var returnObject = markdown_parser.parse(markdownFileContents); + + //assert + test.equals(returnObject.markdown, '

Only baz

\n'); + test.end(); + } +); + +tap.test( + 'parses pattern description block correctly when frontmatter present', + function(test) { + //arrange + var markdownFileName = path.resolve( + `${__dirname}/files/_patterns/00-test/01-bar.md` + ); + var markdownFileContents = fs.readFileSync(markdownFileName, 'utf8'); + + //act + var returnObject = markdown_parser.parse(markdownFileContents); + + //assert + test.equals( + returnObject.markdown, + '

A Simple Bit of Markup

\n

Foo cannot get simpler than bar, amiright?

\n' + ); + test.equals(returnObject.state, 'complete'); + test.end(); + } +); + +tap.test('parses frontmatter only when no markdown present', function(test) { + //arrange + var markdownFileName = path.resolve( + `${__dirname}/files/_patterns/00-test/03-styled-atom.md` + ); + var markdownFileContents = fs.readFileSync(markdownFileName, 'utf8'); + + //act + var returnObject = markdown_parser.parse(markdownFileContents); + + //assert + test.equals(returnObject.markdown, ''); + test.equals(returnObject.state, 'inprogress'); + test.end(); +}); diff --git a/packages/core/test/object_factory_tests.js b/packages/core/test/object_factory_tests.js new file mode 100644 index 000000000..910c02d89 --- /dev/null +++ b/packages/core/test/object_factory_tests.js @@ -0,0 +1,262 @@ +'use strict'; + +var tap = require('tap'); +var config = require('./util/patternlab-config.json'); + +// fake pattern lab constructor: +// sets up a fake patternlab object, which is needed by the pattern processing +// apparatus. +function fakePatternLab() { + var fpl = { + partials: {}, + patterns: [], + footer: '', + header: '', + listitems: {}, + data: { + link: {}, + }, + config: config, + package: {}, + }; + + return fpl; +} + +var of = require('../src/lib/object_factory'); +var Pattern = require('../src/lib/object_factory').Pattern; +var path = require('path'); +var pl = fakePatternLab(); +var engineLoader = require('../src/lib/pattern_engines'); +engineLoader.loadAllEngines(config); + +tap.test('test Pattern initializes correctly', function(test) { + var p = new Pattern('00-atoms/00-global/00-colors.mustache', { d: 123 }); + test.equals( + p.relPath, + '00-atoms' + path.sep + '00-global' + path.sep + '00-colors.mustache' + ); + test.equals(p.name, '00-atoms-00-global-00-colors'); + test.equals(p.subdir, '00-atoms' + path.sep + '00-global'); + test.equals(p.fileName, '00-colors'); + test.equals(p.fileExtension, '.mustache'); + test.equals(p.jsonFileData.d, 123); + test.equals(p.patternBaseName, 'colors'); + test.equals(p.patternName, 'Colors'); + test.equals( + p.getPatternLink(pl), + '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'); + test.equals(p.flatPatternPath, '00-atoms-00-global'); + test.equals(p.patternPartial, 'atoms-colors'); + test.equals(p.template, ''); + test.equals(p.patternPartialCode, ''); + 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.equals(p.patternState, ''); + test.end(); +}); + +tap.test( + 'test Pattern initializes correctly with pattern in sepatated directory', + function(test) { + var p = new Pattern('00-atoms/00-global/00-colors/colors.mustache', { + d: 123, + }); + test.equals( + p.relPath, + '00-atoms' + + path.sep + + '00-global' + + path.sep + + '00-colors' + + 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.fileName, 'colors'); + test.equals(p.fileExtension, '.mustache'); + test.equals(p.jsonFileData.d, 123); + test.equals(p.patternBaseName, 'colors'); + test.equals(p.patternName, 'Colors'); + test.equals( + p.getPatternLink(pl), + '00-atoms-00-global' + path.sep + '00-atoms-00-global.rendered.html' + ); + test.equals(p.patternGroup, 'atoms'); + test.equals(p.patternSubGroup, 'global'); //because of p.info.hasDir + test.equals(p.flatPatternPath, '00-atoms-00-global'); + test.equals(p.patternPartial, 'atoms-colors'); + test.equals(p.template, ''); + test.equals(p.patternPartialCode, ''); + 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.equals(p.patternState, ''); + test.end(); + } +); + +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', { + d: 123, + }); + test.equals(p1.name, '00-atoms-00-global-colors-variant'); + test.equals(p2.name, '00-atoms-00-global-colors-variant-minus'); + test.end(); +}); + +tap.test('test Pattern with one-directory subdir works as expected', function( + test +) { + var p = new Pattern('00-atoms/00-colors.mustache', { d: 123 }); + test.equals(p.relPath, '00-atoms' + path.sep + '00-colors.mustache'); + test.equals(p.name, '00-atoms-00-colors'); + test.equals(p.subdir, '00-atoms'); + test.equals(p.fileName, '00-colors'); + test.equals(p.fileExtension, '.mustache'); + test.equals(p.jsonFileData.d, 123); + test.equals(p.patternBaseName, 'colors'); + test.equals(p.patternName, 'Colors'); + test.equals( + p.getPatternLink(pl), + '00-atoms-00-colors' + path.sep + '00-atoms-00-colors.rendered.html' + ); + test.equals(p.patternGroup, 'atoms'); + test.equals(p.flatPatternPath, '00-atoms'); + test.equals(p.patternPartial, 'atoms-colors'); + 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) { + var p = new Pattern('atoms/colors.mustache', { d: 123 }); + test.equals(p.relPath, 'atoms' + path.sep + 'colors.mustache'); + test.equals(p.name, 'atoms-colors'); + test.equals(p.subdir, 'atoms'); + test.equals(p.fileName, 'colors'); + test.equals( + p.getPatternLink(pl), + 'atoms-colors' + path.sep + 'atoms-colors.rendered.html' + ); + test.equals(p.patternGroup, 'atoms'); + test.equals(p.flatPatternPath, 'atoms'); + test.equals(p.patternPartial, 'atoms-colors'); + test.end(); + } +); + +tap.test('test Pattern capitalizes patternDisplayName correctly', function( + test +) { + var p = new Pattern('00-atoms/00-global/00-colors-alt.mustache', { d: 123 }); + test.equals(p.patternBaseName, 'colors-alt'); + test.equals(p.patternName, 'Colors Alt'); + test.end(); +}); + +tap.test('test Pattern get dir level no separated pattern directory', function( + test +) { + 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 }), ''); + 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 }), ''); + 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.end(); +}); + +tap.test( + 'test Pattern get dir level with separated pattern directory', + function(test) { + var p = new Pattern( + '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 }), ''); + 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 }), ''); + 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(3, { hasDir: true, dirLevel: 1 }), ''); + test.end(); + } +); + +tap.test('The forms of Pattern.getPatternLink() work as expected', function( + test +) { + var p = new Pattern('00-atoms/00-global/00-colors.hbs'); + test.equals( + p.getPatternLink(pl), + '00-atoms-00-global-00-colors' + + path.sep + + '00-atoms-00-global-00-colors.rendered.html' + ); + test.equals( + p.getPatternLink(pl, 'rendered'), + '00-atoms-00-global-00-colors' + + path.sep + + '00-atoms-00-global-00-colors.rendered.html' + ); + test.equals( + p.getPatternLink(pl, 'rawTemplate'), + '00-atoms-00-global-00-colors' + + path.sep + + '00-atoms-00-global-00-colors.hbs' + ); + test.equals( + p.getPatternLink(pl, 'markupOnly'), + '00-atoms-00-global-00-colors' + + path.sep + + '00-atoms-00-global-00-colors.markup-only.html' + ); + test.equals( + p.getPatternLink(pl, 'custom', '.custom-extension'), + '00-atoms-00-global-00-colors' + + path.sep + + '00-atoms-00-global-00-colors.custom-extension' + ); + test.end(); +}); diff --git a/packages/core/test/parameter_hunter_tests.js b/packages/core/test/parameter_hunter_tests.js new file mode 100644 index 000000000..3c3328648 --- /dev/null +++ b/packages/core/test/parameter_hunter_tests.js @@ -0,0 +1,465 @@ +'use strict'; + +const path = require('path'); +const util = require('./util/test_utils.js'); +const tap = require('tap'); + +const loadPattern = require('../src/lib/loadPattern'); +const ph = require('../src/lib/parameter_hunter'); +const processIterative = require('../src/lib/processIterative'); + +const parameter_hunter = new ph(); + +const config = require('./util/patternlab-config.json'); +const engineLoader = require('../src/lib/pattern_engines'); +engineLoader.loadAllEngines(config); + +const testPatternsPath = path.resolve(__dirname, 'files', '_patterns'); + +tap.test('parameter hunter finds and extends templates', function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]) + .then(() => { + //act + parameter_hunter + .find_parameters(testPattern, pl) + .then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized( + '

{{foo}}

A life is like a garden. Perfect moments can be had, but not preserved, except in memory.

' + ) + ); + test.end(); + }) + .catch(test.threw); + }) + .catch(test.threw); +}); + +tap.test( + 'parameter hunter finds and extends templates with verbose partials', + function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join( + '00-test', + 'sticky-comment-verbose.mustache' + ); + var testPattern = loadPattern(testPatternPath, pl); + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]) + .then(() => { + //act + parameter_hunter + .find_parameters(testPattern, pl) + .then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized( + '

{{foo}}

A life is like a garden. Perfect moments can be had, but not preserved, except in memory.

' + ) + ); + test.end(); + }) + .catch(test.threw); + }) + .catch(test.threw); + } +); + +//previous tests were for unquoted parameter keys and single-quoted values. +//test other quoting options. +tap.test( + 'parameter hunter parses parameters with unquoted keys and unquoted values', + function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //override the file + testPattern.template = '{{> test-comment(description: true) }}'; + testPattern.extendedTemplate = testPattern.template; + testPattern.parameteredPartials[0] = testPattern.template; + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]).then(() => { + //act + parameter_hunter.find_parameters(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized('

{{foo}}

true

') + ); + test.end(); + }); + }); + } +); + +tap.test( + 'parameter hunter parses parameters with unquoted keys and double-quoted values', + function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //override the file + testPattern.template = '{{> test-comment(description: "true") }}'; + testPattern.extendedTemplate = testPattern.template; + testPattern.parameteredPartials[0] = testPattern.template; + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]).then(() => { + //act + parameter_hunter.find_parameters(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized('

{{foo}}

true

') + ); + test.end(); + }); + }); + } +); + +tap.test( + 'parameter hunter parses parameters with single-quoted keys and unquoted values', + function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //override the file + testPattern.template = "{{> test-comment('description': true) }}"; + testPattern.extendedTemplate = testPattern.template; + testPattern.parameteredPartials[0] = testPattern.template; + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]).then(() => { + //act + parameter_hunter.find_parameters(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized('

{{foo}}

true

') + ); + test.end(); + }); + }); + } +); + +tap.test( + 'parameter hunter parses parameters with single-quoted keys and single-quoted values wrapping internal escaped single-quotes', + function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //override the file + testPattern.template = + "{{> test-comment('description': 'true not,\\'true\\'') }}"; + testPattern.extendedTemplate = testPattern.template; + testPattern.parameteredPartials[0] = testPattern.template; + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]).then(() => { + //act + parameter_hunter.find_parameters(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized(`

{{foo}}

true not,'true'

`) + ); + test.end(); + }); + }); + } +); + +tap.test( + 'parameter hunter parses parameters with single-quoted keys and double-quoted values wrapping internal single-quotes', + function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //override the file + testPattern.template = + "{{> test-comment('description': \"true not:'true'\") }}"; + testPattern.extendedTemplate = testPattern.template; + testPattern.parameteredPartials[0] = testPattern.template; + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]).then(() => { + //act + parameter_hunter.find_parameters(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized(`

{{foo}}

true not:'true'

`) + ); + test.end(); + }); + }); + } +); + +tap.test( + 'parameter hunter parses parameters with double-unquoted keys and unquoted values', + function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //override the file + testPattern.template = '{{> test-comment("description": true) }}'; + testPattern.extendedTemplate = testPattern.template; + testPattern.parameteredPartials[0] = testPattern.template; + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]).then(() => { + //act + parameter_hunter.find_parameters(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized('

{{foo}}

true

') + ); + test.end(); + }); + }); + } +); + +tap.test( + 'parameter hunter parses parameters with double-quoted keys and single-quoted values wrapping internal double-quotes', + function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //override the file + testPattern.template = + '{{> test-comment("description": \'true not{"true"\') }}'; + testPattern.extendedTemplate = testPattern.template; + testPattern.parameteredPartials[0] = testPattern.template; + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]).then(() => { + //act + parameter_hunter.find_parameters(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized('

{{foo}}

true not{"true"

') + ); + test.end(); + }); + }); + } +); + +tap.test( + 'parameter hunter parses parameters with double-quoted keys and double-quoted values wrapping internal escaped double-quotes', + function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //override the file + testPattern.template = + '{{> test-comment("description": "true not}\\"true\\"") }}'; + testPattern.extendedTemplate = testPattern.template; + testPattern.parameteredPartials[0] = testPattern.template; + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]).then(() => { + //act + parameter_hunter.find_parameters(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized('

{{foo}}

true not}"true"

') + ); + test.end(); + }); + }); + } +); + +tap.test( + 'parameter hunter parses parameters with combination of quoting schemes for keys and values', + function(test) { + //arrange + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //override the file + testPattern.template = + '{{> test-comment(description: true, \'foo\': false, "bar": false, \'single\': true, \'singlesingle\': \'true\', \'singledouble\': "true", "double": true, "doublesingle": \'true\', "doubledouble": "true") }}'; + testPattern.extendedTemplate = testPattern.template; + testPattern.parameteredPartials[0] = testPattern.template; + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]).then(() => { + //act + parameter_hunter.find_parameters(testPattern, pl).then(() => { + //assert + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized('

false

true

') + ); + test.end(); + }); + }); + } +); + +//todo https://github.com/pattern-lab/patternlab-node/issues/673 +// tap.test('parameter hunter parses parameters with values containing a closing parenthesis', function (test) { +// //arrange +// const pl = util.fakePatternLab(testPatternsPath); + +// var commentPath = path.join('00-test', 'comment.mustache'); +// var commentPattern = loadPattern(commentPath, pl); + +// var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); +// var testPattern = loadPattern(testPatternPath, pl); + +// //override the file +// testPattern.template = "{{> test-comment(description: 'Hello ) World') }}"; +// testPattern.extendedTemplate = testPattern.template; +// testPattern.parameteredPartials[0] = testPattern.template; + +// var p1 = processIterative(commentPattern, pl); +// var p2 = processIterative(testPattern, pl); + +// Promise.all([p1, p2]).then(() => { +// //act +// parameter_hunter.find_parameters(testPattern, pl).then(() => { +// //assert +// test.equals(util.sanitized(testPattern.extendedTemplate), util.sanitized('

Hello ) World

')); +// test.end(); +// }); +// }); +// }); + +tap.test('parameter hunter skips malformed parameters', function(test) { + const pl = util.fakePatternLab(testPatternsPath); + + var commentPath = path.join('00-test', 'comment.mustache'); + var commentPattern = loadPattern(commentPath, pl); + + var testPatternPath = path.join('00-test', 'sticky-comment.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //override the file + testPattern.template = + '{{> test-comment( missing-val: , : missing-key, : , , foo: "Hello World") }}'; + testPattern.extendedTemplate = testPattern.template; + testPattern.parameteredPartials[0] = testPattern.template; + + var p1 = processIterative(commentPattern, pl); + var p2 = processIterative(testPattern, pl); + + Promise.all([p1, p2]).then(() => { + //act + parameter_hunter.find_parameters(testPattern, pl).then(() => { + //assert + console.log( + '\nPattern Lab should catch JSON.parse() errors and output useful debugging information...' + ); + test.equals( + util.sanitized(testPattern.extendedTemplate), + util.sanitized('

{{foo}}

{{description}}

') + ); + test.end(); + }); + }); +}); diff --git a/packages/core/test/parseAllLinks_tests.js b/packages/core/test/parseAllLinks_tests.js new file mode 100644 index 000000000..271b3ee5b --- /dev/null +++ b/packages/core/test/parseAllLinks_tests.js @@ -0,0 +1,116 @@ +'use strict'; + +const path = require('path'); +const tap = require('tap'); +const fs = require('fs-extra'); + +const addPattern = require('../src/lib/addPattern'); +const parseAllLinks = require('../src/lib/parseAllLinks'); + +const Pattern = require('../src/lib/object_factory').Pattern; +const PatternGraph = require('../src/lib/pattern_graph').PatternGraph; +const da = require('../src/lib/data_loader'); +const dataLoader = new da(); + +const util = require('./util/test_utils.js'); +const patterns_dir = './test/files/_patterns'; + +tap.test( + 'parseDataLinks - replaces found link.* data for their expanded links', + function(test) { + //arrange + 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 = {}; + + // copies essential logic from loadPattern + const navPattern = new Pattern('00-test/nav.mustache'); + const patternData = dataLoader.loadDataFromFile( + path.resolve( + __dirname, + 'files/_patterns', + navPattern.subdir, + navPattern.fileName + ), + fs + ); + 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' }; + + let pattern; + for (let i = 0; i < patternlab.patterns.length; i++) { + if (patternlab.patterns[i].patternPartial === 'test-nav') { + pattern = patternlab.patterns[i]; + } + } + + //assert before + test.equals( + pattern.jsonFileData.brad.url, + 'link.twitter-brad', + 'brad pattern data should be found' + ); + test.equals( + pattern.jsonFileData.dave.url, + 'link.twitter-dave', + 'dave pattern data should be found' + ); + test.equals( + pattern.jsonFileData.brian.url, + 'link.twitter-brian', + 'brian pattern data should be found' + ); + + //act + parseAllLinks(patternlab); + + //assert after + test.equals( + pattern.jsonFileData.brad.url, + 'https://twitter.com/brad_frost', + 'brad pattern data should be replaced' + ); + test.equals( + pattern.jsonFileData.dave.url, + 'https://twitter.com/dmolsen', + 'dave pattern data should be replaced' + ); + test.equals( + pattern.jsonFileData.brian.url, + 'https://twitter.com/bmuenzenmeyer', + 'brian pattern data should be replaced' + ); + + test.equals( + patternlab.data.brad.url, + 'https://twitter.com/brad_frost', + 'global brad data should be replaced' + ); + test.equals( + patternlab.data.dave.url, + 'https://twitter.com/dmolsen', + 'global dave data should be replaced' + ); + test.equals( + patternlab.data.brian.url, + 'https://twitter.com/bmuenzenmeyer', + 'global brian data should be replaced' + ); + test.end(); + } +); diff --git a/packages/core/test/pattern_engines_tests.js b/packages/core/test/pattern_engines_tests.js new file mode 100644 index 000000000..f79b129dd --- /dev/null +++ b/packages/core/test/pattern_engines_tests.js @@ -0,0 +1,251 @@ +'use strict'; + +var tap = require('tap'); + +var patternEngines = require('../src/lib/pattern_engines'); +var Pattern = require('../src/lib/object_factory').Pattern; +var config = require('./util/patternlab-config.json'); + +patternEngines.loadAllEngines(config); + +// the mustache test pattern, stolen from object_factory unit tests +var mustacheTestPattern = new Pattern( + 'source/_patterns/00-atoms/00-global/00-colors-alt.mustache', + { d: 123 } +); +var mustacheTestPseudoPatternBasePattern = new Pattern( + 'source/_patterns/04-pages/00-homepage.mustache', + { d: 123 } +); +var mustacheTestPseudoPattern = new Pattern( + 'source/_patterns/04-pages/00-homepage~emergency.json', + { d: 123 } +); +mustacheTestPseudoPattern.isPseudoPattern = true; +mustacheTestPseudoPattern.basePattern = mustacheTestPseudoPatternBasePattern; +var engineNames = Object.keys(patternEngines); + +tap.test( + 'getEngineNameForPattern returns "mustache" from test pattern', + function(test) { + var engineName = patternEngines.getEngineNameForPattern( + mustacheTestPattern + ); + test.equals(engineName, 'mustache'); + test.end(); + } +); + +tap.test( + 'getEngineNameForPattern returns "mustache" for a plain string template as a backwards compatibility measure', + function(test) { + test.plan(1); + test.equals( + patternEngines.getEngineNameForPattern('plain text string'), + 'mustache' + ); + test.end(); + } +); + +tap.test( + 'getEngineNameForPattern returns "mustache" for an artificial empty template', + function(test) { + test.plan(1); + var emptyPattern = Pattern.createEmpty(); + test.equals( + patternEngines.getEngineNameForPattern(emptyPattern), + 'mustache' + ); + test.end(); + } +); + +tap.test( + 'getEngineForPattern returns a reference to the mustache engine from test pattern', + function(test) { + var engine = patternEngines.getEngineForPattern(mustacheTestPattern); + test.equals(engine, patternEngines.mustache); + test.end(); + } +); + +tap.test( + 'getEngineForPattern returns a reference to the mustache engine from test pseudo-pattern', + function(test) { + var engine = patternEngines.getEngineForPattern(mustacheTestPseudoPattern); + test.equals(engine, patternEngines.mustache); + test.end(); + } +); + +tap.test( + 'isPseudoPatternJSON correctly identifies pseudo-pattern JSON filenames', + function(test) { + // each test case + var filenames = { + '00-homepage~emergency.json': true, + '~emergency.json': true, + '00-homepage~emergency.js': false, + '00-homepage-emergency.js': false, + '00-homepage.hbs': false, + '00-homepage.json': false, + 'greatpic.jpg': false, + }; + // expect one test per test case + test.plan(Object.keys(filenames).length); + + // loop over each test case and test it + Object.keys(filenames).forEach(function(filename) { + var expectedResult = filenames[filename], + actualResult = patternEngines.isPseudoPatternJSON(filename), + testMessage = + 'isPseudoPatternJSON should return ' + + expectedResult + + ' for ' + + filename; + test.strictEqual(actualResult, expectedResult, testMessage); + }); + + // done + test.end(); + } +); + +tap.test( + 'isPatternFile correctly identifies pattern files and rejects non-pattern files', + function(test) { + // each test case + var filenames = { + '00-comment-thread.mustache': true, + '00-comment-thread.fakeextthatdoesntexist': false, + '00-comment-thread': false, + '_00-comment-thread.mustache': true, + '.00-comment-thread.mustache': false, + '00-comment-thread.json': false, + '00-homepage~emergency.json': true, + }; + // expect one test per test case + test.plan(Object.keys(filenames).length); + + // loop over each test case and test it + Object.keys(filenames).forEach(function(filename) { + var expectedResult = filenames[filename], + actualResult = patternEngines.isPatternFile(filename), + testMessage = + 'isPatternFile should return ' + expectedResult + ' for ' + filename; + test.strictEqual(actualResult, expectedResult, testMessage); + }); + + // done + test.end(); + } +); + +// testProps() utility function: given an object, and a hash of expected +// 'property name':'property type' pairs, verify that the object contains each +// expected property, and that each property is of the expected type. +function testProps(object, propTests, test) { + // function to test each expected property is present and the correct type + function testProp(propName, types) { + var possibleTypes; + + // handle "types" being a string or an array of strings + if (types instanceof Array) { + possibleTypes = types; + } else { + // "types" is just a single string, load it into an array; the rest of + // the code expects it! + possibleTypes = [types]; + } + + var isOneOfTheseTypes = possibleTypes + .map(function(type) { + return typeof object[propName] === type; + }) + .reduce(function(isPrevType, isCurrentType) { + return isPrevType || isCurrentType; + }); + + test.ok( + object.hasOwnProperty(propName), + '"' + propName + '" prop should be present' + ); + test.ok( + isOneOfTheseTypes, + '"' + + propName + + '" prop should be one of types ' + + possibleTypes + + ' but was instead ' + + typeof propName + ); + } + + // go over each property test and run it + Object.keys(propTests).forEach(function(propName) { + var propType = propTests[propName]; + testProp(propName, propType); + }); +} + +tap.test( + 'patternEngines object contains at least the default mustache engine', + function(test) { + test.plan(1); + test.ok(patternEngines.hasOwnProperty('mustache')); + test.end(); + } +); + +tap.test( + 'patternEngines object reports that it supports the .mustache extension', + function(test) { + test.plan(1); + test.ok(patternEngines.isFileExtensionSupported('.mustache')); + test.end(); + } +); + +// make one big test group for each pattern engine +engineNames.forEach(function(engineName) { + tap.test( + 'engine ' + engineName + ' contains expected properties and methods', + function(test) { + var propertyTests = { + engine: ['object', 'function'], + engineName: 'string', + engineFileExtension: ['string', 'object'], + renderPattern: 'function', + findPartials: 'function', + }; + + test.plan(Object.keys(propertyTests).length * 2); + testProps(patternEngines[engineName], propertyTests, test); + test.end(); + } + ); +}); + +tap.test( + 'patternEngines getSupportedFileExtensions flattens known engine extensions into a single array', + function(test) { + //arrange + patternEngines.fooEngine = { + engineFileExtension: ['.foo1', '.foo2'], + }; + patternEngines.barEngine = { + engineFileExtension: '.bar', + }; + + const exts = patternEngines.getSupportedFileExtensions(); + test.ok(exts.includes('.foo1')); + test.ok(exts.includes('.foo2')); + test.ok(exts.includes('.bar')); + + delete patternEngines.fooEngine; + delete patternEngines.barEngine; + + test.end(); + } +); diff --git a/packages/core/test/pattern_graph_tests.js b/packages/core/test/pattern_graph_tests.js new file mode 100644 index 000000000..a2618031c --- /dev/null +++ b/packages/core/test/pattern_graph_tests.js @@ -0,0 +1,585 @@ +'use strict'; + +var path = require('path'); +var tap = require('tap'); + +var PatternGraph = require('../src/lib/pattern_graph').PatternGraph; +var VERSION = require('../src/lib/pattern_graph').PATTERN_GRAPH_VERSION; +var Pattern = require('../src/lib/object_factory').Pattern; +var CompileState = require('../src/lib/object_factory').CompileState; +const posixPath = require('./util/test_utils.js').posixPath; +var config = require('./util/patternlab-config.json'); +var engineLoader = require('../src/lib/pattern_engines'); +engineLoader.loadAllEngines(config); + +var patternlab = { + config: { + paths: { + public: { + root: `${__dirname}/public`, + }, + }, + }, +}; + +var mockGraph = function() { + return PatternGraph.empty(); +}; + +tap.test('checkVersion - Current version returns true', test => { + test.same(PatternGraph.checkVersion({ version: VERSION }), true); + test.end(); +}); + +tap.test('checkVersion - Older version returns false', test => { + test.same(PatternGraph.checkVersion({ version: VERSION - 1 }), false); + test.end(); +}); + +tap.test('Loading an empty graph works', test => { + var g = PatternGraph.loadFromFile( + path.resolve(__dirname, 'public'), + 'does not exist' + ); + tap.equal(g.graph.nodes().length, 0, 'foo'); + test.end(); +}); + +tap.test('PatternGraph.fromJson() - Loading a graph from JSON', test => { + var graph = PatternGraph.loadFromFile( + path.resolve(__dirname, 'public'), + 'testDependencyGraph.json' + ); + test.same(graph.timestamp, 1337); + test.same(graph.graph.nodes(), ['atom-foo', 'molecule-foo']); + test.same(graph.graph.edges(), [{ v: 'molecule-foo', w: 'atom-foo' }]); + test.end(); +}); + +tap.test( + 'PatternGraph.fromJson() - Loading a graph from JSON using an older version throws error', + test => { + test.throws( + function() { + PatternGraph.fromJson({ version: 0 }); + }, + {}, + /Version of graph on disk.*/g + ); + + test.end(); + } +); + +tap.test('toJson() - Storing a graph to JSON correctly', test => { + var graph = mockGraph(); + graph.timestamp = 1337; + var atomFoo = Pattern.create('atom-foo', null, { + compileState: CompileState.CLEAN, + }); + var moleculeFoo = Pattern.create('molecule-foo', null, { + compileState: CompileState.CLEAN, + }); + graph.add(atomFoo); + graph.add(moleculeFoo); + graph.link(moleculeFoo, atomFoo); + test.same(graph.toJson(), { + version: VERSION, + timestamp: 1337, + graph: { + options: { directed: true, multigraph: false, compound: false }, + nodes: [ + { v: 'atom-foo', value: { compileState: 'clean' } }, + { v: 'molecule-foo', value: { compileState: 'clean' } }, + ], + edges: [{ v: 'molecule-foo', w: 'atom-foo', value: {} }], + }, + }); + // For generating the above output:console.log(JSON.stringify(graph.toJson())); + test.end(); +}); + +tap.test( + 'Storing and loading a graph from JSON return the identical graph', + test => { + var oldGraph = mockGraph(); + oldGraph.timestamp = 1337; + var atomFoo = Pattern.create('atom-foo', null, { + compileState: CompileState.CLEAN, + }); + var moleculeFoo = Pattern.create('molecule-foo', null, { + compileState: CompileState.CLEAN, + }); + oldGraph.add(atomFoo); + oldGraph.add(moleculeFoo); + oldGraph.link(moleculeFoo, atomFoo); + + // act + var newGraph = PatternGraph.fromJson(oldGraph.toJson()); + + // assert + test.same(newGraph.version, VERSION); + test.same(newGraph.timestamp, 1337); + test.same(newGraph.graph.nodes(), ['atom-foo', 'molecule-foo']); + test.same(newGraph.graph.edges(), [{ w: 'atom-foo', v: 'molecule-foo' }]); + // The graph is a new object + test.notEqual(newGraph, oldGraph); + test.end(); + } +); + +tap.test('clone()', test => { + var oldGraph = mockGraph(); + oldGraph.timestamp = 1337; + var atomFoo = Pattern.create('atom-foo', null, { + compileState: CompileState.CLEAN, + }); + var moleculeFoo = Pattern.create('molecule-foo', null, { + compileState: CompileState.CLEAN, + }); + oldGraph.add(atomFoo); + oldGraph.add(moleculeFoo); + oldGraph.link(moleculeFoo, atomFoo); + + // act + var newGraph = oldGraph.clone(); + + // assert + test.same(newGraph.version, VERSION); + test.same(newGraph.timestamp, 1337); + test.same(newGraph.graph.nodes(), ['atom-foo', 'molecule-foo']); + test.same(newGraph.graph.edges(), [{ w: 'atom-foo', v: 'molecule-foo' }]); + // The graph is a new object + test.notEqual(newGraph, oldGraph); + test.end(); +}); + +tap.test('Adding a node', test => { + var g = mockGraph(); + var pattern = Pattern.create('atom-foo', null, { + compileState: CompileState.CLEAN, + }); + g.add(pattern); + test.same( + { compileState: CompileState.CLEAN }, + g.node('atom-foo'), + 'Data were set correctly' + ); + var actual = g.nodes(); + test.same(actual, ['atom-foo']); + test.end(); +}); + +tap.test('Adding a node twice', test => { + var g = mockGraph(); + var pattern = Pattern.create('atom-foo', null, { + compileState: CompileState.CLEAN, + }); + g.add(pattern); + g.add(pattern); + var actual = g.nodes(); + test.same(actual, ['atom-foo']); + test.end(); +}); + +tap.test('Adding two nodes', test => { + var g = mockGraph(); + var atomFoo = Pattern.create('atom-foo', { + compileState: CompileState.CLEAN, + }); + var moleculeFoo = Pattern.create('molecule-foo', { + compileState: CompileState.CLEAN, + }); + g.add(atomFoo); + g.add(moleculeFoo); + var actual = g.nodes(); + test.same(actual, ['atom-foo', 'molecule-foo']); + test.end(); +}); + +tap.test('Adding two nodes with only different subpattern types', test => { + var g = mockGraph(); + var atomFoo = Pattern.create('00-atoms/00-foo/baz.html', { + compileState: CompileState.CLEAN, + }); + var moleculeFoo = Pattern.create('00-atoms/00-bar/baz.html', { + compileState: CompileState.CLEAN, + }); + g.add(atomFoo); + g.add(moleculeFoo); + var actual = g.nodes(); + test.same(posixPath(actual), [ + '00-atoms/00-foo/baz.html', + '00-atoms/00-bar/baz.html', + ]); + test.end(); +}); + +tap.test('Linking two nodes', test => { + var g = mockGraph(); + var atomFoo = Pattern.create('atom-foo', null, { + compileState: CompileState.CLEAN, + }); + var moleculeFoo = Pattern.create('molecule-foo', null, { + compileState: CompileState.CLEAN, + }); + g.add(atomFoo); + g.add(moleculeFoo); + g.link(moleculeFoo, atomFoo); + test.same( + g.graph.edges(), + [{ v: 'molecule-foo', w: 'atom-foo' }], + 'There is an edge from v to w' + ); + test.end(); +}); + +tap.test('remove() - Removing a node', test => { + var g = mockGraph(); + var atomFoo = Pattern.create('atom-foo', null, { + compileState: CompileState.CLEAN, + }); + var moleculeFoo = Pattern.create('molecule-foo', null, { + compileState: CompileState.CLEAN, + }); + g.add(atomFoo); + g.add(moleculeFoo); + test.same(g.nodes(), ['atom-foo', 'molecule-foo']); + g.remove(moleculeFoo); + test.same( + g.graph.nodes(), + ['atom-foo'], + 'The molecule was removed from the graph' + ); + test.same( + g.patterns.allPatterns()[0].relPath, + 'atom-foo', + 'The molecule was removed from the known patterns' + ); + test.end(); +}); + +tap.test('filter() - Removing nodes via filter', test => { + var g = mockGraph(); + var atomFoo = Pattern.create('atom-foo', null, { + compileState: CompileState.CLEAN, + }); + var moleculeFoo = Pattern.create('molecule-foo', null, { + compileState: CompileState.CLEAN, + }); + g.add(atomFoo); + g.add(moleculeFoo); + test.same(g.nodes(), ['atom-foo', 'molecule-foo']); + g.filter(n => n != 'molecule-foo'); + test.same( + g.graph.nodes(), + ['atom-foo'], + 'The molecule was removed from the graph' + ); + test.same( + g.patterns.allPatterns()[0].relPath, + 'atom-foo', + 'The molecule was removed from the known patterns' + ); + test.end(); +}); + +// Prevents nodes from escaping the scope, at the same time have some default graph for lineage to +// test on +(function() { + var atomFoo = Pattern.create('00-atom/xy/foo', null, { + compileState: CompileState.CLEAN, + }); + var atomIsolated = Pattern.create('00-atom/xy/isolated', null, { + compileState: CompileState.CLEAN, + }); + var moleculeFoo = Pattern.create('01-molecule/xy/foo', null, { + compileState: CompileState.CLEAN, + }); + var moleculeBar = Pattern.create('01-molecule/xy/bar', null, { + compileState: CompileState.CLEAN, + }); + var organismFoo = Pattern.create('02-organism/xy/foo', null, { + compileState: CompileState.CLEAN, + }); + var organismBar = Pattern.create('02-organism/xy/bar', null, { + compileState: CompileState.CLEAN, + }); + + var g = mockGraph(); + + // Included nowhere + g.add(atomIsolated); + + g.add(atomFoo); + g.add(moleculeFoo); + g.add(moleculeBar); + g.add(organismFoo); + g.add(organismBar); + // single molecule + g.link(organismFoo, moleculeFoo); + + // include two molecules + g.link(organismBar, moleculeFoo); + g.link(organismBar, moleculeBar); + + // both include atomFoo + g.link(moleculeFoo, atomFoo); + g.link(moleculeBar, atomFoo); + + tap.test('lineage() - Calculate the lineage of a node', test => { + test.same(posixPath(g.lineage(organismFoo).map(p => p.relPath)), [ + '01-molecule/xy/foo', + ]); + test.same(posixPath(g.lineage(organismBar).map(p => p.relPath)), [ + '01-molecule/xy/foo', + '01-molecule/xy/bar', + ]); + test.same(posixPath(g.lineage(moleculeFoo).map(p => p.relPath)), [ + '00-atom/xy/foo', + ]); + test.same(posixPath(g.lineage(moleculeBar).map(p => p.relPath)), [ + '00-atom/xy/foo', + ]); + test.same(g.lineage(atomFoo).map(p => p.relPath), []); + test.same(g.lineage(atomIsolated).map(p => p.relPath), []); + test.end(); + }); + + tap.test('lineageIndex() - Calculate the lineage of a node', test => { + test.same(g.lineageIndex(organismFoo), ['molecule-foo']); + test.same(g.lineageIndex(organismBar), ['molecule-foo', 'molecule-bar']); + test.same(g.lineageIndex(moleculeFoo), ['atom-foo']); + test.same(g.lineageIndex(moleculeBar), ['atom-foo']); + test.same(g.lineageIndex(atomFoo), []); + test.same(g.lineageIndex(atomIsolated), []); + test.end(); + }); + + tap.test('lineageR() - Calculate the reverse lineage of a node', test => { + test.same(g.lineageR(organismFoo).map(p => p.relPath), []); + test.same(g.lineageR(organismBar).map(p => p.relPath), []); + test.same(posixPath(g.lineageR(moleculeFoo).map(p => p.relPath)), [ + '02-organism/xy/foo', + '02-organism/xy/bar', + ]); + test.same(posixPath(g.lineageR(moleculeBar).map(p => p.relPath)), [ + '02-organism/xy/bar', + ]); + test.same(posixPath(g.lineageR(atomFoo).map(p => p.relPath)), [ + '01-molecule/xy/foo', + '01-molecule/xy/bar', + ]); + test.same(g.lineageR(atomIsolated).map(p => p.relPath), []); + test.end(); + }); + + tap.test('lineageRIndex() - Calculate the lineage index of a node', test => { + test.same(g.lineageRIndex(organismFoo), []); + test.same(g.lineageRIndex(organismBar), []); + test.same(g.lineageRIndex(moleculeFoo), ['organism-foo', 'organism-bar']); + test.same(g.lineageRIndex(moleculeBar), ['organism-bar']); + test.same(g.lineageRIndex(atomFoo), ['molecule-foo', 'molecule-bar']); + test.same(g.lineageRIndex(atomIsolated), []); + test.end(); + }); +})(); + +(function() { + function TestGraph() { + function csAt(args, idx) { + return { compileState: args[idx] || CompileState.CLEAN }; + } + var i = 0; + var atomFoo = (this.atomFoo = Pattern.create( + '00-atom/xy/foo', + null, + csAt(arguments, i++) + )); + var atomIsolated = (this.atomIsolated = Pattern.create( + '00-atom/xy/isolated', + null, + csAt(arguments, i++) + )); + var moleculeFoo = (this.moleculeFoo = Pattern.create( + '01-molecule/xy/foo', + null, + csAt(arguments, i++) + )); + var moleculeBar = (this.moleculeBar = Pattern.create( + '01-molecule/xy/bar', + null, + csAt(arguments, i++) + )); + var organismFoo = (this.organismFoo = Pattern.create( + '02-organism/xy/foo', + null, + csAt(arguments, i++) + )); + var organismBar = (this.organismBar = Pattern.create( + '02-organism/xy/bar', + null, + csAt(arguments, i++) + )); + + var graph = (this.graph = mockGraph()); + + // Included nowhere + graph.add(atomIsolated); + + graph.add(atomFoo); + graph.add(moleculeFoo); + graph.add(moleculeBar); + graph.add(organismFoo); + graph.add(organismBar); + // single molecule + graph.link(organismFoo, moleculeFoo); + + // include two molecules + graph.link(organismBar, moleculeFoo); + graph.link(organismBar, moleculeBar); + + // both include atomFoo + graph.link(moleculeFoo, atomFoo); + graph.link(moleculeBar, atomFoo); + } + + tap.test( + 'compileOrder() - A clean graph results in no nodes to recompile', + test => { + var g = new TestGraph(); + var co = g.graph.compileOrder(); + test.equals(0, co.length); + test.end(); + } + ); + + tap.test( + 'compileOrder() - Recompile isolated atoms does not do anything else', + test => { + var g = new TestGraph( + // atomFoo + CompileState.CLEAN, + // atomIsolated + CompileState.NEEDS_REBUILD + ); + + var co = g.graph.compileOrder(); + test.same([g.atomIsolated], co, 'Only recompile atomIsolated'); + co.forEach(p => + test.same( + p.compileState, + CompileState.NEEDS_REBUILD, + 'All patterns are marked for rebuilding' + ) + ); + + test.end(); + } + ); + + tap.test( + 'compileOrder() - Changing a linked atom bubbles back to the organisms', + test => { + // Almost every pattern - except atomIsolated - has a transitive dependency on atomFoo + var g = new TestGraph(CompileState.NEEDS_REBUILD); + var co = g.graph.compileOrder(); + + test.equals(5, co.length); + test.same( + [g.atomFoo, g.moleculeFoo, g.organismFoo, g.moleculeBar, g.organismBar], + co, + 'Recompile everything except atomIsolated' + ); + co.forEach(p => + test.same( + p.compileState, + CompileState.NEEDS_REBUILD, + 'All patterns are marked for rebuilding' + ) + ); + + test.end(); + } + ); + + tap.test( + 'compileOrder() - Changing a molecule leaves atoms untouched', + test => { + // Bubble up from molecules to organisms, leaving atoms unchanged as they were not modified + var g = new TestGraph(null, null, CompileState.NEEDS_REBUILD); + var co = g.graph.compileOrder(); + + test.same( + [g.moleculeFoo, g.organismFoo, g.organismBar], + co, + 'Recompile moleculeFoo and transitive dependencies' + ); + co.forEach(p => + test.same( + p.compileState, + CompileState.NEEDS_REBUILD, + 'All patterns are marked for rebuilding' + ) + ); + test.end(); + } + ); + + tap.test( + 'compileOrder() - Changing an organism leaves atoms and molecules untouched', + test => { + // Almost every pattern - except atomIsolated - has a transitive dependency on atomFoo + var g = new TestGraph( + // atoms + null, + null, + // molecules + null, + null, + // organismFoo + CompileState.NEEDS_REBUILD + ); + var co = g.graph.compileOrder(); + + test.same([g.organismFoo], co); + test.same( + co[0].compileState, + CompileState.NEEDS_REBUILD, + 'All patterns are marked for rebuilding' + ); + test.end(); + } + ); + + tap.test('compileOrder() - Recompile everything', test => { + // Almost every pattern - except atomIsolated - has a transitive dependency on atomFoo + // Also recompile atomIsolated + var g = new TestGraph( + CompileState.NEEDS_REBUILD, + CompileState.NEEDS_REBUILD + ); + var compileOrder = g.graph.compileOrder(); + + test.same( + [ + g.atomIsolated, + g.atomFoo, + g.moleculeFoo, + g.organismFoo, + g.moleculeBar, + g.organismBar, + ], + compileOrder, + 'Recompile everything except atomIsolated' + ); + compileOrder.forEach(p => + test.same( + p.compileState, + CompileState.NEEDS_REBUILD, + 'All patterns are marked for rebuilding' + ) + ); + test.end(); + }); +})(); diff --git a/packages/core/test/pattern_registry_tests.js b/packages/core/test/pattern_registry_tests.js new file mode 100644 index 000000000..bf2e71914 --- /dev/null +++ b/packages/core/test/pattern_registry_tests.js @@ -0,0 +1,72 @@ +'use strict'; + +var PatternRegistry = require('./../src/lib/pattern_registry'); + +var tap = require('tap'); + +// #540 Copied from pattern_assembler_tests +tap.test( + 'get_pattern_by_key - returns the fuzzy result when no others found', + function(test) { + var pattern_registry = new PatternRegistry(); + + var pattern = { + key: 'character-han-solo', + patternPartial: 'character-han-solo', + subdir: 'character', + fileName: 'han-solo', + }; + pattern_registry.put(pattern); + + //act + var result = pattern_registry.getPartial('character-han'); + //assert + test.equals(result, pattern); + test.end(); + } +); + +// #540 Copied from pattern_assembler_tests +tap.test('remove - remove an existing pattern', function(test) { + var pattern_registry = new PatternRegistry(); + + var pattern = { + key: 'character-han-solo', + patternPartial: 'character-han-solo', + subdir: 'character', + fileName: 'han-solo', + }; + pattern_registry.put(pattern); + + //act + pattern_registry.remove('character-han-solo'); + test.same(null, pattern_registry.get('character-han-solo')); + test.end(); +}); + +// #540 Copied from pattern_assembler_tests +tap.test('getPartial - returns the exact key if found', function(test) { + //arrange + var pattern_registry = new PatternRegistry(); + let patterns = [ + { + key: 'molecules-primary-nav-jagged', + patternPartial: 'molecules-primary-nav-jagged', + subdir: 'molecules', + fileName: 'primary-nav-jagged', + }, + { + key: 'molecules-primary-nav', + patternPartial: 'molecules-primary-nav', + subdir: 'molecules', + fileName: 'molecules-primary-nav', + }, + ]; + patterns.forEach(p => pattern_registry.put(p)); + + //act + var result = pattern_registry.getPartial('molecules-primary-nav'); + //assert + test.equals(result, patterns[1]); + test.end(); +}); diff --git a/packages/core/test/patternlab_tests.js b/packages/core/test/patternlab_tests.js new file mode 100644 index 000000000..741fbca15 --- /dev/null +++ b/packages/core/test/patternlab_tests.js @@ -0,0 +1,56 @@ +'use strict'; + +const tap = require('tap'); +const rewire = require('rewire'); +const fs = require('fs-extra'); +var config = require('./util/patternlab-config.json'); + +var plEngineModule = rewire('../src/lib/patternlab'); + +//set up a global mocks - we don't want to be writing/rendering any files right now +const fsMock = { + outputFileSync: function(path, content) { + /* INTENTIONAL NOOP */ + }, + readJSONSync: function(path, encoding) { + return fs.readJSONSync(path, encoding); + }, + emptyDir: function(path) { + return fs.emptyDir(path); + }, + readFileSync: function(path, encoding) { + return fs.readFileSync(path, encoding); + }, +}; + +//set our mocks in place of usual require() +plEngineModule.__set__({ + fs: fsMock, +}); + +tap.test( + 'buildPatternData - should merge all JSON files in the data folder except listitems', + function(test) { + const data_dir = `${__dirname}/files/_data/`; + + var pl = new plEngineModule(config); + var dataResult = pl.buildPatternData(data_dir, fs); + test.equals(dataResult.data, 'test'); + test.equals(dataResult.foo, 'bar'); + test.equals(dataResult.test_list_item, undefined); + test.end(); + } +); + +tap.test('buildPatternData - can load json, yaml, and yml files', function( + test +) { + const data_dir = `${__dirname}/files/_data/`; + + var pl = new plEngineModule(config); + var dataResult = pl.buildPatternData(data_dir, fs); + test.equals(dataResult.from_yml, 'from_yml'); + test.equals(dataResult.from_yaml, 'from_yaml'); + test.equals(dataResult.from_json, 'from_json'); + test.end(); +}); diff --git a/packages/core/test/processRecursive_tests.js b/packages/core/test/processRecursive_tests.js new file mode 100644 index 000000000..8522c0403 --- /dev/null +++ b/packages/core/test/processRecursive_tests.js @@ -0,0 +1,465 @@ +'use strict'; + +const tap = require('tap'); +const path = require('path'); + +const util = require('./util/test_utils.js'); +const buildListItems = require('../src/lib/buildListItems'); +const loadPattern = require('../src/lib/loadPattern'); +const engineLoader = require('../src/lib/pattern_engines'); +const processRecursive = require('../src/lib/processRecursive'); +const processIterative = require('../src/lib/processIterative'); + +var config = require('./util/patternlab-config.json'); + +engineLoader.loadAllEngines(config); + +const patterns_dir = `${__dirname}/files/_patterns`; + +tap.test('processRecursive recursively includes partials', function(test) { + //assert + const patternlab = util.fakePatternLab(patterns_dir); + + var fooPatternPath = path.join('00-test', '00-foo.mustache'); + var fooPattern = loadPattern(fooPatternPath, patternlab); + + var barPatternPath = path.join('00-test', '01-bar.mustache'); + var barPattern = loadPattern(barPatternPath, patternlab); + + var p1 = processIterative(fooPattern, patternlab); + var p2 = processIterative(barPattern, patternlab); + + Promise.all([p1, p2]) + .then(() => { + //act + processRecursive(fooPatternPath, patternlab) + .then(() => { + //assert + const expectedValue = 'bar'; + test.equals( + util.sanitized(fooPattern.extendedTemplate), + util.sanitized(expectedValue) + ); + test.end(); + }) + .catch(test.threw); + }) + .catch(test.threw); +}); + +tap.test( + 'processRecursive - correctly replaces all stylemodifiers when multiple duplicate patterns with different stylemodifiers found', + function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + var atomPath = path.join('00-test', '03-styled-atom.mustache'); + var atomPattern = loadPattern(atomPath, patternlab); + + var groupPath = path.join('00-test', '04-group.mustache'); + var groupPattern = loadPattern(groupPath, patternlab); + + var p1 = processIterative(atomPattern, patternlab); + var p2 = processIterative(groupPattern, patternlab); + + Promise.all([p1, p2]) + .then(() => { + //act + processRecursive(groupPath, patternlab) + .then(() => { + //assert + const expectedValue = + '
{{message}} {{message}} {{message}} {{message}}
'; + test.equals( + util.sanitized(groupPattern.extendedTemplate), + util.sanitized(expectedValue) + ); + test.end(); + }) + .catch(test.threw); + }) + .catch(test.threw); + } +); + +tap.only( + 'processRecursive - correctly replaces multiple stylemodifier classes on same partial', + function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + var atomPath = path.join('00-test', '03-styled-atom.mustache'); + var atomPattern = loadPattern(atomPath, patternlab); + + var groupPath = path.join( + '00-test', + '10-multiple-classes-numeric.mustache' + ); + var groupPattern = loadPattern(groupPath, patternlab); + + var p1 = processIterative(atomPattern, patternlab); + var p2 = processIterative(groupPattern, patternlab); + + Promise.all([p1, p2]) + .then(() => { + //act + processRecursive(groupPath, patternlab) + .then(() => { + //assert + const expectedValue = + '
{{message}} {{message}} bar
'; + test.equals( + util.sanitized(groupPattern.extendedTemplate), + util.sanitized(expectedValue) + ); + test.end(); + }) + .catch(test.threw); + }) + .catch(test.threw); + } +); + +tap.test( + 'processRecursive - correctly ignores a partial without a style modifier when the same partial later has a style modifier', + function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + var atomPath = path.join('00-test', '03-styled-atom.mustache'); + var atomPattern = loadPattern(atomPath, patternlab); + + var mixedPath = path.join('00-test', '06-mixed.mustache'); + var mixedPattern = loadPattern(mixedPath, patternlab); + + var p1 = processIterative(atomPattern, patternlab); + var p2 = processIterative(mixedPattern, patternlab); + + Promise.all([p1, p2]) + .then(() => { + //act + processRecursive(mixedPath, patternlab) + .then(() => { + //assert. here we expect {{styleModifier}} to be in the first group, since it was not replaced by anything. rendering with data will then remove this (correctly) + const expectedValue = + '
{{message}} {{message}} {{message}} {{message}}
'; + test.equals( + util.sanitized(mixedPattern.extendedTemplate), + util.sanitized(expectedValue) + ); + test.end(); + }) + .catch(test.threw); + }) + .catch(test.threw); + } +); + +tap.test( + 'processRecursive - correctly ignores bookended partials without a style modifier when the same partial has a style modifier between', + function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + var atomPath = path.join('00-test', '03-styled-atom.mustache'); + var atomPattern = loadPattern(atomPath, patternlab); + + var bookendPath = path.join('00-test', '09-bookend.mustache'); + var bookendPattern = loadPattern(bookendPath, patternlab); + + var p1 = processIterative(atomPattern, patternlab); + var p2 = processIterative(bookendPattern, patternlab); + + Promise.all([p1, p2]) + .then(() => { + //act + processRecursive(bookendPath, patternlab) + .then(() => { + //assert. here we expect {{styleModifier}} to be in the first and last group, since it was not replaced by anything. rendering with data will then remove this (correctly) + const expectedValue = + '
{{message}} {{message}} {{message}} {{message}}
'; + test.equals( + util.sanitized(bookendPattern.extendedTemplate), + util.sanitized(expectedValue) + ); + test.end(); + }) + .catch(test.threw); + }) + .catch(test.threw); + } +); + +tap.test( + 'processRecursive - correctly ignores a partial without a style modifier when the same partial later has a style modifier and pattern parameters', + function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + var atomPath = path.join('00-test', '03-styled-atom.mustache'); + var atomPattern = loadPattern(atomPath, patternlab); + + var mixedPath = path.join('00-test', '07-mixed-params.mustache'); + var mixedPattern = loadPattern(mixedPath, patternlab); + + var p1 = processIterative(atomPattern, patternlab); + var p2 = processIterative(mixedPattern, patternlab); + + Promise.all([p1, p2]) + .then(() => { + //act + processRecursive(mixedPath, patternlab) + .then(() => { + //assert. here we expect {{styleModifier}} to be in the first span, since it was not replaced by anything. rendering with data will then remove this (correctly) + const expectedValue = + '
{{message}} 2 3 4
'; + test.equals( + util.sanitized(mixedPattern.extendedTemplate), + util.sanitized(expectedValue) + ); + test.end(); + }) + .catch(test.threw); + }) + .catch(test.threw); + } +); + +tap.test( + 'processRecursive - correctly ignores bookended partials without a style modifier when the same partial has a style modifier and pattern parameters between', + function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + var atomPath = path.join('00-test', '03-styled-atom.mustache'); + var atomPattern = loadPattern(atomPath, patternlab); + + var bookendPath = path.join('00-test', '08-bookend-params.mustache'); + var bookendPattern = loadPattern(bookendPath, patternlab); + + var p1 = processIterative(atomPattern, patternlab); + var p2 = processIterative(bookendPattern, patternlab); + + Promise.all([p1, p2]) + .then(() => { + //act + processRecursive(bookendPath, patternlab) + .then(() => { + //assert. here we expect {{styleModifier}} to be in the first and last span, since it was not replaced by anything. rendering with data will then remove this (correctly) + const expectedValue = + '
{{message}} 2 3 {{message}}
'; + test.equals( + util.sanitized(bookendPattern.extendedTemplate), + util.sanitized(expectedValue) + ); + test.end(); + }) + .catch(test.threw); + }) + .catch(test.threw); + } +); + +tap.test( + 'processRecursive - does not pollute previous patterns when a later one is found with a styleModifier', + function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + var atomPath = path.join('00-test', '03-styled-atom.mustache'); + var atomPattern = loadPattern(atomPath, patternlab); + + var anotherPath = path.join('00-test', '12-another-styled-atom.mustache'); + var anotherPattern = loadPattern(anotherPath, patternlab); + + var p1 = processIterative(atomPattern, patternlab); + var p2 = processIterative(anotherPattern, patternlab); + + Promise.all([p1, p2]) + .then(() => { + //act + processRecursive(anotherPath, patternlab) + .then(() => { + //assert + const expectedCleanValue = + ' {{message}} '; + const expectedSetValue = + ' {{message}} '; + + //this is the "atom" - it should remain unchanged + test.equals( + util.sanitized(atomPattern.template), + util.sanitized(expectedCleanValue) + ); + test.equals( + util.sanitized(atomPattern.extendedTemplate), + util.sanitized(expectedCleanValue) + ); + + // this is the style modifier pattern, which should resolve correctly + test.equals( + util.sanitized(anotherPattern.template), + '{{> test-styled-atom:test_1 }}' + ); + test.equals( + util.sanitized(anotherPattern.extendedTemplate), + util.sanitized(expectedSetValue) + ); + test.end(); + }) + .catch(test.threw); + }) + .catch(test.threw); + } +); + +tap + .test('processRecursive - ensure deep-nesting works', function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + var atomPath = path.join('00-test', '01-bar.mustache'); + var atomPattern = loadPattern(atomPath, patternlab); + + var templatePath = path.join('00-test', '00-foo.mustache'); + var templatePattern = loadPattern(templatePath, patternlab); + + var pagesPath = path.join('00-test', '14-inception.mustache'); + var pagesPattern = loadPattern(pagesPath, patternlab); + + var p1 = processIterative(atomPattern, patternlab); + var p2 = processIterative(templatePattern, patternlab); + var p3 = processIterative(pagesPattern, patternlab); + + return Promise.all([ + p1, + p2, + p3, + processRecursive(atomPath, patternlab), + processRecursive(templatePath, patternlab), + processRecursive(pagesPath, patternlab), + ]).then(() => { + //act + return test.test( + 'processRecursive - ensure deep-nesting works2', + function(tt) { + //assert + const expectedCleanValue = 'bar'; + const expectedSetValue = 'bar'; + + //this is the "atom" - it should remain unchanged + tt.equals(util.sanitized(atomPattern.template), expectedCleanValue); + tt.equals( + util.sanitized(atomPattern.extendedTemplate), + expectedCleanValue + ); + + //this is the "template pattern" - it should have an updated extendedTemplate but an unchanged template + tt.equals( + util.sanitized(templatePattern.template), + '{{> test-bar }}' + ); + tt.equals( + util.sanitized(templatePattern.extendedTemplate), + expectedSetValue + ); + + //this is the "pages pattern" - it should have an updated extendedTemplate equal to the template pattern but an unchanged template + tt.equals(util.sanitized(pagesPattern.template), '{{> test-foo }}'); + tt.equals( + util.sanitized(pagesPattern.extendedTemplate), + expectedSetValue + ); + tt.end(); + test.end(); + } + ); + }); + }) + .catch(tap.threw); + +tap.test('hidden patterns can be called by their nice names', function(test) { + //arrange + const patternlab = util.fakePatternLab(patterns_dir); + + //act + var hiddenPatternPath = path.join('00-test', '_00-hidden-pattern.mustache'); + var hiddenPattern = loadPattern(hiddenPatternPath, patternlab); + + var testPatternPath = path.join( + '00-test', + '15-hidden-pattern-tester.mustache' + ); + var testPattern = loadPattern(testPatternPath, patternlab); + + var p1 = processIterative(hiddenPattern, patternlab); + var p2 = processIterative(testPattern, patternlab); + + Promise.all([p1, p2]).then(() => { + //act + processRecursive(hiddenPatternPath, patternlab).then(() => { + processRecursive(testPatternPath, patternlab).then(() => { + testPattern.render().then(results => { + //assert + test.equals( + util.sanitized(results), + util.sanitized( + "Hello there! Here's the hidden atom: [This is the hidden atom]" + ), + 'hidden pattern rendered output not as expected' + ); + test.end(); + }); + }); + }); + }); +}); + +tap.test('parses pattern title correctly when frontmatter present', function( + test +) { + //arrange + var pl = util.fakePatternLab(patterns_dir); + + var testPatternPath = path.join('00-test', '01-bar.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //act + Promise.all([ + processIterative(testPattern, pl), + processRecursive(testPatternPath, pl), + ]) + .then(results => { + //assert + test.equals( + results[0].patternName, + 'An Atom Walks Into a Bar', + 'patternName not overridden' + ); + test.end(); + }) + .catch(test.threw); +}); + +tap.test( + 'parses pattern extra frontmatter correctly when frontmatter present', + function(test) { + //arrange + var pl = util.fakePatternLab(patterns_dir); + + var testPatternPath = path.join('00-test', '01-bar.mustache'); + var testPattern = loadPattern(testPatternPath, pl); + + //act + Promise.all([ + processIterative(testPattern, pl), + processRecursive(testPatternPath, pl), + ]) + .then(results => { + //assert + test.equals(results[0].allMarkdown.joke, 'bad', 'extra key not added'); + test.end(); + }) + .catch(test.threw); + } +); diff --git a/packages/core/test/pseudopattern_hunter_tests.js b/packages/core/test/pseudopattern_hunter_tests.js new file mode 100644 index 000000000..1464f28b4 --- /dev/null +++ b/packages/core/test/pseudopattern_hunter_tests.js @@ -0,0 +1,135 @@ +'use strict'; + +var tap = require('tap'); + +var path = require('path'); +var pph = require('../src/lib/pseudopattern_hunter'); + +var loadPattern = require('../src/lib/loadPattern'); +var Pattern = require('../src/lib/object_factory').Pattern; +var PatternGraph = require('../src/lib/pattern_graph').PatternGraph; +const addPattern = require('../src/lib/addPattern'); + +var config = require('./util/patternlab-config.json'); +var engineLoader = require('../src/lib/pattern_engines'); +engineLoader.loadAllEngines(config); + +var fs = require('fs-extra'); +var patterns_dir = `${__dirname}/files/_patterns/`; +var public_patterns_dir = `${__dirname}/test/public/patterns`; + +function stubPatternlab() { + var pl = {}; + pl.graph = PatternGraph.empty(); + pl.config = { + paths: { + source: { + patterns: patterns_dir, + }, + public: { + patterns: public_patterns_dir, + }, + }, + }; + pl.data = {}; + pl.data.link = {}; + pl.config.logLevel = 'quiet'; + pl.patterns = []; + pl.partials = {}; + pl.config.patternStates = {}; + pl.config.outputFileSuffixes = { rendered: '' }; + + return pl; +} + +tap.test('pseudpattern found and added as a pattern', function(test) { + //arrange + var pl = stubPatternlab(); + + var atomPattern = loadPattern('00-test/03-styled-atom.mustache', pl); + addPattern(atomPattern, pl); + + //act + var patternCountBefore = pl.patterns.length; + return pph.find_pseudopatterns(atomPattern, pl).then(() => { + //assert + test.equals(patternCountBefore + 1, pl.patterns.length); + test.equals(pl.patterns[1].patternPartial, 'test-styled-atom-alt'); + test.equals( + JSON.stringify(pl.patterns[1].jsonFileData), + JSON.stringify({ message: 'alternateMessage' }) + ); + test.equals( + pl.patterns[1].patternLink, + '00-test-03-styled-atom-alt' + + path.sep + + '00-test-03-styled-atom-alt.html' + ); + }); +}); + +tap.test('pseudpattern does not pollute base pattern data', function(test) { + //arrange + var pl = stubPatternlab(); + + var atomPattern = loadPattern('00-test/03-styled-atom.mustache', pl); + + //act + var patternCountBefore = pl.patterns.length; + return pph.find_pseudopatterns(atomPattern, pl).then(() => { + //assert + test.equals(pl.patterns[0].patternPartial, 'test-styled-atom'); + test.equals( + JSON.stringify(pl.patterns[0].jsonFileData), + JSON.stringify({ message: 'baseMessage' }) + ); + }); +}); + +tap.test( + 'pseudpattern variant includes stylePartials and parameteredPartials', + function(test) { + //arrange + var pl = stubPatternlab(); + + var atomPattern = new Pattern('00-test/03-styled-atom.mustache'); + atomPattern.template = fs.readFileSync( + patterns_dir + '00-test/03-styled-atom.mustache', + 'utf8' + ); + atomPattern.extendedTemplate = atomPattern.template; + atomPattern.stylePartials = atomPattern.findPartialsWithStyleModifiers( + atomPattern + ); + atomPattern.parameteredPartials = atomPattern.findPartialsWithPatternParameters( + atomPattern + ); + + var pseudoPattern = new Pattern('00-test/474-pseudomodifier.mustache'); + pseudoPattern.template = fs.readFileSync( + patterns_dir + '00-test/474-pseudomodifier.mustache', + 'utf8' + ); + pseudoPattern.extendedTemplate = atomPattern.template; + pseudoPattern.stylePartials = pseudoPattern.findPartialsWithStyleModifiers( + pseudoPattern + ); + pseudoPattern.parameteredPartials = pseudoPattern.findPartialsWithPatternParameters( + pseudoPattern + ); + + addPattern(atomPattern, pl); + addPattern(pseudoPattern, pl); + + //act + return pph.find_pseudopatterns(pseudoPattern, pl).then(() => { + //assert + test.equals(pl.patterns[2].patternPartial, 'test-pseudomodifier-test'); + test.equals(pl.patterns[2].stylePartials, pseudoPattern.stylePartials); + test.equals( + pl.patterns[2].parameteredPartials, + pseudoPattern.parameteredPartials + ); + }); + } +); diff --git a/packages/core/test/public/testDependencyGraph.json b/packages/core/test/public/testDependencyGraph.json new file mode 100644 index 000000000..47d86ede1 --- /dev/null +++ b/packages/core/test/public/testDependencyGraph.json @@ -0,0 +1 @@ +{"version":1,"timestamp":1337,"graph":{"options":{"directed":true,"multigraph":false,"compound":false},"nodes":[{"v":"atom-foo","value":{"compileState":"clean"}},{"v":"molecule-foo","value":{"compileState":"clean"}}],"edges":[{"v":"molecule-foo","w":"atom-foo","value":{}}]}} diff --git a/packages/core/test/replaceParameter_tests.js b/packages/core/test/replaceParameter_tests.js new file mode 100644 index 000000000..41fde31c1 --- /dev/null +++ b/packages/core/test/replaceParameter_tests.js @@ -0,0 +1,49 @@ +'use strict'; + +const path = require('path'); +const util = require('./util/test_utils.js'); +const tap = require('tap'); + +const replaceParameter = require('../src/lib/replaceParameter'); + +tap.test('replaces simple value', function(test) { + const result = replaceParameter('{{key}}', 'key', 'value'); + test.equals(result, 'value'); + test.end(); +}); + +tap.test('replaces simple boolean true value', function(test) { + const result = replaceParameter('{{key}}', 'key', true); + test.equals(result, 'true'); + test.end(); +}); + +tap.test('replaces simple boolean false value', function(test) { + const result = replaceParameter('{{key}}', 'key', false); + test.equals(result, 'false'); + test.end(); +}); + +tap.test('replaces raw value', function(test) { + const result = replaceParameter('{{{key}}}', 'key', 'value'); + test.equals(result, 'value'); + test.end(); +}); + +tap.test('replaces boolean true section', function(test) { + const result = replaceParameter('1{{#key}}value{{/key}}2', 'key', true); + test.equals(result, '1value2'); + test.end(); +}); + +tap.only('replaces boolean true section with spaces', function(test) { + const result = replaceParameter('1{{ #key }}value{{ /key }}2', 'key', true); + test.equals(result, '1value2'); + test.end(); +}); + +tap.test('replaces boolean section false', function(test) { + const result = replaceParameter('1{{#key}}value{{/key}}2', 'key', false); + test.equals(result, '12'); + test.end(); +}); diff --git a/packages/core/test/style_modifier_hunter_tests.js b/packages/core/test/style_modifier_hunter_tests.js new file mode 100644 index 000000000..bdbc1e994 --- /dev/null +++ b/packages/core/test/style_modifier_hunter_tests.js @@ -0,0 +1,110 @@ +'use strict'; + +var tap = require('tap'); + +var smh = require('../src/lib/style_modifier_hunter'); + +tap.test( + 'uses the partial stylemodifer to modify the patterns extendedTemplate', + function(test) { + //arrange + var pl = {}; + pl.partials = {}; + pl.config = {}; + pl.config.logLevel = 'quiet'; + + var pattern = { + extendedTemplate: '
', + }; + + var style_modifier_hunter = new smh(); + + //act + style_modifier_hunter.consume_style_modifier( + pattern, + '{{> partial:bar}}', + pl + ); + + //assert + test.equals(pattern.extendedTemplate, '
'); + test.end(); + } +); + +tap.test('replaces style modifiers with spaces in the syntax', function(test) { + //arrange + var pl = {}; + pl.partials = {}; + pl.config = {}; + pl.config.logLevel = 'quiet'; + + var pattern = { + extendedTemplate: '
', + }; + + var style_modifier_hunter = new smh(); + + //act + style_modifier_hunter.consume_style_modifier( + pattern, + '{{> partial:bar}}', + pl + ); + + //assert + test.equals(pattern.extendedTemplate, '
'); + test.end(); +}); + +tap.test('replaces multiple style modifiers', function(test) { + //arrange + var pl = {}; + pl.partials = {}; + pl.config = {}; + pl.config.logLevel = 'quiet'; + + var pattern = { + extendedTemplate: '
', + }; + + var style_modifier_hunter = new smh(); + + //act + style_modifier_hunter.consume_style_modifier( + pattern, + '{{> partial:bar|baz|dum}}', + pl + ); + + //assert + test.equals(pattern.extendedTemplate, '
'); + test.end(); +}); + +tap.test( + 'does not alter pattern extendedTemplate if styleModifier not found in partial', + function(test) { + //arrange + var pl = {}; + pl.partials = {}; + pl.config = {}; + pl.config.logLevel = 'quiet'; + + var pattern = { + extendedTemplate: '
', + }; + + var style_modifier_hunter = new smh(); + + //act + style_modifier_hunter.consume_style_modifier(pattern, '{{> partial}}', pl); + + //assert + test.equals( + pattern.extendedTemplate, + '
' + ); + test.end(); + } +); diff --git a/packages/core/test/ui_builder_tests.js b/packages/core/test/ui_builder_tests.js new file mode 100644 index 000000000..607d9fd95 --- /dev/null +++ b/packages/core/test/ui_builder_tests.js @@ -0,0 +1,585 @@ +'use strict'; + +var tap = require('tap'); +var rewire = require('rewire'); +var _ = require('lodash'); +var eol = require('os').EOL; +var Pattern = require('../src/lib/object_factory').Pattern; +var extend = require('util')._extend; +var uiModule = rewire('../src/lib/ui_builder'); +var path = require('path'); +var config = require('./util/patternlab-config.json'); + +var engineLoader = require('../src/lib/pattern_engines'); +engineLoader.loadAllEngines(config); + +//set up a global mocks - we don't want to be writing/rendering any files right now +var fsMock = { + outputFileSync: function(path, data, cb) {}, + outputFile: function(path, data, cb) {}, +}; + +var renderMock = function(template, data, partials) { + return Promise.resolve(''); +}; +var buildFooterMock = function(patternlab, patternPartial) { + return Promise.resolve(''); +}; + +//set our mocks in place of usual require() +uiModule.__set__({ + fs: fsMock, + render: renderMock, + buildFooter: buildFooterMock, +}); + +const uikit = { + name: 'uikit-workshop', + modulePath: '', + outputDir: 'test/output', + excludedPatternStates: [], +}; + +var ui = uiModule(); + +function createFakePatternLab(customProps) { + var pl = { + config: { + paths: { + source: { + patterns: './test/files/_patterns', + }, + public: { + patterns: '', + }, + }, + styleGuideExcludes: ['templates'], + logLevel: 'quiet', + outputFileSuffixes: { + rendered: '.rendered', + rawTemplate: '', + markupOnly: '.markup-only', + }, + }, + data: {}, + uikits: [uikit], + }; + return extend(pl, customProps); +} + +tap.test( + 'isPatternExcluded - returns true when pattern filename starts with underscore', + function(test) { + //arrange + var patternlab = createFakePatternLab({}); + var pattern = new Pattern('00-test/_ignored-pattern.mustache'); + + //act + var result = ui.isPatternExcluded(pattern, patternlab, uikit); + + //assert + test.equals(result, true); + test.end(); + } +); + +tap.test( + 'isPatternExcluded - returns true when pattern is defaultPattern', + function(test) { + //arrange + var patternlab = createFakePatternLab({}); + var pattern = new Pattern('00-test/foo.mustache'); + patternlab.config.defaultPattern = 'test-foo'; + + //act + var result = ui.isPatternExcluded(pattern, patternlab, uikit); + + //assert + test.equals(result, true); + test.end(); + } +); + +tap.test( + 'isPatternExcluded - returns true when pattern within underscored directory - top level', + function(test) { + //arrange + var patternlab = createFakePatternLab({}); + var pattern = Pattern.createEmpty({ + relPath: + path.sep + + '_hidden' + + path.sep + + 'patternsubtype' + + path.sep + + 'foo.mustache', + isPattern: true, + fileName: 'foo.mustache', + patternPartial: 'hidden-foo', + }); + + //act + var result = ui.isPatternExcluded(pattern, patternlab, uikit); + + //assert + test.equals(result, true); + test.end(); + } +); + +tap.test( + 'isPatternExcluded - returns true when pattern within underscored directory - subtype level', + function(test) { + //arrange + var patternlab = createFakePatternLab({}); + var pattern = Pattern.createEmpty({ + relPath: + 'shown' + path.sep + '_patternsubtype' + path.sep + 'foo.mustache', + isPattern: true, + fileName: 'foo.mustache', + patternPartial: 'shown-foo', + }); + + //act + var result = ui.isPatternExcluded(pattern, patternlab, uikit); + + //assert + test.equals(result, true); + test.end(); + } +); + +tap.test( + 'isPatternExcluded - returns true when pattern state found withing uikit exclusions', + function(test) { + //arrange + var patternlab = createFakePatternLab({}); + var pattern = Pattern.createEmpty({ + relPath: + 'shown' + path.sep + '_patternsubtype' + path.sep + 'foo.mustache', + isPattern: true, + fileName: 'foo.mustache', + patternPartial: 'shown-foo', + patternState: 'complete', + }); + + //act + var result = ui.isPatternExcluded(pattern, patternlab, { + excludedPatternStates: 'complete', + }); + + //assert + test.equals(result, true); + test.end(); + } +); + +tap.test('groupPatterns - creates pattern groups correctly', function(test) { + //arrange + var patternlab = createFakePatternLab({ + patterns: [], + patternGroups: {}, + subtypePatterns: {}, + }); + + patternlab.patterns.push( + new Pattern('00-test/bar.mustache'), + 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); + + //act + var result = ui.groupPatterns(patternlab, uikit); + + test.equals( + result.patternGroups.patternType1.patternSubType1.blue.patternPartial, + 'patternType1-blue' + ); + test.equals( + result.patternGroups.patternType1.patternSubType1.red.patternPartial, + 'patternType1-red' + ); + test.equals( + result.patternGroups.patternType1.patternSubType1.yellow.patternPartial, + 'patternType1-yellow' + ); + test.equals( + result.patternGroups.patternType1.patternSubType2.black.patternPartial, + 'patternType1-black' + ); + test.equals( + result.patternGroups.patternType1.patternSubType2.grey.patternPartial, + 'patternType1-grey' + ); + test.equals( + result.patternGroups.patternType1.patternSubType2.white.patternPartial, + 'patternType1-white' + ); + + test.equals( + patternlab.patternTypes[0].patternItems[0].patternPartial, + 'test-bar', + 'first pattern item should be test-bar' + ); + test.equals( + patternlab.patternTypes[0].patternItems[1].patternPartial, + 'test-foo', + 'second pattern item should be test-foo' + ); + + //todo: patternlab.patternTypes[0].patternItems[1] looks malformed + + test.end(); +}); + +tap.test('groupPatterns - orders patterns when provided from md', function( + test +) { + //arrange + var patternlab = createFakePatternLab({ + patterns: [], + patternGroups: {}, + subtypePatterns: {}, + }); + + patternlab.patterns.push( + new Pattern('patternType1/patternSubType1/blue.mustache'), + new Pattern('patternType1/patternSubType1/red.mustache'), + new Pattern('patternType1/patternSubType1/yellow.mustache') + ); + ui.resetUIBuilderState(patternlab); + + patternlab.patterns[1].order = 1; + + //act + ui.groupPatterns(patternlab, uikit); + + let patternType = _.find(patternlab.patternTypes, [ + 'patternType', + 'patternType1', + ]); + let patternSubType = _.find(patternType.patternTypeItems, [ + 'patternSubtype', + 'patternSubType1', + ]); + 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'); + + test.end(); +}); + +tap.test( + 'groupPatterns - retains pattern order from name when order provided from md is malformed', + function(test) { + //arrange + var patternlab = createFakePatternLab({ + patterns: [], + patternGroups: {}, + subtypePatterns: {}, + }); + + patternlab.patterns.push( + new Pattern('patternType1/patternSubType1/blue.mustache'), + new Pattern('patternType1/patternSubType1/red.mustache'), + new Pattern('patternType1/patternSubType1/yellow.mustache') + ); + ui.resetUIBuilderState(patternlab); + + patternlab.patterns[1].order = 'notanumber!'; + + //act + ui.groupPatterns(patternlab, uikit); + + let patternType = _.find(patternlab.patternTypes, [ + 'patternType', + 'patternType1', + ]); + let patternSubType = _.find(patternType.patternTypeItems, [ + 'patternSubtype', + 'patternSubType1', + ]); + 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'); + + test.end(); + } +); + +tap.test( + 'groupPatterns - sorts viewall subtype pattern to the beginning', + function(test) { + //arrange + var patternlab = createFakePatternLab({ + patterns: [], + patternGroups: {}, + subtypePatterns: {}, + }); + + patternlab.patterns.push( + new Pattern('patternType1/patternSubType1/blue.mustache'), + new Pattern('patternType1/patternSubType1/red.mustache'), + new Pattern('patternType1/patternSubType1/yellow.mustache') + ); + ui.resetUIBuilderState(patternlab); + + patternlab.patterns[0].order = 1; + patternlab.patterns[1].order = 3; + patternlab.patterns[2].order = 2; + + //act + ui.groupPatterns(patternlab, uikit); + + let patternType = _.find(patternlab.patternTypes, [ + 'patternType', + 'patternType1', + ]); + let patternSubType = _.find(patternType.patternTypeItems, [ + 'patternSubtype', + 'patternSubType1', + ]); + var items = patternSubType.patternSubtypeItems; + + //zero is viewall + test.equals( + items[0].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.end(); + } +); + +tap.test( + 'groupPatterns - creates documentation patterns for each type and subtype if not exists', + function(test) { + //arrange + var patternlab = createFakePatternLab({ + patterns: [], + patternGroups: {}, + subtypePatterns: {}, + }); + + patternlab.patterns.push( + new Pattern('00-test/foo.mustache'), + new Pattern('00-test/bar.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); + + //act + var result = ui.groupPatterns(patternlab, uikit); + + //assert + test.equals( + result.patternGroups.patternType1.patternSubType1[ + 'viewall-patternType1-patternSubType1' + ].patternPartial, + 'viewall-patternType1-patternSubType1' + ); + test.equals( + result.patternGroups.patternType1.patternSubType2[ + 'viewall-patternType1-patternSubType2' + ].patternPartial, + 'viewall-patternType1-patternSubType2' + ); + + test.end(); + } +); + +tap.test( + 'groupPatterns - adds each pattern to the patternPaths object', + function(test) { + //arrange + var patternlab = createFakePatternLab({ + patterns: [], + patternGroups: {}, + subtypePatterns: {}, + }); + + patternlab.patterns.push( + new Pattern('00-test/foo.mustache'), + new Pattern('00-test/bar.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); + + //act + var result = ui.groupPatterns(patternlab, uikit); + + //assert + test.equals(patternlab.patternPaths['test']['foo'], '00-test-foo'); + test.equals(patternlab.patternPaths['test']['bar'], '00-test-bar'); + test.equals( + patternlab.patternPaths['patternType1']['blue'], + 'patternType1-patternSubType1-blue' + ); + test.equals( + patternlab.patternPaths['patternType1']['red'], + 'patternType1-patternSubType1-red' + ); + test.equals( + patternlab.patternPaths['patternType1']['yellow'], + 'patternType1-patternSubType1-yellow' + ); + test.equals( + patternlab.patternPaths['patternType1']['black'], + 'patternType1-patternSubType2-black' + ); + test.equals( + patternlab.patternPaths['patternType1']['grey'], + 'patternType1-patternSubType2-grey' + ); + test.equals( + patternlab.patternPaths['patternType1']['white'], + 'patternType1-patternSubType2-white' + ); + + test.end(); + } +); + +tap.test( + 'groupPatterns - adds each pattern to the view all paths object', + function(test) { + //arrange + var patternlab = createFakePatternLab({ + patterns: [], + patternGroups: {}, + subtypePatterns: {}, + }); + + patternlab.patterns.push( + new Pattern('00-test/foo.mustache'), + new Pattern('00-test/bar.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); + + //act + var result = ui.groupPatterns(patternlab, uikit); + + //assert + test.equals('todo', 'todo'); + + test.end(); + } +); + +tap.test('resetUIBuilderState - reset global objects', function(test) { + //arrange + var patternlab = createFakePatternLab({ + patternPaths: { foo: 1 }, + viewAllPaths: { bar: 2 }, + patternTypes: ['baz'], + }); + + //act + ui.resetUIBuilderState(patternlab); + + //assert + test.equals(patternlab.patternPaths.foo, undefined); + test.equals(patternlab.viewAllPaths.bar, undefined); + test.equals(patternlab.patternTypes.length, 0); + + test.end(); +}); + +tap.test( + 'buildViewAllPages - adds viewall page for each type and subtype', + 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 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(); + }); + } +); diff --git a/packages/core/test/uikitExcludePattern_tests.js b/packages/core/test/uikitExcludePattern_tests.js new file mode 100644 index 000000000..a31f4c3f3 --- /dev/null +++ b/packages/core/test/uikitExcludePattern_tests.js @@ -0,0 +1,53 @@ +'use strict'; + +const tap = require('tap'); + +const uikitExcludePattern = require('../src/lib/uikitExcludePattern'); + +tap.test( + 'uikitExcludePattern - returns false when uikit has no excluded states', + test => { + //arrange + const uikit = { excludedPatternStates: [] }; + const pattern = { patternState: 'complete' }; + + //act + const result = uikitExcludePattern(pattern, uikit); + + //assert + test.false(result); + test.end(); + } +); + +tap.test( + 'uikitExcludePattern - returns false pattern does not have same state as uikit exclusions', + test => { + //arrange + const uikit = { excludedPatternStates: ['complete'] }; + const pattern = { patternState: 'inprogress' }; + + //act + const result = uikitExcludePattern(pattern, uikit); + + //assert + test.false(result); + test.end(); + } +); + +tap.test( + 'uikitExcludePattern - returns true when uikit has same state as pattern', + test => { + //arrange + const uikit = { excludedPatternStates: ['inreview', 'complete'] }; + const pattern = { patternState: 'complete' }; + + //act + const result = uikitExcludePattern(pattern, uikit); + + //assert + test.true(result); + test.end(); + } +); diff --git a/packages/core/test/util/patternlab-config.json b/packages/core/test/util/patternlab-config.json new file mode 100644 index 000000000..c2edf9561 --- /dev/null +++ b/packages/core/test/util/patternlab-config.json @@ -0,0 +1,83 @@ +{ + "paths": { + "source": { + "root": "test/files/", + "patterns": "./test/files/_patterns/", + "data": "./test/files/_data/", + "meta": "./test/files/_meta/", + "styleguide": "./test/files/styleguide/", + "patternlabFiles": { + "general-header": "./test/files/partials/general-header.mustache", + "general-footer": "./test/files/partials/general-footer.mustache", + "patternSection": "./test/files/partials/patternSection.mustache", + "patternSectionSubtype": + "./test/files/partials/patternSectionSubtype.mustache", + "viewall": "./test/files/viewall.mustache" + }, + "js": "./test/files/js", + "images": "./test/files/images", + "fonts": "./test/files/fonts", + "css": "./test/files/css/" + }, + "public": { + "root": "public/", + "patterns": "public/patterns/", + "data": "public/data/", + "styleguide": "public/styleguide/", + "js": "public/js", + "images": "public/images", + "fonts": "public/fonts", + "css": "public/css" + } + }, + "styleGuideExcludes": ["templates", "pages"], + "defaultPattern": "all", + "logLevel": "quiet", + "ishControlsHide": { + "s": false, + "m": false, + "l": false, + "full": false, + "random": false, + "disco": false, + "hay": true, + "mqs": false, + "find": false, + "views-all": false, + "views-annotations": false, + "views-code": false, + "views-new": false, + "tools-all": false, + "tools-docs": false + }, + "ishMinimum": "240", + "ishMaximum": "2600", + "patternStateCascade": ["inprogress", "inreview", "complete"], + "patternStates": {}, + "patternExportPatternPartials": [], + "patternExportDirectory": "./pattern_exports/", + "patternExtension": "mustache", + "cacheBust": true, + "outputFileSuffixes": { + "rendered": ".rendered", + "rawTemplate": "", + "markupOnly": ".markup-only" + }, + "cleanOutputHtml": true, + "exportToGraphViz": false, + "cleanPublic": true, + "theme": { + "color": "dark", + "density": "compact", + "layout": "horizontal" + }, + "uikits": [ + { + "name": "uikit-workshop", + "outputDir": "packages/core/test/", + "enabled": true, + "excludedPatternStates": [], + "excludedTags": [] + } + ] +} diff --git a/packages/core/test/util/test_utils.js b/packages/core/test/util/test_utils.js new file mode 100644 index 000000000..36d3f6e1d --- /dev/null +++ b/packages/core/test/util/test_utils.js @@ -0,0 +1,59 @@ +'use strict'; + +var PatternGraph = require('./../../src/lib/pattern_graph').PatternGraph; + +module.exports = { + // fake pattern lab constructor: + // sets up a fake patternlab object, which is needed by the pattern processing + // apparatus. + fakePatternLab: (testPatternsPath, extraData) => { + var fpl = { + graph: PatternGraph.empty(), + partials: {}, + patterns: [], + subtypePatterns: {}, + footer: '', + header: '', + listitems: {}, + data: { + link: {}, + }, + config: require('../../patternlab-config.json'), + package: {}, + }; + + // patch the pattern source so the pattern assembler can correctly determine + // the "subdir" + fpl.config.paths.source.patterns = testPatternsPath; + + return Object.assign({}, fpl, extraData); + }, + + /** + * Strip out control characters from output if needed so make comparisons easier + * @param output - the template to strip + */ + sanitized: outputTemplate => { + return outputTemplate + .replace(/\n/g, ' ') + .replace(/\r/g, ' ') + .replace(/\s\s+/g, ' ') + .trim(); + }, + + /** + * normalize a string (probably a path) to posix - style + * @param s - the string or array of strings to normalize path separators to posix - style + */ + posixPath: s => { + if (Array.isArray(s)) { + var paths = []; + for (let i = 0; i < s.length; i++) { + paths.push(s[i].replace(/\\/g, '/')); + } + return paths; + } else { + return s.replace(/\\/g, '/'); + } + }, +}; diff --git a/packages/core/test/watchAssets_tests.js b/packages/core/test/watchAssets_tests.js new file mode 100644 index 000000000..d72cd0715 --- /dev/null +++ b/packages/core/test/watchAssets_tests.js @@ -0,0 +1,53 @@ +'use strict'; + +const _ = require('lodash'); +const tap = require('tap'); +const rewire = require('rewire'); +const path = require('path'); + +const util = require('./util/test_utils.js'); +const watchAssets = rewire('../src/lib/watchAssets'); + +const patterns_dir = './test/files/_patterns'; + +tap.test( + 'watchAssets - adds assetWatcher to patternlab.watchers for given key ', + test => { + const pl = util.fakePatternLab(patterns_dir, { watchers: [] }); + const key = 'images'; + + watchAssets( + pl, + '/foo', + { source: '/images', public: '/images' }, + key, + {}, + true + ); + + test.equals(_.keys(pl.watchers)[0], 'images'); + + test.end(); + } +); + +tap.test('watchAssets - complete path copied', test => { + const copyFileMock = function(p, des) { + test.equals(des, path.resolve('/proj/public/images/sample/waterfall.jpg')); + }; + + //set our mocks in place of usual require() + watchAssets.__set__({ + copyFile: copyFileMock, + }); + + const onWatchTripped = watchAssets.__get__('onWatchTripped'); + onWatchTripped( + '/proj/source/images/sample/waterfall.jpg', + '/proj/source/images', + '/proj', + { public: '/proj/public/images' }, + {} + ); + test.end(); +}); diff --git a/packages/core/test/watchPatternLabFiles_tests.js b/packages/core/test/watchPatternLabFiles_tests.js new file mode 100644 index 000000000..f61b873e6 --- /dev/null +++ b/packages/core/test/watchPatternLabFiles_tests.js @@ -0,0 +1,44 @@ +'use strict'; + +const _ = require('lodash'); +const tap = require('tap'); +const rewire = require('rewire'); +const path = require('path'); + +const util = require('./util/test_utils.js'); +const watchPatternLabFiles = rewire('../src/lib/watchPatternLabFiles'); + +const patterns_dir = './test/files/_patterns'; + +tap.test( + 'watchPatternLabFiles - adds watcher to patternlab.watchers for given patternWatchPath', + test => { + const pl = util.fakePatternLab(patterns_dir, { + watchers: [], + engines: {}, + }); + + pl.engines.getSupportedFileExtensions = () => { + return ['.mustache']; + }; + + watchPatternLabFiles( + pl, + { + source: { + data: '_data', + meta: '_meta', + patterns: 'patterns', + }, + }, + '/foo', + true + ); + + // should have two for _data and _meta + // should have five for '.json', '.yml', '.yaml', '.md' and '.mustache' + test.equals(Object.keys(pl.watchers).length, 7); + + test.end(); + } +); diff --git a/packages/create/CHANGELOG.md b/packages/create/CHANGELOG.md new file mode 100644 index 000000000..f964faba6 --- /dev/null +++ b/packages/create/CHANGELOG.md @@ -0,0 +1,100 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [5.9.3](https://github.com/pattern-lab/patternlab-node/compare/v5.9.2...v5.9.3) (2020-05-01) + +**Note:** Version bump only for package create-pattern-lab + + + + + +## [5.9.1](https://github.com/pattern-lab/patternlab-node/compare/v5.9.0...v5.9.1) (2020-04-24) + +**Note:** Version bump only for package create-pattern-lab + + + + + +# [5.9.0](https://github.com/pattern-lab/patternlab-node/compare/v5.8.0...v5.9.0) (2020-04-24) + +**Note:** Version bump only for package create-pattern-lab + + + + + +# [5.7.0](https://github.com/pattern-lab/patternlab-node/compare/v5.6.0...v5.7.0) (2020-02-17) + +**Note:** Version bump only for package create-pattern-lab + + + + + + +# [5.4.0](https://github.com/pattern-lab/patternlab-node/compare/v5.3.3...v5.4.0) (2019-11-26) + +**Note:** Version bump only for package create-pattern-lab + + + + + +# [5.3.0](https://github.com/pattern-lab/patternlab-node/compare/v5.2.0...v5.3.0) (2019-11-13) + +**Note:** Version bump only for package create-pattern-lab + + + + + +# [5.1.0](https://github.com/pattern-lab/patternlab-node/compare/v5.0.2...v5.1.0) (2019-10-29) + +**Note:** Version bump only for package create-pattern-lab + + + + + +# [5.0.0](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25) + +**Note:** Version bump only for package create-pattern-lab + + + + + + +## [1.0.11](https://github.com/pattern-lab/patternlab-node/compare/create-pattern-lab@1.0.10...create-pattern-lab@1.0.11) (2019-10-14) + +**Note:** Version bump only for package create-pattern-lab + + + + + + +## [1.0.8](https://github.com/pattern-lab/patternlab-node/compare/create-pattern-lab@1.0.7...create-pattern-lab@1.0.8) (2019-08-23) + +**Note:** Version bump only for package create-pattern-lab + + + + + +## [1.0.7](https://github.com/pattern-lab/patternlab-node/compare/create-pattern-lab@1.0.6...create-pattern-lab@1.0.7) (2019-08-23) + +**Note:** Version bump only for package create-pattern-lab + + + + + + +## [1.0.6](https://github.com/sghoweri/patternlab-node/compare/create-pattern-lab@1.0.5...create-pattern-lab@1.0.6) (2019-05-16) + +**Note:** Version bump only for package create-pattern-lab diff --git a/packages/create/README.md b/packages/create/README.md new file mode 100644 index 000000000..53e6f5770 --- /dev/null +++ b/packages/create/README.md @@ -0,0 +1,16 @@ +# Create Pattern Lab + +To get started, simply run: + +```bash +npm create pattern-lab +``` + +Then follow the prompts. + +This is the same as using the main Pattern Lab CLI's `init` command: + +```bash +npm i -g @pattern-lab/cli +pattern-lab init +``` diff --git a/packages/create/index.js b/packages/create/index.js new file mode 100755 index 000000000..ac811a7ba --- /dev/null +++ b/packages/create/index.js @@ -0,0 +1,4 @@ +#! /usr/bin/env node +const init = require('@pattern-lab/cli/bin/cli-actions/init'); + +init({}); diff --git a/packages/create/package.json b/packages/create/package.json new file mode 100644 index 000000000..e09874fee --- /dev/null +++ b/packages/create/package.json @@ -0,0 +1,16 @@ +{ + "name": "create-pattern-lab", + "version": "5.9.3", + "description": "", + "bin": "index.js", + "main": "index.js", + "scripts": {}, + "dependencies": { + "@pattern-lab/cli": "^5.9.3" + }, + "author": "", + "license": "MIT", + "publishConfig": { + "access": "public" + } +} diff --git a/packages/development-edition-engine-handlebars/.gitignore b/packages/development-edition-engine-handlebars/.gitignore new file mode 100644 index 000000000..0679bd2b5 --- /dev/null +++ b/packages/development-edition-engine-handlebars/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +.DS_Store +patternlab.json +.sass-cache/* +/sass-cache +Thumbs.db +.idea/ +public +dependencyGraph.json diff --git a/packages/development-edition-engine-handlebars/.npmrc b/packages/development-edition-engine-handlebars/.npmrc new file mode 100644 index 000000000..ce73f58bf --- /dev/null +++ b/packages/development-edition-engine-handlebars/.npmrc @@ -0,0 +1,2 @@ +package-lock=false +save-exact=true diff --git a/packages/development-edition-engine-handlebars/.nvmrc b/packages/development-edition-engine-handlebars/.nvmrc new file mode 100644 index 000000000..7f976a5ae --- /dev/null +++ b/packages/development-edition-engine-handlebars/.nvmrc @@ -0,0 +1 @@ +12.12.0 diff --git a/packages/development-edition-engine-handlebars/CHANGELOG.md b/packages/development-edition-engine-handlebars/CHANGELOG.md new file mode 100644 index 000000000..2aba60593 --- /dev/null +++ b/packages/development-edition-engine-handlebars/CHANGELOG.md @@ -0,0 +1,256 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [5.9.3](https://github.com/pattern-lab/patternlab-node/compare/v5.9.2...v5.9.3) (2020-05-01) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +## [5.9.2](https://github.com/pattern-lab/patternlab-node/compare/v5.9.1...v5.9.2) (2020-04-24) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +## [5.9.1](https://github.com/pattern-lab/patternlab-node/compare/v5.9.0...v5.9.1) (2020-04-24) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +# [5.9.0](https://github.com/pattern-lab/patternlab-node/compare/v5.8.0...v5.9.0) (2020-04-24) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +## [5.7.2](https://github.com/pattern-lab/patternlab-node/compare/v5.7.1...v5.7.2) (2020-03-24) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +## [5.7.1](https://github.com/pattern-lab/patternlab-node/compare/v5.7.0...v5.7.1) (2020-02-24) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +# [5.7.0](https://github.com/pattern-lab/patternlab-node/compare/v5.6.0...v5.7.0) (2020-02-17) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +# [5.6.0](https://github.com/pattern-lab/patternlab-node/compare/v5.5.0...v5.6.0) (2020-01-18) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + + +# [5.5.0](https://github.com/pattern-lab/patternlab-node/compare/v5.4.2...v5.5.0) (2019-12-19) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +## [5.4.2](https://github.com/pattern-lab/patternlab-node/compare/v5.4.1...v5.4.2) (2019-11-27) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +## [5.4.1](https://github.com/pattern-lab/patternlab-node/compare/v5.4.0...v5.4.1) (2019-11-26) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +# [5.4.0](https://github.com/pattern-lab/patternlab-node/compare/v5.3.3...v5.4.0) (2019-11-26) + + +### Features + +* major improvements to local UIKit workflow ([4dc9173](https://github.com/pattern-lab/patternlab-node/commit/4dc9173a5a44b422e9677824de3728048b7c4f05)) + + + + + +## [5.3.3](https://github.com/pattern-lab/patternlab-node/compare/v5.3.2...v5.3.3) (2019-11-22) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + + +## [5.3.2](https://github.com/pattern-lab/patternlab-node/compare/v5.3.1...v5.3.2) (2019-11-14) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +## [5.3.1](https://github.com/pattern-lab/patternlab-node/compare/v5.3.0...v5.3.1) (2019-11-13) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +# [5.3.0](https://github.com/pattern-lab/patternlab-node/compare/v5.2.0...v5.3.0) (2019-11-13) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +# [5.2.0](https://github.com/pattern-lab/patternlab-node/compare/v5.1.0...v5.2.0) (2019-11-12) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + + +# [5.1.0](https://github.com/pattern-lab/patternlab-node/compare/v5.0.2...v5.1.0) (2019-10-29) + + +### Features + +* **config:** add new default pattern export options ([a7487a0](https://github.com/pattern-lab/patternlab-node/commit/a7487a0681cb11e6f3c5c8eaefd62e5648ad5ea3)) + + + + + +## [5.0.2](https://github.com/pattern-lab/patternlab-node/compare/v5.0.1...v5.0.2) (2019-10-28) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +## [5.0.1](https://github.com/pattern-lab/patternlab-node/compare/v5.0.0...v5.0.1) (2019-10-28) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +# [5.0.0](https://github.com/pattern-lab/patternlab-node/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25) + + +### Bug Fixes + +* Rename Handlebars and Nunjucks extension setting to "extend" ([74e5af2](https://github.com/pattern-lab/patternlab-node/commit/74e5af28c4e714fdfc1db535b94c52f3dc14a3a4)) +* update the default pattern that displays in the Handlebars demo ([ff1d85f](https://github.com/pattern-lab/patternlab-node/commit/ff1d85f2852fc4f210841e8e0aaf14b55165ce58)) + + +### Features + +* **engine-handlebars:** Demonstration of custom Handlebars helper ([f330b5b](https://github.com/pattern-lab/patternlab-node/commit/f330b5bca72f2f34bfafe5c2c64e6b0b8823eb1c)) +* **plugin-tab, core:** initial plugin hook exploration ([2f3d39a](https://github.com/pattern-lab/patternlab-node/commit/2f3d39ac6b125ad4c6b872e27ee224ce2ea33a12)) +* introduce netlify preview ([6c5d332](https://github.com/pattern-lab/patternlab-node/commit/6c5d332479fb6836bd8bd5530a074d13440f8ae4)) + + + + + + +## [0.1.6](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/development-edition-engine-handlebars@0.1.5...@pattern-lab/development-edition-engine-handlebars@0.1.6) (2019-10-14) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + + +## [0.1.5](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/development-edition-engine-handlebars@0.1.4...@pattern-lab/development-edition-engine-handlebars@0.1.5) (2019-10-14) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + + +## [0.1.1](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/development-edition-engine-handlebars@0.1.0...@pattern-lab/development-edition-engine-handlebars@0.1.1) (2019-08-23) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +# [0.1.0](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/development-edition-engine-handlebars@0.0.3...@pattern-lab/development-edition-engine-handlebars@0.1.0) (2019-08-23) + + +### Bug Fixes + +* Rename Handlebars and Nunjucks extension setting to "extend" ([74e5af2](https://github.com/pattern-lab/patternlab-node/commit/74e5af2)) + + +### Features + +* **engine-handlebars:** Demonstration of custom Handlebars helper ([f330b5b](https://github.com/pattern-lab/patternlab-node/commit/f330b5b)) + + + + + + +## [0.0.3](https://github.com/pattern-lab/patternlab-node/compare/@pattern-lab/development-edition-engine-handlebars@0.0.3-alpha.0...@pattern-lab/development-edition-engine-handlebars@0.0.3) (2019-05-16) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-handlebars + + + + + +## 0.0.1-beta.0 (2019-02-09) + + +### Bug Fixes + +* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/patternlab-node/commit/98dfadf)) + + +### Features + +* **package:** add handlebars development edition ([454095d](https://github.com/pattern-lab/patternlab-node/commit/454095d)) diff --git a/packages/development-edition-engine-handlebars/README.md b/packages/development-edition-engine-handlebars/README.md new file mode 100644 index 000000000..5bb88ff96 --- /dev/null +++ b/packages/development-edition-engine-handlebars/README.md @@ -0,0 +1,28 @@ +![Pattern Lab Logo](/patternlab.png "Pattern Lab Logo") + +# Pattern Lab Node - Development Edition Engine Handlebars + +_here be dragons_ + +This Development Edition is a variant of [Edition Node](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node) for convience purposes only, loaded with the Handlebars Engine. The goals of this Development Edition are two-fold: + +* Develop the [Handlebars Engine](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-handlebars) +* Build and test against Handlebars pattern tree + +> Development Editions of Pattern Lab provide the ability to work on and commit changes to select packages within the overall Pattern Lab [ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). This Edition is NOT stable. + + +## Working on Pattern Lab's UI Locally + +### Step 1: Install Dependencies +Run the following in the root of the Pattern Lab repo: + +``` +yarn run setup +``` + +### Step 2 (Optional) +If you want to build using a fuller set of examples than what comes with this default Handlebars demo, run `yarn run preview:hbs`. Otherwise skip to step 3. + +### Step 3 +Finally, go back into this folder, `cd packages/development-edition-engine-handlebars`, and start up the local dev server which watches UIKit and the local Pattern Lab instance for changes, live reloads, etc by running `yarn dev` diff --git a/packages/development-edition-engine-handlebars/helpers/test.js b/packages/development-edition-engine-handlebars/helpers/test.js new file mode 100644 index 000000000..8b32ed799 --- /dev/null +++ b/packages/development-edition-engine-handlebars/helpers/test.js @@ -0,0 +1,5 @@ +module.exports = function(Handlebars) { + Handlebars.registerHelper('test', function() { + return 'This is a test helper'; + }); +}; diff --git a/packages/development-edition-engine-handlebars/package.json b/packages/development-edition-engine-handlebars/package.json new file mode 100644 index 000000000..6ede6103f --- /dev/null +++ b/packages/development-edition-engine-handlebars/package.json @@ -0,0 +1,39 @@ +{ + "name": "@pattern-lab/development-edition-engine-handlebars", + "private": true, + "version": "5.9.3", + "description": "The tree of components we use to test, develop and validate the Handlebars engine", + "scripts": { + "pl:build": "patternlab build --config ./patternlab-config.json", + "pl:help": "patternlab --help", + "pl:install": "patternlab install --config ./patternlab-config.json", + "pl:serve": "patternlab serve --config ./patternlab-config.json", + "pl:version": "patternlab --version", + "dev": "node ./node_modules/@pattern-lab/uikit-workshop/build-tools.js" + }, + "keywords": [ + "Pattern Lab", + "Atomic Web Design", + "Node", + "Handlebars", + "Edition" + ], + "author": "Brian Muenzenmeye", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/pattern-lab/patternlab-node.git" + }, + "engines": { + "node": ">=12.12.0" + }, + "dependencies": { + "@pattern-lab/cli": "^5.9.3", + "@pattern-lab/core": "^5.9.3", + "@pattern-lab/engine-handlebars": "^5.5.0", + "@pattern-lab/engine-mustache": "^5.0.0", + "@pattern-lab/plugin-tab": "^5.9.3", + "@pattern-lab/starterkit-mustache-demo": "^5.0.0", + "@pattern-lab/uikit-workshop": "^5.9.3" + } +} diff --git a/packages/development-edition-engine-handlebars/patternlab-config.json b/packages/development-edition-engine-handlebars/patternlab-config.json new file mode 100644 index 000000000..5b9e9a0c2 --- /dev/null +++ b/packages/development-edition-engine-handlebars/patternlab-config.json @@ -0,0 +1,107 @@ +{ + "cacheBust": true, + "cleanPublic": true, + "defaultPattern": "all", + "defaultShowPatternInfo": false, + "ishControlsHide": { + "s": false, + "m": false, + "l": false, + "full": false, + "random": true, + "disco": true, + "hay": true, + "mqs": true, + "find": true, + "views-all": false, + "views-annotations": false, + "views-code": false, + "views-new": false, + "tools-all": false, + "tools-docs": false + }, + "ishViewportRange": { + "s": [240, 500], + "m": [500, 800], + "l": [800, 2600] + }, + "logLevel": "info", + "outputFileSuffixes": { + "rendered": ".rendered", + "rawTemplate": "", + "markupOnly": ".markup-only" + }, + "paths": { + "source": { + "root": "source/", + "patterns": "source/_patterns/", + "data": "source/_data/", + "meta": "source/_meta/", + "annotations": "source/_annotations/", + "styleguide": "dist/", + "patternlabFiles": { + "general-header": "views/partials/general-header.mustache", + "general-footer": "views/partials/general-footer.mustache", + "patternSection": "views/partials/patternSection.mustache", + "patternSectionSubtype": "views/partials/patternSectionSubtype.mustache", + "viewall": "views/viewall.mustache" + }, + "js": "source/js", + "images": "source/images", + "fonts": "source/fonts", + "css": "source/css" + }, + "public": { + "root": "public/", + "patterns": "public/patterns/", + "data": "public/styleguide/data/", + "annotations": "public/annotations/", + "styleguide": "public/styleguide/", + "js": "public/js", + "images": "public/images", + "fonts": "public/fonts", + "css": "public/css" + } + }, + "patternExtension": "hbs", + "patternStateCascade": ["inprogress", "inreview", "complete"], + "patternExportAll": false, + "patternExportDirectory": "pattern_exports", + "patternExportPatternPartials": [], + "patternExportPreserveDirectoryStructure": true, + "patternExportRaw": false, + "serverOptions": { + "wait": 1000 + }, + "starterkitSubDir": "dist", + "styleGuideExcludes": [], + "theme": { + "color": "dark", + "density": "compact", + "layout": "horizontal", + "noViewAll": false + }, + "uikits": [ + { + "name": "uikit-workshop", + "outputDir": "", + "enabled": true, + "excludedPatternStates": [], + "excludedTags": [] + } + ], + "engines": { + "handlebars": { + "extend": "helpers/*.js" + } + }, + "plugins": { + "@pattern-lab/plugin-tab": { + "enabled": true, + "initialized": false, + "options": { + "tabsToAdd": ["scss"] + } + } + } +} diff --git a/packages/development-edition-engine-handlebars/source/_annotations/annotations.json b/packages/development-edition-engine-handlebars/source/_annotations/annotations.json new file mode 100644 index 000000000..a0d0268f8 --- /dev/null +++ b/packages/development-edition-engine-handlebars/source/_annotations/annotations.json @@ -0,0 +1,3 @@ +{ + "comments": [] +} diff --git a/packages/development-edition-engine-handlebars/source/_data/data.json b/packages/development-edition-engine-handlebars/source/_data/data.json new file mode 100644 index 000000000..250376db0 --- /dev/null +++ b/packages/development-edition-engine-handlebars/source/_data/data.json @@ -0,0 +1,34 @@ +{ + "version": "2", + "swatches": [ + { + "color": { + "hex": "#031636", + "cmyk": "94 59 0 79" + }, + "label": "dark blue" + }, + { + "color": { + "hex": "#0D80F0", + "cmyk": "95 47 0 6" + }, + "label": "light blue" + }, + { + "color": { + "hex": "#4c4c4c", + "cmyk": "0 0 0 70" + }, + "label": "dark grey" + }, + { + "color": { + "hex": "#b2b2b2", + "cmyk": "0 0 0 30" + }, + "label": "light grey", + "inverted": true + } + ] +} diff --git a/packages/development-edition-engine-handlebars/source/_data/listitems.json b/packages/development-edition-engine-handlebars/source/_data/listitems.json new file mode 100644 index 000000000..c35d1076d --- /dev/null +++ b/packages/development-edition-engine-handlebars/source/_data/listitems.json @@ -0,0 +1,6 @@ +{ + "1": {}, + "2": {}, + "3": {}, + "4": {} +} diff --git a/packages/development-edition-engine-handlebars/source/_meta/README.md b/packages/development-edition-engine-handlebars/source/_meta/README.md new file mode 100644 index 000000000..c6c8c3b8e --- /dev/null +++ b/packages/development-edition-engine-handlebars/source/_meta/README.md @@ -0,0 +1,5 @@ +This is the default location to place meta files, otherwise known a pattern's header and footer. + +Pattern Lab builds each pattern while prepending and appending the header and footer. Read more about [pattern headers and footers](http://patternlab.io/docs/pattern-header-footer.html). + +If you wish to rename this directory, make sure you update the `paths.source.meta` property within `patternlab-config.json`. diff --git a/packages/development-edition-engine-handlebars/source/_meta/_00-head.hbs b/packages/development-edition-engine-handlebars/source/_meta/_00-head.hbs new file mode 100644 index 000000000..9058b6521 --- /dev/null +++ b/packages/development-edition-engine-handlebars/source/_meta/_00-head.hbs @@ -0,0 +1,19 @@ + + + + + {{ title }} + + + + + + + + + {{{ patternLabHead }}} + + + + + diff --git a/packages/development-edition-engine-handlebars/source/_meta/_00-head.mustache b/packages/development-edition-engine-handlebars/source/_meta/_00-head.mustache new file mode 100644 index 000000000..45ce3bb7d --- /dev/null +++ b/packages/development-edition-engine-handlebars/source/_meta/_00-head.mustache @@ -0,0 +1,17 @@ + + + + {{ title }} + + + + + + + + {{{ patternLabHead }}} + + + + + diff --git a/packages/development-edition-engine-handlebars/source/_meta/_01-foot.hbs b/packages/development-edition-engine-handlebars/source/_meta/_01-foot.hbs new file mode 100644 index 000000000..797d9418d --- /dev/null +++ b/packages/development-edition-engine-handlebars/source/_meta/_01-foot.hbs @@ -0,0 +1,6 @@ + + +{{{ patternLabFoot }}} + + + diff --git a/packages/development-edition-engine-handlebars/source/_meta/_01-foot.mustache b/packages/development-edition-engine-handlebars/source/_meta/_01-foot.mustache new file mode 100644 index 000000000..797d9418d --- /dev/null +++ b/packages/development-edition-engine-handlebars/source/_meta/_01-foot.mustache @@ -0,0 +1,6 @@ + + +{{{ patternLabFoot }}} + + + diff --git a/packages/development-edition-engine-handlebars/source/css/pattern-scaffolding.css b/packages/development-edition-engine-handlebars/source/css/pattern-scaffolding.css new file mode 100644 index 000000000..f6c2da29d --- /dev/null +++ b/packages/development-edition-engine-handlebars/source/css/pattern-scaffolding.css @@ -0,0 +1,52 @@ +/** + * This stylesheet is for styles you want to include only when displaying demo + * styles for grids, animations, color swatches, etc. + * These styles will not be your production CSS. + */ + +#sg-patterns { + -webkit-box-sizing: border-box !important; + box-sizing: border-box !important; + max-width: 100%; + padding: 0 0.5em; +} + +.sg-colors { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + list-style: none !important; + padding: 0 !important; + margin: 0 !important; +} +.sg-colors li { + -webkit-box-flex: 1; + -ms-flex: auto; + flex: auto; + margin: 0 0 0.5em 0.5em; + width: 14em; +} + +.sg-swatch { + display: flex; + flex-direction: column; + height: 6em; + margin-bottom: 0.3em; + padding: 0.5rem; +} + +.sg-label { + font-size: 90%; + line-height: 1; + color: white; +} + +.sg-label__inverted { + color: black; +} + +.sg-label__top { + margin-bottom: auto; +} diff --git a/packages/development-edition-engine-handlebars/source/css/style.css b/packages/development-edition-engine-handlebars/source/css/style.css new file mode 100644 index 000000000..7f25a7fe6 --- /dev/null +++ b/packages/development-edition-engine-handlebars/source/css/style.css @@ -0,0 +1,3 @@ +.annotation { + color: #b2b2b2; +} diff --git a/packages/development-edition-engine-handlebars/source/favicon.ico b/packages/development-edition-engine-handlebars/source/favicon.ico new file mode 100644 index 000000000..eee4aa78f Binary files /dev/null and b/packages/development-edition-engine-handlebars/source/favicon.ico differ diff --git a/packages/development-edition-engine-handlebars/source/fonts/.gitkeep b/packages/development-edition-engine-handlebars/source/fonts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/packages/development-edition-engine-react/.editorconfig b/packages/development-edition-engine-react/.editorconfig new file mode 100644 index 000000000..8951c3929 --- /dev/null +++ b/packages/development-edition-engine-react/.editorconfig @@ -0,0 +1,11 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 2 +tab_width = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/packages/development-edition-engine-react/.gitignore b/packages/development-edition-engine-react/.gitignore new file mode 100644 index 000000000..f5ffa0899 --- /dev/null +++ b/packages/development-edition-engine-react/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +.DS_Store +patternlab.json +.sass-cache/* +/sass-cache +Thumbs.db +.idea/ +public diff --git a/packages/development-edition-engine-react/.npmrc b/packages/development-edition-engine-react/.npmrc new file mode 100644 index 000000000..ce73f58bf --- /dev/null +++ b/packages/development-edition-engine-react/.npmrc @@ -0,0 +1,2 @@ +package-lock=false +save-exact=true diff --git a/packages/development-edition-engine-react/.nvmrc b/packages/development-edition-engine-react/.nvmrc new file mode 100644 index 000000000..95c4e8d27 --- /dev/null +++ b/packages/development-edition-engine-react/.nvmrc @@ -0,0 +1 @@ +10.0.0 \ No newline at end of file diff --git a/packages/development-edition-engine-react/CHANGELOG.md b/packages/development-edition-engine-react/CHANGELOG.md new file mode 100644 index 000000000..611fa23a3 --- /dev/null +++ b/packages/development-edition-engine-react/CHANGELOG.md @@ -0,0 +1,266 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [5.9.3](https://github.com/pattern-lab/edition-node-gulp/compare/v5.9.2...v5.9.3) (2020-05-01) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +## [5.9.2](https://github.com/pattern-lab/edition-node-gulp/compare/v5.9.1...v5.9.2) (2020-04-24) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +# [5.9.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.8.0...v5.9.0) (2020-04-24) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +## [5.7.2](https://github.com/pattern-lab/edition-node-gulp/compare/v5.7.1...v5.7.2) (2020-03-24) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +## [5.7.1](https://github.com/pattern-lab/edition-node-gulp/compare/v5.7.0...v5.7.1) (2020-02-24) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +# [5.7.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.6.0...v5.7.0) (2020-02-17) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +# [5.6.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.5.0...v5.6.0) (2020-01-18) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + + +# [5.5.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.4.2...v5.5.0) (2019-12-19) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +## [5.4.2](https://github.com/pattern-lab/edition-node-gulp/compare/v5.4.1...v5.4.2) (2019-11-27) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +## [5.4.1](https://github.com/pattern-lab/edition-node-gulp/compare/v5.4.0...v5.4.1) (2019-11-26) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +# [5.4.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.3.3...v5.4.0) (2019-11-26) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +## [5.3.3](https://github.com/pattern-lab/edition-node-gulp/compare/v5.3.2...v5.3.3) (2019-11-22) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + + +## [5.3.2](https://github.com/pattern-lab/edition-node-gulp/compare/v5.3.1...v5.3.2) (2019-11-14) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +## [5.3.1](https://github.com/pattern-lab/edition-node-gulp/compare/v5.3.0...v5.3.1) (2019-11-13) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +# [5.3.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.2.0...v5.3.0) (2019-11-13) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +# [5.2.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.1.0...v5.2.0) (2019-11-12) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + + +# [5.1.0](https://github.com/pattern-lab/edition-node-gulp/compare/v5.0.2...v5.1.0) (2019-10-29) + + +### Features + +* **core:** fix pattern export all conflicts ([b210d82](https://github.com/pattern-lab/edition-node-gulp/commit/b210d820ba8ac0b64c82c7ff0f18c9f8a900fce2)) + + + + + +## [5.0.2](https://github.com/pattern-lab/edition-node-gulp/compare/v5.0.1...v5.0.2) (2019-10-28) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +## [5.0.1](https://github.com/pattern-lab/edition-node-gulp/compare/v5.0.0...v5.0.1) (2019-10-28) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +# [5.0.0](https://github.com/pattern-lab/edition-node-gulp/compare/v3.0.0-beta.3...v5.0.0) (2019-10-25) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + + +## [0.1.8](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.7...@pattern-lab/engine-react-testing-tree@0.1.8) (2019-10-14) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + + +## [0.1.7](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.6...@pattern-lab/engine-react-testing-tree@0.1.7) (2019-10-14) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + + +## [0.1.3](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.2...@pattern-lab/engine-react-testing-tree@0.1.3) (2019-08-23) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + + +## [0.1.2](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.2-alpha.0...@pattern-lab/engine-react-testing-tree@0.1.2) (2019-05-16) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + +## [0.1.1-beta.1](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.4...@pattern-lab/engine-react-testing-tree@0.1.1-beta.1) (2019-02-09) + + +### Bug Fixes + +* manually bump package.json versions of packages published in September but with mismatched package.json versions ([98dfadf](https://github.com/pattern-lab/edition-node-gulp/commit/98dfadf)) + + + + + + +## [0.1.1-beta.0](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.4...@pattern-lab/engine-react-testing-tree@0.1.1-beta.0) (2018-09-07) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + + + + + +## [0.1.1-alpha.4](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.3...@pattern-lab/engine-react-testing-tree@0.1.1-alpha.4) (2018-07-06) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + +## [0.1.1-alpha.3](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.2...@pattern-lab/engine-react-testing-tree@0.1.1-alpha.3) (2018-07-06) + +### Bug Fixes + +* **package:** fix fat-fingered dependency ([e439f4e](https://github.com/pattern-lab/edition-node-gulp/commit/e439f4e)) + +### Features + +* **package:** add npmrc file ([55f5bc2](https://github.com/pattern-lab/edition-node-gulp/commit/55f5bc2)) +* **package:** pin all dependencies ([415698e](https://github.com/pattern-lab/edition-node-gulp/commit/415698e)) +* **package:** remove package-lock.json files ([5ab3995](https://github.com/pattern-lab/edition-node-gulp/commit/5ab3995)) + + + +## [0.1.1-alpha.2](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.1...@pattern-lab/engine-react-testing-tree@0.1.1-alpha.2) (2018-07-05) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + +## [0.1.1-alpha.1](https://github.com/pattern-lab/edition-node-gulp/compare/@pattern-lab/engine-react-testing-tree@0.1.1-alpha.0...@pattern-lab/engine-react-testing-tree@0.1.1-alpha.1) (2018-05-04) + +**Note:** Version bump only for package @pattern-lab/engine-react-testing-tree + + + +## 0.1.1-alpha.0 (2018-05-04) + +### Features + +* **README:** clarify purpose of development edition ([d90df0e](https://github.com/pattern-lab/edition-node-gulp/commit/d90df0e)) diff --git a/packages/development-edition-engine-react/LICENSE b/packages/development-edition-engine-react/LICENSE new file mode 100644 index 000000000..0e6363fb1 --- /dev/null +++ b/packages/development-edition-engine-react/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Pattern Lab + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/development-edition-engine-react/README.md b/packages/development-edition-engine-react/README.md new file mode 100644 index 000000000..bdb531d95 --- /dev/null +++ b/packages/development-edition-engine-react/README.md @@ -0,0 +1,14 @@ +![Pattern Lab Logo](/patternlab.png "Pattern Lab Logo") + +# Pattern Lab Node - Development Edition Engine React + +_here be dragons_ + +This Development Edition is a variant of [Edition Node Gulp](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node-gulp) for convience purposes only, loaded with the React Engine. The goals of this Development Edition are two-fold: + +* Develop the [React Engine](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-react) +* Build and test against React pattern tree + +If you'd like to help with the React Engine, please reference the [contribution guidelines](https://github.com/pattern-lab/patternlab-node/blob/master/.github/CONTRIBUTING.md). + +> Development Editions of Pattern Lab provide the ability to work on and commit changes to select packages within the overall Pattern Lab [ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). This Edition is NOT stable. diff --git a/packages/development-edition-engine-react/gulpfile.js b/packages/development-edition-engine-react/gulpfile.js new file mode 100644 index 000000000..8d8653659 --- /dev/null +++ b/packages/development-edition-engine-react/gulpfile.js @@ -0,0 +1,72 @@ +/****************************************************** + * PATTERN LAB NODE + * EDITION-NODE-GULP + * The gulp wrapper around patternlab-node core, providing tasks to interact with the core library. + ******************************************************/ +const gulp = require('gulp'); +const argv = require('minimist')(process.argv.slice(2)); + +/****************************************************** + * PATTERN LAB NODE WRAPPER TASKS with core library + ******************************************************/ +const config = require('./patternlab-config.json'); +const patternlab = require('@pattern-lab/core')(config); + +function build() { + return patternlab + .build({ + watch: argv.watch, + cleanPublic: config.cleanPublic, + }) + .then(() => { + // do something else when this promise resolves + }); +} + +function serve() { + return patternlab + .serve({ + cleanPublic: config.cleanPublic, + }) + .then(() => { + // do something else when this promise resolves + }); +} + +gulp.task('patternlab:version', function() { + patternlab.version(); +}); + +gulp.task('patternlab:help', function() { + patternlab.help(); +}); + +gulp.task('patternlab:patternsonly', function() { + patternlab.patternsonly(config.cleanPublic); +}); + +gulp.task('patternlab:liststarterkits', function() { + patternlab.liststarterkits(); +}); + +gulp.task('patternlab:loadstarterkit', function() { + patternlab.loadstarterkit(argv.kit, argv.clean); +}); + +gulp.task('patternlab:build', function() { + build().then(() => { + // do something else when this promise resolves + }); +}); + +gulp.task('patternlab:serve', function() { + serve().then(() => { + // do something else when this promise resolves + }); +}); + +gulp.task('patternlab:installplugin', function() { + patternlab.installplugin(argv.plugin); +}); + +gulp.task('default', ['patternlab:help']); diff --git a/packages/development-edition-engine-react/package.json b/packages/development-edition-engine-react/package.json new file mode 100644 index 000000000..e17c489fd --- /dev/null +++ b/packages/development-edition-engine-react/package.json @@ -0,0 +1,36 @@ +{ + "name": "@pattern-lab/engine-react-testing-tree", + "description": "The tree of components we use to test, develop and validate the React engine", + "version": "5.9.3", + "private": true, + "main": "gulpfile.js", + "dependencies": { + "@pattern-lab/core": "^5.9.3", + "@pattern-lab/engine-mustache": "^5.0.0", + "@pattern-lab/engine-react": "^5.0.0", + "@pattern-lab/uikit-workshop": "^5.9.3", + "gulp": "3.9.1", + "minimist": "^1.2.0", + "react": "16.2.0" + }, + "keywords": [ + "Pattern Lab", + "Atomic Web Design", + "Node", + "Gulp", + "Javascript" + ], + "repository": { + "type": "git", + "url": "git://github.com/pattern-lab/edition-node-gulp.git" + }, + "bugs": "https://github.com/pattern-lab/edition-node-gulp/issues", + "author": "Brian Muenzenmeyer", + "scripts": { + "gulp": "gulp -- " + }, + "license": "MIT", + "engines": { + "node": ">=10.0" + } +} diff --git a/packages/development-edition-engine-react/patternlab-config.json b/packages/development-edition-engine-react/patternlab-config.json new file mode 100644 index 000000000..23b459f2d --- /dev/null +++ b/packages/development-edition-engine-react/patternlab-config.json @@ -0,0 +1,95 @@ +{ + "cacheBust": true, + "cleanPublic" : true, + "defaultPattern": "all", + "defaultShowPatternInfo": false, + "ishControlsHide": { + "s": false, + "m": false, + "l": false, + "full": false, + "random": false, + "disco": false, + "hay": true, + "mqs": false, + "find": false, + "views-all": false, + "views-annotations": false, + "views-code": false, + "views-new": false, + "tools-all": false, + "tools-docs": false + }, + "ishViewportRange": { + "s": [240, 500], + "m": [500, 800], + "l": [800, 2600] + }, +<<<<<<< HEAD:packages/development-edition-engine-react/patternlab-config.json + "logLevel": "info", +======= + "patternExportAll": false, + "patternExportPreserveDirectoryStructure": false, + "patternExportRaw": false, + "patternExportPatternPartials": [], + "patternExportDirectory": "./pattern_exports/", + "cacheBust": true, +>>>>>>> 436dd99e50d808cd14691593b927209f1ecb663e:patternlab-config.json + "outputFileSuffixes": { + "rendered": ".rendered", + "rawTemplate": "", + "markupOnly": ".markup-only" + }, + "paths" : { + "source" : { + "root": "./source/", + "patterns" : "./source/_patterns/", + "data" : "./source/_data/", + "meta": "./source/_meta/", + "annotations" : "./source/_annotations/", + "styleguide": "./node_modules/@pattern-lab/uikit-workshop/dist/", + "patternlabFiles": { + "general-header": + "./node_modules/@pattern-lab/uikit-workshop/views/partials/general-header.mustache", + "general-footer": + "./node_modules/@pattern-lab/uikit-workshop/views/partials/general-footer.mustache", + "patternSection": + "./node_modules/@pattern-lab/uikit-workshop/views/partials/patternSection.mustache", + "patternSectionSubtype": + "./node_modules/@pattern-lab/uikit-workshop/views/partials/patternSectionSubtype.mustache", + "viewall": + "./node_modules/@pattern-lab/uikit-workshop/views/viewall.mustache" + }, + "js" : "./source/js", + "images" : "./source/images", + "fonts" : "./source/fonts", + "css" : "./source/css/" + }, + "public" : { + "root" : "./public/", + "patterns" : "./public/patterns/", + "data" : "./public/styleguide/data/", + "annotations" : "./public/annotations/", + "styleguide" : "./public/styleguide/", + "js" : "./public/js", + "images" : "./public/images", + "fonts" : "./public/fonts", + "css" : "./public/css" + } + }, + "patternExtension": "mustache", + "patternStateCascade": ["inprogress", "inreview", "complete"], + "patternExportDirectory": "./pattern_exports/", + "patternExportPatternPartials": [], + "serverOptions": { + "wait": 1000 + }, + "starterkitSubDir": "dist", + "styleGuideExcludes": [ + ], + "theme": { + "color": "dark", + "density": "compact", + "layout": "horizontal" + } +} diff --git a/packages/development-edition-engine-react/source/_annotations/README.md b/packages/development-edition-engine-react/source/_annotations/README.md new file mode 100644 index 000000000..42592a09b --- /dev/null +++ b/packages/development-edition-engine-react/source/_annotations/README.md @@ -0,0 +1,5 @@ +This is the default location to place annotations. + +Pattern Lab uses annotations defined here to markup the UI. Read more about [annotations](http://patternlab.io/docs/pattern-adding-annotations.html). + +If you wish to rename this directory, make sure you update the `paths.source.annotations` property within `patternlab-config.json`. diff --git a/packages/development-edition-engine-react/source/_data/README.md b/packages/development-edition-engine-react/source/_data/README.md new file mode 100644 index 000000000..3b9ea1ea4 --- /dev/null +++ b/packages/development-edition-engine-react/source/_data/README.md @@ -0,0 +1,5 @@ +This is the default location to place global data files. + +Pattern Lab uses data defined here as the global fallback if a template does not provide its own data. Read more about [data](http://patternlab.io/docs/data-overview.html). + +If you wish to rename this directory, make sure you update the `paths.source.data` property within `patternlab-config.json`. diff --git a/packages/development-edition-engine-react/source/_meta/README.md b/packages/development-edition-engine-react/source/_meta/README.md new file mode 100644 index 000000000..c6c8c3b8e --- /dev/null +++ b/packages/development-edition-engine-react/source/_meta/README.md @@ -0,0 +1,5 @@ +This is the default location to place meta files, otherwise known a pattern's header and footer. + +Pattern Lab builds each pattern while prepending and appending the header and footer. Read more about [pattern headers and footers](http://patternlab.io/docs/pattern-header-footer.html). + +If you wish to rename this directory, make sure you update the `paths.source.meta` property within `patternlab-config.json`. diff --git a/packages/development-edition-engine-react/source/_meta/_00-head.hbs b/packages/development-edition-engine-react/source/_meta/_00-head.hbs new file mode 100644 index 000000000..cf826617a --- /dev/null +++ b/packages/development-edition-engine-react/source/_meta/_00-head.hbs @@ -0,0 +1,16 @@ + + + + {{ title }} + + + + + + + + {{{ patternLabHead }}} + + + + diff --git a/packages/development-edition-engine-react/source/_meta/_00-head.html b/packages/development-edition-engine-react/source/_meta/_00-head.html new file mode 100644 index 000000000..cf826617a --- /dev/null +++ b/packages/development-edition-engine-react/source/_meta/_00-head.html @@ -0,0 +1,16 @@ + + + + {{ title }} + + + + + + + + {{{ patternLabHead }}} + + + + diff --git a/packages/development-edition-engine-react/source/_meta/_00-head.mustache b/packages/development-edition-engine-react/source/_meta/_00-head.mustache new file mode 100644 index 000000000..069727248 --- /dev/null +++ b/packages/development-edition-engine-react/source/_meta/_00-head.mustache @@ -0,0 +1,17 @@ + + + + {{ title }} + + + + + + + + {{{ patternLabHead }}} + + + + + diff --git a/packages/development-edition-engine-react/source/_meta/_01-foot.hbs b/packages/development-edition-engine-react/source/_meta/_01-foot.hbs new file mode 100644 index 000000000..2feb91336 --- /dev/null +++ b/packages/development-edition-engine-react/source/_meta/_01-foot.hbs @@ -0,0 +1,6 @@ + + +{{{ patternLabFoot }}} + + + diff --git a/packages/development-edition-engine-react/source/_meta/_01-foot.html b/packages/development-edition-engine-react/source/_meta/_01-foot.html new file mode 100644 index 000000000..2feb91336 --- /dev/null +++ b/packages/development-edition-engine-react/source/_meta/_01-foot.html @@ -0,0 +1,6 @@ + + +{{{ patternLabFoot }}} + + + diff --git a/packages/development-edition-engine-react/source/_meta/_01-foot.mustache b/packages/development-edition-engine-react/source/_meta/_01-foot.mustache new file mode 100644 index 000000000..7c15d6a74 --- /dev/null +++ b/packages/development-edition-engine-react/source/_meta/_01-foot.mustache @@ -0,0 +1,6 @@ + + + {{{ patternLabFoot }}} + + + diff --git a/packages/development-edition-engine-react/source/_patterns/00-atoms/00-general/HelloWorld.jsx b/packages/development-edition-engine-react/source/_patterns/00-atoms/00-general/HelloWorld.jsx new file mode 100644 index 000000000..9790e5af0 --- /dev/null +++ b/packages/development-edition-engine-react/source/_patterns/00-atoms/00-general/HelloWorld.jsx @@ -0,0 +1,5 @@ +import React from 'react'; + +const HelloWorld = () =>
Hello world!
; + +export default HelloWorld; diff --git a/packages/development-edition-engine-react/source/_patterns/01-molecules/00-general/HelloIncluder.jsx b/packages/development-edition-engine-react/source/_patterns/01-molecules/00-general/HelloIncluder.jsx new file mode 100644 index 000000000..6b2f3c985 --- /dev/null +++ b/packages/development-edition-engine-react/source/_patterns/01-molecules/00-general/HelloIncluder.jsx @@ -0,0 +1,30 @@ +import React, { Component } from 'react'; + +import HelloWorld from '../../00-atoms/00-general/HelloWorld'; + +// const HelloWorld = () => ( +//
+// Hello world! +//
+// ); + +class HelloIncluder extends Component { + constructor() { + super(); + this.state = { + bgColor: 'transparent', + }; + setTimeout(() => this.setState({ bgColor: 'red' }), 2000); + } + + render() { + return ( +
+
Hey! Here's the Hello World component:
+ +
+ ); + } +} + +export default HelloIncluder; diff --git a/packages/development-edition-engine-react/source/_patterns/README.md b/packages/development-edition-engine-react/source/_patterns/README.md new file mode 100644 index 000000000..2f89266bf --- /dev/null +++ b/packages/development-edition-engine-react/source/_patterns/README.md @@ -0,0 +1,5 @@ +This is the default location to place pattern files. + +Pattern Lab builds patterns and the ui from the structure defined within. Read more about [pattern organization](http://patternlab.io/docs/pattern-organization.html). + +If you wish to rename this directory, make sure you update the `paths.source.patterns` property within `patternlab-config.json`. diff --git a/packages/development-edition-engine-react/source/css/README.md b/packages/development-edition-engine-react/source/css/README.md new file mode 100644 index 000000000..a6ce7bf27 --- /dev/null +++ b/packages/development-edition-engine-react/source/css/README.md @@ -0,0 +1,5 @@ +This is the default location to place global css files. + +The entire contents of this directory will be recursively copied to your configured `public/` directory. + +If you wish to rename this directory, make sure you update the `paths.source.css` property within `patternlab-config.json`. diff --git a/packages/development-edition-engine-react/source/css/pattern-scaffolding.css b/packages/development-edition-engine-react/source/css/pattern-scaffolding.css new file mode 100644 index 000000000..b09172fc8 --- /dev/null +++ b/packages/development-edition-engine-react/source/css/pattern-scaffolding.css @@ -0,0 +1,54 @@ +/** + * This stylesheet is for styles you want to include only when displaying demo + * styles for grids, animations, color swatches, etc. + * These styles will not be your production CSS. + */ +#sg-patterns { + -webkit-box-sizing: border-box !important; + box-sizing: border-box !important; + max-width: 100%; + padding: 0 0.5em; +} + +.demo-animate { + background: #ddd; + padding: 1em; + margin-bottom: 1em; + text-align: center; + border-radius: 8px; + cursor: pointer; +} + +.sg-colors { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + list-style: none !important; + padding: 0 !important; + margin: 0 !important; +} +.sg-colors li { + -webkit-box-flex: 1; + -ms-flex: auto; + flex: auto; + padding: 0.3em; + margin: 0 0.5em 0.5em 0; + min-width: 5em; + max-width: 14em; + border: 1px solid #ddd; + border-radius: 8px; +} + +.sg-swatch { + display: block; + height: 4em; + margin-bottom: 0.3em; + border-radius: 5px; +} + +.sg-label { + font-size: 90%; + line-height: 1; +} diff --git a/packages/development-edition-engine-react/source/css/style.css b/packages/development-edition-engine-react/source/css/style.css new file mode 100644 index 000000000..04f745349 --- /dev/null +++ b/packages/development-edition-engine-react/source/css/style.css @@ -0,0 +1,3 @@ +/* + * YOUR STYLES HERE + */ diff --git a/packages/development-edition-engine-react/source/favicon.ico b/packages/development-edition-engine-react/source/favicon.ico new file mode 100644 index 000000000..eee4aa78f Binary files /dev/null and b/packages/development-edition-engine-react/source/favicon.ico differ diff --git a/packages/development-edition-engine-react/source/fonts/README.md b/packages/development-edition-engine-react/source/fonts/README.md new file mode 100644 index 000000000..ff4d4dee0 --- /dev/null +++ b/packages/development-edition-engine-react/source/fonts/README.md @@ -0,0 +1,5 @@ +This is the default location to place global font files. + +The entire contents of this directory will be recursively copied to your configured `public/` directory. + +If you wish to rename this directory, make sure you update the `paths.source.fonts` property within `patternlab-config.json`. diff --git a/packages/development-edition-engine-react/source/images/README.md b/packages/development-edition-engine-react/source/images/README.md new file mode 100644 index 000000000..86c91d567 --- /dev/null +++ b/packages/development-edition-engine-react/source/images/README.md @@ -0,0 +1,5 @@ +This is the default location to place global image files. + +The entire contents of this directory will be recursively copied to your configured `public/` directory. + +If you wish to rename this directory, make sure you update the `paths.source.images` property within `patternlab-config.json`. diff --git a/packages/development-edition-engine-react/source/js/README.md b/packages/development-edition-engine-react/source/js/README.md new file mode 100644 index 000000000..857893854 --- /dev/null +++ b/packages/development-edition-engine-react/source/js/README.md @@ -0,0 +1,5 @@ +This is the default location to place global javascript files. + +The entire contents of this directory will be recursively copied to your configured `public/` directory. + +If you wish to rename this directory, make sure you update the `paths.source.js` property within `patternlab-config.json`. diff --git a/packages/development-edition-engine-twig/.gitignore b/packages/development-edition-engine-twig/.gitignore new file mode 100644 index 000000000..0679bd2b5 --- /dev/null +++ b/packages/development-edition-engine-twig/.gitignore @@ -0,0 +1,9 @@ +node_modules/ +.DS_Store +patternlab.json +.sass-cache/* +/sass-cache +Thumbs.db +.idea/ +public +dependencyGraph.json diff --git a/packages/development-edition-engine-twig/CHANGELOG.md b/packages/development-edition-engine-twig/CHANGELOG.md new file mode 100644 index 000000000..f1bda1063 --- /dev/null +++ b/packages/development-edition-engine-twig/CHANGELOG.md @@ -0,0 +1,40 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [5.9.3](https://github.com/pattern-lab/patternlab-node/compare/v5.9.2...v5.9.3) (2020-05-01) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-twig + + + + + +## [5.9.2](https://github.com/pattern-lab/patternlab-node/compare/v5.9.1...v5.9.2) (2020-04-24) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-twig + + + + + +## [5.9.1](https://github.com/pattern-lab/patternlab-node/compare/v5.9.0...v5.9.1) (2020-04-24) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-twig + + + + + +# [5.9.0](https://github.com/pattern-lab/patternlab-node/compare/v5.8.0...v5.9.0) (2020-04-24) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-twig + + + + + +# [5.8.0](https://github.com/pattern-lab/patternlab-node/compare/v5.7.2...v5.8.0) (2020-04-03) + +**Note:** Version bump only for package @pattern-lab/development-edition-engine-twig diff --git a/packages/development-edition-engine-twig/README.md b/packages/development-edition-engine-twig/README.md new file mode 100644 index 000000000..5ea6dce7b --- /dev/null +++ b/packages/development-edition-engine-twig/README.md @@ -0,0 +1,25 @@ +![Pattern Lab Logo](/patternlab.png "Pattern Lab Logo") + +# Pattern Lab Node - Development Edition Engine Twig + +_here be dragons_ + +This Development Edition is a variant of [Edition Node](https://github.com/pattern-lab/patternlab-node/tree/master/packages/edition-node) for convience purposes only, loaded with the Twig Engine. The goals of this Development Edition are two-fold: + +* Develop the [Twig Engine](https://github.com/pattern-lab/patternlab-node/tree/master/packages/engine-twig) +* Build and test against Twig pattern tree + +> Development Editions of Pattern Lab provide the ability to work on and commit changes to select packages within the overall Pattern Lab [ecosystem](http://patternlab.io/docs/advanced-ecosystem-overview.html). This Edition is NOT stable. + + +## Working on Pattern Lab's UI Locally + +### Step 1: Install Dependencies +Run the following in the root of the Pattern Lab repo: + +``` +yarn run setup +``` + +### Step 2 +Finally, go back into this folder, `cd packages/development-edition-engine-twig`, and start up the local dev server which watches UIKit and the local Pattern Lab instance for changes, live reloads, etc by running `yarn dev` diff --git a/packages/development-edition-engine-twig/package.json b/packages/development-edition-engine-twig/package.json new file mode 100644 index 000000000..949ac8f24 --- /dev/null +++ b/packages/development-edition-engine-twig/package.json @@ -0,0 +1,43 @@ +{ + "name": "@pattern-lab/development-edition-engine-twig", + "private": true, + "version": "5.9.3", + "description": "The tree of components we use to test, develop and validate the twig engine (not engine-twig-php)", + "scripts": { + "postbootstrap": "patternlab install --starterkits @pattern-lab/starterkit-twig-demo", + "pl:build": "patternlab build --config ./patternlab-config.json", + "pl:help": "patternlab --help", + "pl:install": "patternlab install --config ./patternlab-config.json", + "pl:serve": "patternlab serve --config ./patternlab-config.json", + "pl:version": "patternlab --version", + "dev": "node ./node_modules/@pattern-lab/uikit-workshop/build-tools.js" + }, + "keywords": [ + "Pattern Lab", + "Atomic Web Design", + "Node", + "Twig", + "Edition" + ], + "author": "Ringo De Smet", + "license": "MIT", + "repository": { + "type": "git", + "url": "git://github.com/pattern-lab/patternlab-node.git" + }, + "engines": { + "node": ">=10.0" + }, + "dependencies": { + "@pattern-lab/cli": "^5.9.3", + "@pattern-lab/core": "^5.9.3", + "@pattern-lab/engine-twig": "^5.9.3", + "@pattern-lab/starterkit-twig-demo": "^5.8.0", + "@pattern-lab/uikit-workshop": "^5.9.3" + }, + "workspaces": { + "nohoist": [ + "**/@pattern-lab/starterkit-twig-demo" + ] + } +} diff --git a/packages/development-edition-engine-twig/patternlab-config.json b/packages/development-edition-engine-twig/patternlab-config.json new file mode 100644 index 000000000..3c18bd403 --- /dev/null +++ b/packages/development-edition-engine-twig/patternlab-config.json @@ -0,0 +1,114 @@ +{ + "cacheBust": true, + "cleanPublic": true, + "defaultPattern": "pages-homepage", + "defaultShowPatternInfo": false, + "ishControlsHide": { + "s": false, + "m": false, + "l": false, + "full": false, + "random": false, + "disco": false, + "hay": true, + "mqs": false, + "find": false, + "views-all": false, + "views-annotations": false, + "views-code": false, + "views-new": false, + "tools-all": false, + "tools-docs": false + }, + "ishViewportRange": { + "s": [ + 240, + 500 + ], + "m": [ + 500, + 800 + ], + "l": [ + 800, + 2600 + ] + }, + "logLevel": "info", + "outputFileSuffixes": { + "rendered": ".rendered", + "rawTemplate": "", + "markupOnly": ".markup-only" + }, + "paths": { + "source": { + "root": "source/", + "patterns": "source/_patterns/", + "data": "source/_data/", + "meta": "source/_meta/", + "annotations": "source/_annotations/", + "styleguide": "dist/", + "patternlabFiles": { + "general-header": "views/partials/general-header.mustache", + "general-footer": "views/partials/general-footer.mustache", + "patternSection": "views/partials/patternSection.mustache", + "patternSectionSubtype": "views/partials/patternSectionSubtype.mustache", + "viewall": "views/viewall.mustache" + }, + "js": "source/js", + "images": "source/images", + "fonts": "source/fonts", + "css": "source/css" + }, + "public": { + "root": "public/", + "patterns": "public/patterns/", + "data": "public/styleguide/data/", + "annotations": "public/annotations/", + "styleguide": "public/styleguide/", + "js": "public/js", + "images": "public/images", + "fonts": "public/fonts", + "css": "public/css" + } + }, + "patternExtension": "twig", + "patternStateCascade": [ + "inprogress", + "inreview", + "complete" + ], + "patternExportDirectory": "pattern_exports", + "patternExportPatternPartials": [], + "serverOptions": { + "wait": 1000 + }, + "starterkitSubDir": "dist", + "styleGuideExcludes": [], + "theme": { + "color": "dark", + "density": "compact", + "layout": "horizontal" + }, + "uikits": [ + { + "name": "uikit-workshop", + "outputDir": "", + "enabled": true, + "excludedPatternStates": [], + "excludedTags": [] + } + ], + "engines": { + "twig": { + "namespaces": { + "atoms": "source/_patterns/00-atoms/", + "molecules": "source/_patterns/01-molecules/", + "organisms": "source/_patterns/02-organisms/", + "templates": "source/_patterns/03-templates/", + "pages": "source/_patterns/04-pages/", + "macros": "source/_macros/" + } + } + } +} diff --git a/packages/development-edition-engine-twig/source/.gitignore b/packages/development-edition-engine-twig/source/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/packages/development-edition-engine-twig/source/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/packages/docs/.eleventy.js b/packages/docs/.eleventy.js new file mode 100644 index 000000000..9e8b8feaa --- /dev/null +++ b/packages/docs/.eleventy.js @@ -0,0 +1,96 @@ +const rssPlugin = require('@11ty/eleventy-plugin-rss'); +const syntaxHighlight = require('@11ty/eleventy-plugin-syntaxhighlight'); +const eleventyNavigationPlugin = require('@11ty/eleventy-navigation'); +const fs = require('fs'); + +// Import filters +const dateFilter = require('./src/filters/date-filter.js'); +const markdownFilter = require('./src/filters/markdown-filter.js'); +const w3DateFilter = require('./src/filters/w3-date-filter.js'); + +// Import transforms +const htmlMinTransform = require('./src/transforms/html-min-transform.js'); +const parseTransform = require('./src/transforms/parse-transform.js'); + +// Import data files +const site = require('./src/_data/site.json'); + +module.exports = function(config) { + // Filters + config.addFilter('dateFilter', dateFilter); + config.addFilter('markdownFilter', markdownFilter); + config.addFilter('w3DateFilter', w3DateFilter); + + // Layout aliases + config.addLayoutAlias('home', 'layouts/home.njk'); + + // Transforms + config.addTransform('htmlmin', htmlMinTransform); + config.addTransform('parse', parseTransform); + + // Passthrough copy + config.addPassthroughCopy('src/images'); + config.addPassthroughCopy('src/js'); + config.addPassthroughCopy('src/admin/config.yml'); + config.addPassthroughCopy('src/admin/previews.js'); + config.addPassthroughCopy('node_modules/nunjucks/browser/nunjucks-slim.js'); + + const now = new Date(); + + // Custom collections + const livePosts = post => post.date <= now && !post.data.draft; + config.addCollection('posts', collection => { + return [ + ...collection.getFilteredByGlob('./src/posts/*.md').filter(livePosts) + ].reverse(); + }); + + config.addCollection('demos', collection => { + return [...collection.getFilteredByGlob('./src/demos/*.md')].reverse(); + }); + + config.addCollection('postFeed', collection => { + return [...collection.getFilteredByGlob('./src/posts/*.md').filter(livePosts)] + .reverse() + .slice(0, site.maxPostsPerPage); + }); + + config.addCollection('docs', collection => { + return [...collection.getFilteredByGlob('./src/docs/*.md')].reverse(); + }); + + config.addCollection('docsOrdered', collection => { + const docs = collection.getFilteredByGlob('src/docs/*.md').sort((a, b) => { + return Number(a.data.order) - Number(b.data.order); + }); + return docs; + }); + + // Plugins + config.addPlugin(rssPlugin); + config.addPlugin(syntaxHighlight); + config.addPlugin(eleventyNavigationPlugin); + + // 404 + config.setBrowserSyncConfig({ + callbacks: { + ready: function(err, browserSync) { + const content_404 = fs.readFileSync('dist/404.html'); + + browserSync.addMiddleware('*', (req, res) => { + // Provides the 404 content without redirect. + res.write(content_404); + res.end(); + }); + } + } + }); + + return { + dir: { + input: 'src', + output: 'dist' + }, + passthroughFileCopy: true + }; +}; diff --git a/packages/docs/.gitignore b/packages/docs/.gitignore new file mode 100644 index 000000000..a54bbf4b4 --- /dev/null +++ b/packages/docs/.gitignore @@ -0,0 +1,17 @@ +*.log +npm-debug.* +*.scssc +*.log +*.swp +.DS_Store +.sass-cache +node_modules +dist + +# Specifics + +# Hide design tokens +src/scss/_tokens.scss + +# Hide compiled CSS +src/_includes/assets/* diff --git a/packages/docs/.prettierrc b/packages/docs/.prettierrc new file mode 100644 index 000000000..a20fac227 --- /dev/null +++ b/packages/docs/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 90, + "useTabs": true, + "tabWidth": 2, + "singleQuote": true, + "bracketSpacing": false +} diff --git a/packages/docs/.vscode/launch.json b/packages/docs/.vscode/launch.json new file mode 100644 index 000000000..2359da6e9 --- /dev/null +++ b/packages/docs/.vscode/launch.json @@ -0,0 +1,17 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "skipFiles": [ + "/**" + ], + "program": "${workspaceFolder}/index.js" + } + ] +} \ No newline at end of file diff --git a/packages/docs/CHANGELOG.md b/packages/docs/CHANGELOG.md new file mode 100644 index 000000000..3bf4e3f5c --- /dev/null +++ b/packages/docs/CHANGELOG.md @@ -0,0 +1,19 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +## [5.9.3](https://github.com/bradfrost/pl-website-eleventy/compare/v5.9.2...v5.9.3) (2020-05-01) + +**Note:** Version bump only for package patternlab-website + + + + + +# [5.9.0](https://github.com/bradfrost/pl-website-eleventy/compare/v5.8.0...v5.9.0) (2020-04-24) + + +### Features + +* **docs:** yarnify ([5a47dc7](https://github.com/bradfrost/pl-website-eleventy/commit/5a47dc7b90dc5c43c12a51143b41943dcbd8564c)) diff --git a/packages/docs/LICENSE.txt b/packages/docs/LICENSE.txt new file mode 100644 index 000000000..312f7109c --- /dev/null +++ b/packages/docs/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 andy-bell.design and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/docs/README.md b/packages/docs/README.md new file mode 100644 index 000000000..7b191158a --- /dev/null +++ b/packages/docs/README.md @@ -0,0 +1,32 @@ +# Pattern Lab Website + +This is the website for patternlab.io. This site was build using the [Hylia starter kit](https://hylia.website/), which is a lightweight [Eleventy](https://11ty.io) starter kit. + +--- + +## How to work with this project + +1. Clone this repository +2. `cd` into the project directory and run `yarn` +3. Once all the dependencies are installed run `yarn start` +4. Open your browser at `http://localhost:8080` + +## Terminal commands + +### Serve the site locally + +```bash +yarn start +``` + +### Build a production version of the site + +```bash +yarn production +``` + +### Compile Sass + +```bash +yarn sass:process +``` diff --git a/packages/docs/package.json b/packages/docs/package.json new file mode 100755 index 000000000..801697fa9 --- /dev/null +++ b/packages/docs/package.json @@ -0,0 +1,52 @@ +{ + "name": "patternlab-website", + "version": "5.9.3", + "description": "The website for patternlab.io", + "main": "index.js", + "dependencies": { + "@11ty/eleventy": "^0.8.3", + "@11ty/eleventy-plugin-rss": "^1.0.6", + "@11ty/eleventy-plugin-syntaxhighlight": "^2.0.3", + "@tbranyen/jsdom": "^13.0.0", + "concurrently": "^4.1.0", + "html-minifier": "^4.0.0", + "json-to-scss": "^1.3.1", + "sass": "^1.21.0", + "semver": "^6.3.0", + "slugify": "^1.3.4", + "stalfos": "github:hankchizljaw/stalfos#c8971d22726326cfc04089b2da4d51eeb1ebb0eb" + }, + "devDependencies": { + "@11ty/eleventy-navigation": "^0.1.5", + "@erquhart/rollup-plugin-node-builtins": "^2.1.5", + "bl": "^3.0.0", + "chokidar-cli": "^2.0.0", + "cross-env": "^5.2.0", + "make-dir-cli": "^2.0.0", + "prettier": "^1.18.2", + "rollup": "^1.16.1", + "rollup-plugin-commonjs": "^10.0.0", + "rollup-plugin-json": "^4.0.0", + "rollup-plugin-node-resolve": "^5.0.3" + }, + "scripts": { + "sass:tokens": "npx json-to-scss src/_data/tokens.json src/scss/_tokens.scss", + "sass:process": "yarn sass:tokens && sass src/scss/style.scss dist/css/style.css --style=compressed", + "cms:precompile": "make-dir dist/admin && nunjucks-precompile src/_includes > dist/admin/templates.js -i \"\\.(njk|css|svg)$\"", + "cms:bundle": "rollup --config", + "start": "concurrently \"yarn sass:process -- --watch\" \"yarn cms:bundle -- --watch\" \"chokidar \\\"src/_includes/**\\\" -c \\\"yarn cms:precompile\\\"\" \"yarn serve\"", + "serve": "cross-env ELEVENTY_ENV=development npx eleventy --serve", + "production": "yarn sass:process && yarn cms:precompile && yarn cms:bundle && npx eleventy" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/bradfrost/pl-website-eleventy.git" + }, + "keywords": [], + "author": "", + "license": "MIT", + "bugs": { + "url": "https://github.com/bradfrost/pl-website-eleventy/issues" + }, + "homepage": "https://github.com/bradfrost/pl-website-eleventy/#readme" +} diff --git a/packages/docs/php-docs/advanced-auto-regenerate.md b/packages/docs/php-docs/advanced-auto-regenerate.md new file mode 100644 index 000000000..fc6985825 --- /dev/null +++ b/packages/docs/php-docs/advanced-auto-regenerate.md @@ -0,0 +1,55 @@ +--- +title: Watching for Changes and Auto Regenerating Patterns +tags: + - docs +--- + +Pattern Lab can watch for changes to files in `./source/` and automatically rebuild the entire Pattern Lab website for you. Make your changes, save the file, and Pattern Lab takes care of the rest. + +## How to Start Watching for Changes + +To start watching for changes do the following: + +1. In a terminal window navigate to the root of your project +2. Type `php core/console --watch` + +To stop watching files use `CTRL+C` in the same terminal window. + +### Only Watch for Changes to Pattern Lab Files + +If you use a task runner like Gulp or Grunt to compile Sass, JavaScript or images you may want Pattern Lab to only concern itself with its own files. To limit Pattern Lab to watch and move only its files do the following: + +1. In a terminal window navigate to the root of your project +2. Type `php core/console --watch --patternsonly` + +Or, better yet, use this command within your Gulp or Grunt script. + +### Start the Web Server & Watch for Changes at the Same Time + +If you're relying on Pattern Lab's server to view your content you'll want to run the watch and server with the same command. Do the following: + +1. In a terminal window navigate to the root of your project +2. Type `php core/console --server --with-watch` + +You can also start the server and watch only patterns: + +1. In a terminal window navigate to the root of your project +2. Type `php core/console --server --with-watch --patternsonly` + +To stop the server and watching files use `CTRL+C` in the same terminal window. + +### Start the Web Server, Watch for Changes, and Reload the Browser at the Same Time + +The ultimate solution for working with Pattern Lab if you're not using a task runner is Pattern Lab's [Auto-Reload Plugin](https://github.com/pattern-lab/plugin-php-reload). Do the following: + +1. In a terminal window navigate to the root of your project +2. Install the [Auto-Reload Plugin](https://github.com/pattern-lab/plugin-php-reload) using `composer require pattern-lab/plugin-reload` +3. Type `php core/console --server --with-watch` + +The Auto-Reload Plugin is automatically enabled when you install it. You can always [disable the plugin](https://github.com/pattern-lab/plugin-php-reload#disabling-the-plugin) if you need to. + +To stop the server, watching files, and auto-reload service use `CTRL+C` in the same terminal window. + +## What Pattern Lab Will Watch + +By default, the PHP version of Pattern Lab will watch all files in `./source` except those that match the "ignore" configuration options in `config/config.yml`. When using `--patternsonly` Pattern Lab will only watch those directories in `./source` that start with an underscore. For example, `_patterns`. To learn how to modify what is ignored check out "[Managing Assets for a Pattern](/docs/pattern-managing-assets.html)". diff --git a/packages/docs/php-docs/advanced-clean-public.md b/packages/docs/php-docs/advanced-clean-public.md new file mode 100644 index 000000000..408e3ed37 --- /dev/null +++ b/packages/docs/php-docs/advanced-clean-public.md @@ -0,0 +1,5 @@ +--- +title: Stopping public/ from Being "Cleaned" +tags: + - docs +--- diff --git a/packages/docs/php-docs/advanced-config-options.md b/packages/docs/php-docs/advanced-config-options.md new file mode 100644 index 000000000..e21791b71 --- /dev/null +++ b/packages/docs/php-docs/advanced-config-options.md @@ -0,0 +1,7 @@ +--- +title: Editing the Configuration Options +tags: + - docs +--- + +Pattern Lab comes with a simple configuration file that allows you to modify certain aspects of the system. The configuration file can be found in `./config/config.yml`. diff --git a/packages/docs/php-docs/advanced-exporting-patterns.md b/packages/docs/php-docs/advanced-exporting-patterns.md new file mode 100644 index 000000000..49f4857d1 --- /dev/null +++ b/packages/docs/php-docs/advanced-exporting-patterns.md @@ -0,0 +1,17 @@ +--- +title: Exporting Patterns +tags: + - docs +--- + +Pattern Lab can export all of your patterns for you sans Pattern Lab's CSS and JavaScript. To export your patterns do the following: + +1. In a terminal window navigate to the root of your project +2. Type `php core/console --export` + +If you require your patterns to be exported without your global header and footer (_e.g. to export a clean molecule_) do the following: + +1. In a terminal window navigate to the root of your project +2. Type `php core/console --export --clean` + +In both cases the patterns will be exported to `./export/patterns`. The export directory is one of the many directories that can be [configured and changed](/docs/editing-source-files.html). diff --git a/packages/docs/php-docs/advanced-page-follow.md b/packages/docs/php-docs/advanced-page-follow.md new file mode 100644 index 000000000..3e38bebfc --- /dev/null +++ b/packages/docs/php-docs/advanced-page-follow.md @@ -0,0 +1,7 @@ +--- +title: Multi browser & Multi device Testing with Page Follow +tags: + - docs +--- + +An auto-reload service was built into Pattern Lab 1. With Pattern Lab 2 this feature has been removed. This feature may return as a plugin in the same way that the [Auto-Reload service](/docs/advanced-reload-browser.html) did. diff --git a/packages/docs/php-docs/advanced-pattern-lab-nav.md b/packages/docs/php-docs/advanced-pattern-lab-nav.md new file mode 100644 index 000000000..2bd2c28c5 --- /dev/null +++ b/packages/docs/php-docs/advanced-pattern-lab-nav.md @@ -0,0 +1,29 @@ +--- +title: Modifying Pattern Lab's Navigation +tags: + - docs +--- + +When sharing Pattern Lab with a client it may be beneficial to turn-off certain elements in the default navigation. To turn-off navigation elements do the following: + +1. Open `./config/config.yml` +2. Add the keys for the elements you'd like to hide to the `ishControlsHide` configuration option +3. Re-generate your Pattern Lab site + +The following keys are supported and will hide their respective elements: + +``` +s +m +l +full +random +disco +hay +find +views-new +tools-all +tools-docs +``` + +`hay` is disabled by default. diff --git a/packages/docs/php-docs/advanced-reload-browser.md b/packages/docs/php-docs/advanced-reload-browser.md new file mode 100644 index 000000000..4da045d5f --- /dev/null +++ b/packages/docs/php-docs/advanced-reload-browser.md @@ -0,0 +1,14 @@ +--- +title: Auto Reloading the Browser Window When Changes Are Made +tags: + - docs +--- + +An auto-reload service was built into Pattern Lab 1. With Pattern Lab 2 this feature has been turned into the [Auto-Reload Plugin](https://github.com/pattern-lab/plugin-php-reload). To install this plugin do the following: + +1. In a terminal window navigate to the root of your project +2. Type `composer require pattern-lab/plugin-reload` + +The Auto-Reload Plugin is automatically enabled when you install it. You can always [disable the plugin](https://github.com/pattern-lab/plugin-php-reload#disabling-the-plugin) if you need to. + +This service is enabled when using the `--watch` or `--server --with-watch` commands. Learn more about [watching for changes](/docs/advanced-auto-regenerate.html). diff --git a/packages/docs/php-docs/advanced-starterkits.md b/packages/docs/php-docs/advanced-starterkits.md new file mode 100644 index 000000000..5a90e0bdf --- /dev/null +++ b/packages/docs/php-docs/advanced-starterkits.md @@ -0,0 +1,21 @@ +--- +title: Starterkits +tags: + - docs +--- + +StarterKits can be installed via the following commands: + +``` +php core/console --starterkit --install [starterkit-name] +``` + +where [starterkit-name] is the name of the Starterkit. + +so... a complete example: + +``` +php core/console --starterkit --install pattern-lab/starterkit-mustache-demo +``` + +It is recommended that you do not install this StarterKit as a dependency for your Pattern Lab project via Composer. diff --git a/packages/docs/php-docs/changes-1-to-2.md b/packages/docs/php-docs/changes-1-to-2.md new file mode 100644 index 000000000..38ec38bee --- /dev/null +++ b/packages/docs/php-docs/changes-1-to-2.md @@ -0,0 +1,46 @@ +--- +title: Pattern Lab 1 to Pattern Lab 2 Changes +tags: + - docs +--- + +With Pattern Lab 2 in development for almost two years many under-the-hood changes have been implemented. For the most part a Pattern Lab 1 project should work with minimal changes in Pattern Lab 2. Here is a non-exhaustive list of new features in Pattern Lab 2: + +* complete rebuilding of core +* support for Composer +* support for more template languages (_currently Mustache and Twig_) +* support for StarterKits allowing separation of a team's unique needs from Pattern Lab proper +* event notification system, getters, setters, and a clean install process to allow for plugins +* redesigned and rebuilt modal view +* redesigned and rebuilt styleguide view +* multi-source directory support +* support for YAML in global data, pattern-specific data, and pseudo-patterns +* can have multiple JSON/YAML files in `./_data/` +* support for JSON/YAML linting to find errors +* can set multiple classes using the style modifier +* can use link.[pattern-name] within data to link to other patterns +* pattern parameters support simple lists +* pattern parameters act more like mustache (_but not exactly!_) +* patternParameters can over listItem loop numbers +* global pattern header and footer is now in `./source/_meta` +* upgraded console utility +* patterns and pattern subtypes can be documented in the styleguide by using `[pattern-name].md` or `[pattern-subtype].md` +* view all pages for pattern sub-types +* annotations can be defined using Markdown +* patterns can be exported minus Pattern Lab mark-up +* can hide individual patterns from "view all" view still available via the nav +* can set a pattern to be the default pattern when loading Pattern Lab +* can turn on modal view by default +* implemented server +* sayings can now be defined in the config +* install process that makes it easier to install various components + +These are the features of Pattern Lab 1 that have become plugins: + +* Automatic Browser Reload + +These are the features of Pattern Lab 1 that have been removed in Pattern Lab 2: + +* QR Code Generator +* Page Follow +* MQs diff --git a/packages/docs/php-docs/command-line.md b/packages/docs/php-docs/command-line.md new file mode 100644 index 000000000..264d235c3 --- /dev/null +++ b/packages/docs/php-docs/command-line.md @@ -0,0 +1,22 @@ +--- +title: Using The Command Line Options +tags: + - docs +--- + +To use Pattern Lab you must use the command line interface. To view the available commands when using Pattern Lab do the following: + +1. In a terminal window navigate to the root of your project +2. Type `php core/console --help` + +To get the options for a particular command, for example the `--generate` command, you can type: + + php core/console --help --generate + +## A Special Note About Windows + +To access the command prompt on Windows you can [follow the directions from Microsoft](http://windows.microsoft.com/en-us/windows-vista/open-a-command-prompt-window). After getting to the command prompt type the following to make sure you have PHP installed: + + php -v + +If you get an error and know that you've installed PHP you may need to [update your path variable so Windows can find PHP](http://willj.co/2012/10/run-wamp-php-windows-7-command-line/). diff --git a/packages/docs/php-docs/generating-pattern-lab.md b/packages/docs/php-docs/generating-pattern-lab.md new file mode 100644 index 000000000..b9831d371 --- /dev/null +++ b/packages/docs/php-docs/generating-pattern-lab.md @@ -0,0 +1,14 @@ +--- +title: Generating Pattern Lab +tags: + - docs +--- + +Pattern Lab consists of an empty shell when you first install it. To populate the public-facing side of Pattern Lab with your content and patterns do the following: + +1. In a terminal window navigate to the root of your project +2. Type `php core/console --generate` + +Your Pattern Lab install should now be populated and [available for viewing](/docs/viewing-patterns.html). As you [make changes to your patterns](/docs/editing-source-files.html) you'll need re-generate your site using step 2 above. + +Manually re-generating your site after each change or collection of changes can be cumbersome. Pattern Lab can [watch files in the `./source/` directory for changes and re-generate the site automatically](/docs/advanced-auto-regenerate.html). The Pattern Lab website can also be [automatically reloaded](/docs/advanced-reload-browser.html). diff --git a/packages/docs/php-docs/installation.md b/packages/docs/php-docs/installation.md new file mode 100644 index 000000000..23b3ec3c4 --- /dev/null +++ b/packages/docs/php-docs/installation.md @@ -0,0 +1,5 @@ +--- +title: Installing Pattern Lab +tags: + - docs +--- diff --git a/packages/docs/php-docs/pattern-managing-assets.md b/packages/docs/php-docs/pattern-managing-assets.md new file mode 100644 index 000000000..1f6927cde --- /dev/null +++ b/packages/docs/php-docs/pattern-managing-assets.md @@ -0,0 +1,41 @@ +--- +title: Managing Pattern Assets +tags: + - docs +--- + +Assets for patterns - including JavaScript, CSS, and images - should be stored and edited in the `./source/` directory. Pattern Lab will move these assets to the `./public/` directory for you when you generate your site or when you watch the `./source/` directory for changes. **You can name and organize your assets however you like.** If you would like to use `./source/stylesheets/` to store your styles instead of `./source/css/` you can do that. There is nothing to configure. The structure will be maintained when they're moved to the `./public/` directory. + +## Ignoring and Not Moving Assets Based on File Extension + +By default, Pattern Lab will not move assets with the following file extensions: + +- `.less` +- `.scss` +- `.DS_Store` + +To ignore more file extensions edit the `ie` configuration option in `./config/config.yml`. For example, to ignore `*.png` files your `ie` configuration option would look like: + + ie: + - DS_Store + - less + - scss + - png + +## Ignoring and Not Moving Assets Based on Directory + +By default, the PHP version of Pattern Lab will ignore **all** assets in directories that exactly match: + +- `scss` + +To ignore more directories just edit the `id` configuration option in `./config/config.yml`. For example, to ignore directories named `test/` your `id` configuration option would look like: + + id: + - scss + - test + +**Important:** Pattern Lab will only ignore exact matches of ignored directories. For example, if you had a directory named `cool_scss/` it, and the assets underneath it, _would_ be moved to `./public/` even though `scss` was in the name of the directory. + +## Adding Assets to the Pattern Header & Footer + +Static assets like Javascript and CSS **are not** added automagically to your patterns. You need to add them manually to the [shared pattern header and footer](/docs/pattern-header-footer.html). diff --git a/packages/docs/php-docs/pattern-states.md b/packages/docs/php-docs/pattern-states.md new file mode 100644 index 000000000..cebea9de9 --- /dev/null +++ b/packages/docs/php-docs/pattern-states.md @@ -0,0 +1,45 @@ +--- +title: Using Pattern States +tags: + - docs +--- + +Pattern states provide your team and client a simple visual of the current state of patterns in Pattern Lab. Pattern states can track progress of a pattern from development, through client review, to completion or they can be used to give certain patterns specific classes. It's important to note that the state of a pattern can be influenced by its pattern partials. + +## The Default Pattern States + +Pattern Lab comes with the following default pattern states: + +- **inprogress**: pattern is in development or being worked upon. a red dot. +- **inreview**: pattern is ready for a client to look at and comment upon. a yellow dot. +- **complete**: pattern is ready to be moved to production. a green dot. + +Any pattern that includes a pattern partial that has a lower pattern state will inherit that state. For example, a pattern with the state of `inreview` that includes a pattern partial with the state of `inprogress` will have its state overridden and set to `inprogress`. It will not change to `inreview` until the pattern partial has a state of `inreview` or `complete`. + +## Giving Patterns a State + +Giving patterns a state is simply a matter of modifying the file name. If we wanted to give our `molecules-media-block` pattern a state of `inprogress` we'd change the file name from: + +``` +./source/_patterns/01-molecules/02-blocks/00-media-block.mustache +``` + +to: + +``` +./source/_patterns/01-molecules/02-blocks/00-media-block@inprogress.mustache +``` + +## Adding Customized States + +The three default states included with Pattern Lab might not be enough for everyone. To add customized states you should modify your own CSS files. **DO NOT** modify `states.css` in `public/styleguide/css/`. This is because `states.css` will be overwritten in future upgrades. + +You can use the following as your CSS template for new pattern states: + +```css +{% raw %}.newpatternstate:before { + color: #B10DC9 !important; +}{% endraw %} +``` + +Then add `@newpatternstate` to your patterns to have the new look show up. If you want to add it to the cascade of the default patterns you can modify `./config/config.yml`. Simply add your new pattern state to the `patternStates` list. diff --git a/packages/docs/php-docs/requirements.md b/packages/docs/php-docs/requirements.md new file mode 100644 index 000000000..7a741bb60 --- /dev/null +++ b/packages/docs/php-docs/requirements.md @@ -0,0 +1,22 @@ +--- +title: Requirements +tags: + - docs +--- + + + +The requirements for Pattern Lab 2 vary depending on what features you want to use. + +## Minimum Requirements + +To use the basic features of Pattern Lab to compile patterns, you must have **PHP 5.4+** installed. On Mac OS X Pattern Lab should work "out of the box." If you're on Windows you can [download PHP from PHP.net](http://windows.php.net/download/). Pattern Lab comes with its own built-in web server. + +Because Pattern Lab's output consists of HTML, CSS, and JavaScript there are **no requirements** for hosting your Pattern Lab site. Simply upload the `./public/` directory to your host and you should be good to go. + +## Highly Recommended: Composer + +Pattern Lab uses [Composer](https://getcomposer.org/) to manage project dependencies. While Pattern Lab can be downloaded as a Zip we highly recommend installing Composer so you can easily update your project in the future. Please follow the directions for [installing Composer](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx) on the Composer website. We recommend you [install it globally](https://getcomposer.org/doc/00-intro.md#globally). + + + diff --git a/packages/docs/php-docs/upgrading.md b/packages/docs/php-docs/upgrading.md new file mode 100644 index 000000000..8daf7ab1d --- /dev/null +++ b/packages/docs/php-docs/upgrading.md @@ -0,0 +1,52 @@ +--- +title: Upgrading Pattern Lab +tags: + - docs +--- + + + +Pattern Lab 2 uses [Composer](https://getcomposer.org) to manage project dependencies. To upgrade an edition based on Pattern Lab 2 do the following: + +1. In a terminal window navigate to the root of your project +2. Type `composer update` + +During the upgrade process Pattern Lab 2 will move or add any files that are required for the new version to work. It will also update your configuration as appropriate. If you don't have Composer installed please [follow the directions for installing Composer](https://getcomposer.org/doc/00-intro.md#installation-linux-unix-osx) that are available on the Composer website. We recommend you [install it globally](https://getcomposer.org/doc/00-intro.md#globally). + +## Upgrading Pattern Lab 1 to Pattern Lab 2 + +Pattern Lab 2 was a complete rewrite and reorganization of Pattern Lab 1. [Learn about the changes](/docs/changes-1-to-2.html). To upgrade do the following: + +1. [Download](http://patternlab.io/download.html) the PHP edition that matches your needs + +If you chose a Mustache-based edition do the following: + +1. Copy `./source` from your old project to your new edition +2. Copy `./source/_patterns/00-atoms/00-meta/_00-head.mustache` to `./source/_meta/_00-head.mustache` +3. Copy `./source/_patterns/00-atoms/00-meta/_01-foot.mustache` to `./source/_meta/_00-foot.mustache` (you can then delete `source/_patterns/00-atoms/00-meta/` directory) +4. In `./source/_meta/_00-head.mustache`, replace `{% raw %}{% pattern-lab-head %}{% endraw %}` with `{% raw %}{{{ patternLabHead }}}{% endraw %}` +5. In `./source/_meta/_00-foot.mustache` replace `{% raw %}{% pattern-lab-foot %}{% endraw %}` with `{% raw %}{{{ patternLabFoot }}}{% endraw %}` +6. Copy `./source/_data/annotations.js` to `./source/_annotations/annotations.js` +7. Remove the underscore in front of the JSON files in `source/data` (i.e. `data.json` not `_data.json`). + + + +If you chose another version do the above and convert the templates as appropriate. + +## Learning About Upgrades + +New releases and upgrades are announced in Pattern Lab's [PHP room on Gitter](https://gitter.im/pattern-lab/php) and on Twitter at [@patternlabio](https://twitter.com/patternlabio). + +You can also determine if your version of Pattern Lab 2 can be upgraded yourself by doing the following: + +1. In a terminal window navigate to the root of your project +2. Type `composer outdated` + +Two components of Pattern Lab 2 maintain CHANGELOGs as part of their "Releases" page on GitHub: + +* [pattern-lab/core](https://github.com/pattern-lab/patternlab-php-core/releases) +* [pattern-lab/styleguidekit-assets-default](https://github.com/pattern-lab/styleguidekit-assets-default/releases) + + + + diff --git a/packages/docs/php-docs/viewing-patterns.html b/packages/docs/php-docs/viewing-patterns.html new file mode 100644 index 000000000..ebc7461d4 --- /dev/null +++ b/packages/docs/php-docs/viewing-patterns.html @@ -0,0 +1,17 @@ +--- +title: Viewing Patterns +tags: + - docs +--- + + + +Pattern Lab utilizes PHP's [built-in web server](http://php.net/manual/en/features.commandline.webserver.php) to let you browse your generated patterns. To start the server do the following: + +1. In a terminal window navigate to the root of your project +2. Type `php core/console --server` + +Your local Pattern Lab install should now be available for browsing at [http://localhost:8080](http://localhost:8080). + + + diff --git a/packages/docs/rollup.config.js b/packages/docs/rollup.config.js new file mode 100644 index 000000000..7f62e07f4 --- /dev/null +++ b/packages/docs/rollup.config.js @@ -0,0 +1,19 @@ +const builtins = require('@erquhart/rollup-plugin-node-builtins'); +const commonjs = require('rollup-plugin-commonjs'); +const nodeResolve = require('rollup-plugin-node-resolve'); +const json = require('rollup-plugin-json'); + +export default { + input: 'src/admin/util', + output: { + file: 'dist/admin/util.js', + format: 'iife', + name: 'previewUtil', + }, + plugins: [ + builtins(), + nodeResolve(), + commonjs(), + json(), + ] +}; diff --git a/packages/docs/src/404.md b/packages/docs/src/404.md new file mode 100644 index 000000000..eccd5a45c --- /dev/null +++ b/packages/docs/src/404.md @@ -0,0 +1,17 @@ +--- +title: '404 - not found' +layout: layouts/page.njk +permalink: 404.html +eleventyExcludeFromCollections: true +--- + +We’re sorry, but that content can’t be found. Please go [back to home](/). + +{% comment %} +Read more: https://www.11ty.io/docs/quicktips/not-found/ + +This will work for both GitHub pages and Netlify: + +- https://help.github.com/articles/creating-a-custom-404-page-for-your-github-pages-site/ +- https://www.netlify.com/docs/redirects/#custom-404 + {% endcomment %} diff --git a/packages/docs/src/_data/global.js b/packages/docs/src/_data/global.js new file mode 100644 index 000000000..683a0a332 --- /dev/null +++ b/packages/docs/src/_data/global.js @@ -0,0 +1,9 @@ +module.exports = { + random() { + const segment = () => { + return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + }; + return `${segment()}-${segment()}-${segment()}`; + }, + now: Date.now() +}; diff --git a/packages/docs/src/_data/helpers.js b/packages/docs/src/_data/helpers.js new file mode 100644 index 000000000..9ae9b7883 --- /dev/null +++ b/packages/docs/src/_data/helpers.js @@ -0,0 +1,10 @@ +module.exports = { + getNextHeadingLevel(currentLevel) { + return parseInt(currentLevel, 10) + 1; + }, + getReadingTime(text) { + const wordsPerMinute = 200; + const numberOfWords = text.split(/\s/g).length; + return Math.ceil(numberOfWords / wordsPerMinute); + } +}; diff --git a/packages/docs/src/_data/navigation.json b/packages/docs/src/_data/navigation.json new file mode 100644 index 000000000..8838d1f0b --- /dev/null +++ b/packages/docs/src/_data/navigation.json @@ -0,0 +1,66 @@ +{ + "items": [ + { + "label": "Getting Started", + "url": "/docs/installing-pattern-lab/" + }, + { + "label": "Documentation", + "url": "/docs/", + "subnavIsClosed": true, + "subnav": [ + { + "label": "Getting Started", + "category": "getting-started" + }, + { + "label": "Working with Patterns", + "category": "patterns" + }, + { + "label": "Working with Data", + "category": "data" + }, + { + "label": "Advanced", + "category": "advanced" + } + ] + }, + { + "label": "Demos", + "url": "/demos/" + }, + { + "label": "Support", + "url": "/support/" + }, + { + "label": "Resources", + "url": "/resources/" + }, + { + "label": "On Github", + "url": "https://github.com/pattern-lab/patternlab-node", + "external": true + } + ], + "footerNav": [ + { + "label": "Resources", + "url": "/resources" + }, + { + "label": "Updates", + "url": "/updates" + }, + { + "label": "Demos", + "url": "/demos" + }, + { + "label": "On Github", + "url": "https://github.com/pattern-lab/patternlab-node" + } + ] +} diff --git a/packages/docs/src/_data/site.json b/packages/docs/src/_data/site.json new file mode 100644 index 000000000..8a5e4ce23 --- /dev/null +++ b/packages/docs/src/_data/site.json @@ -0,0 +1,13 @@ +{ + "showThemeCredit": true, + "name": "Pattern Lab", + "shortDesc": "Pattern Lab is a frontend workshop environment that helps you build, view, test, and showcase your design system's UI components.", + "url": "https://patternlab.io/", + "authorEmail": "brad@bradfrost.com", + "authorHandle": "@bradfrost", + "authorName": "Brad Frost", + "enableThirdPartyComments": false, + "maxPostsPerPage": 5, + "paymentPointer": "$coil.xrptipbot.com/c1f8f05d-8d8c-4f37-b1ad-25677ae129da", + "faviconPath": "/images/favicon.ico" +} diff --git a/packages/docs/src/_data/styleguide.js b/packages/docs/src/_data/styleguide.js new file mode 100644 index 000000000..4969bec39 --- /dev/null +++ b/packages/docs/src/_data/styleguide.js @@ -0,0 +1,28 @@ +const tokens = require('./tokens.json'); + +module.exports = { + colors() { + let response = []; + + Object.keys(tokens.colors).forEach(key => { + response.push({ + value: tokens.colors[key], + key + }); + }); + + return response; + }, + sizes() { + let response = []; + + Object.keys(tokens['size-scale']).forEach(key => { + response.push({ + value: tokens['size-scale'][key], + key + }); + }); + + return response; + } +}; diff --git a/packages/docs/src/_data/tokens.json b/packages/docs/src/_data/tokens.json new file mode 100644 index 000000000..b5bf4b3ae --- /dev/null +++ b/packages/docs/src/_data/tokens.json @@ -0,0 +1,26 @@ +{ + "size-scale": { + "base": "1rem", + "300": "0.8rem", + "500": "1.25rem", + "600": "1.56rem", + "700": "1.95rem", + "800": "2.44rem", + "900": "3.05rem", + "max": "4rem" + }, + "colors": { + "primary": "#173854", + "primary-shade": "#102538", + "primary-glare": "#22547c", + "highlight": "#fedb8b", + "light": "#ffffff", + "mid": "#cccccc", + "dark": "#333333", + "slate": "#404040" + }, + "fonts": { + "base": "\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'\"", + "serif": "\"'Lora', serif\"" + } +} diff --git a/packages/docs/src/_includes/components/footer-nav.njk b/packages/docs/src/_includes/components/footer-nav.njk new file mode 100644 index 000000000..54a448266 --- /dev/null +++ b/packages/docs/src/_includes/components/footer-nav.njk @@ -0,0 +1,10 @@ + diff --git a/packages/docs/src/_includes/components/footer.njk b/packages/docs/src/_includes/components/footer.njk new file mode 100644 index 000000000..7764312d2 --- /dev/null +++ b/packages/docs/src/_includes/components/footer.njk @@ -0,0 +1,10 @@ + diff --git a/packages/docs/src/_includes/components/header.njk b/packages/docs/src/_includes/components/header.njk new file mode 100644 index 000000000..5e9ebd5de --- /dev/null +++ b/packages/docs/src/_includes/components/header.njk @@ -0,0 +1,21 @@ +
+
+ {% include "components/logo.njk" %} + + + +
+ {% include "components/tree-nav.njk" %} + + + +
+
+ +
+ + diff --git a/packages/docs/src/_includes/components/hero.njk b/packages/docs/src/_includes/components/hero.njk new file mode 100644 index 000000000..7053c247c --- /dev/null +++ b/packages/docs/src/_includes/components/hero.njk @@ -0,0 +1,16 @@ +
+
+
+

Create atomic design systems with Pattern Lab

+ +

+ Pattern Lab is a frontend workshop environment that helps you build, view, test, and showcase your design system's UI components. +

+ +

+ Run the following command in your terminal and read the installation guide to get started: +

+
npm create pattern-lab
+
+
+
\ No newline at end of file diff --git a/packages/docs/src/_includes/components/icon-chevron-down.njk b/packages/docs/src/_includes/components/icon-chevron-down.njk new file mode 100644 index 000000000..37c8e757a --- /dev/null +++ b/packages/docs/src/_includes/components/icon-chevron-down.njk @@ -0,0 +1,4 @@ + +cheveron-down + + \ No newline at end of file diff --git a/packages/docs/src/_includes/components/logo.njk b/packages/docs/src/_includes/components/logo.njk new file mode 100644 index 000000000..b8b528064 --- /dev/null +++ b/packages/docs/src/_includes/components/logo.njk @@ -0,0 +1,11 @@ + + + diff --git a/packages/docs/src/_includes/components/meta-info.njk b/packages/docs/src/_includes/components/meta-info.njk new file mode 100644 index 000000000..c7d6a3aa0 --- /dev/null +++ b/packages/docs/src/_includes/components/meta-info.njk @@ -0,0 +1,41 @@ +{% set pageTitle = title + ' - ' + site.name %} +{% set pageDesc = '' %} +{% set siteTitle = site.name %} +{% set currentUrl = site.url + page.url %} + +{% if metaTitle %} + {% set pageTitle = metaTitle %} +{% endif %} + +{% if metaDesc %} + {% set pageDesc = metaDesc %} +{% endif %} + +{{ pageTitle }} + + + + + + + +{% if site.authorHandle %} + +{% endif %} + +{% if metaDesc %} + + + +{% endif %} + +{% if socialImage %} + + + + +{% endif %} + +{% if site.paymentPointer %} + +{% endif %} diff --git a/packages/docs/src/_includes/components/page-header.njk b/packages/docs/src/_includes/components/page-header.njk new file mode 100644 index 000000000..8fbae6236 --- /dev/null +++ b/packages/docs/src/_includes/components/page-header.njk @@ -0,0 +1,13 @@ +
+ + {% if introKicker %} +

{{ introKicker }}

+ {% endif %} + +

{{ introHeading }}

+ + {% if introDescription %} +

{{ introDescription }}

+ {% endif %} + +
diff --git a/packages/docs/src/_includes/components/stacked-block.njk b/packages/docs/src/_includes/components/stacked-block.njk new file mode 100644 index 000000000..24c79fb1f --- /dev/null +++ b/packages/docs/src/_includes/components/stacked-block.njk @@ -0,0 +1,6 @@ +
+

+ {{ title }} +

+

{{ description }}

+
\ No newline at end of file diff --git a/packages/docs/src/_includes/components/tile.njk b/packages/docs/src/_includes/components/tile.njk new file mode 100644 index 000000000..a2aeb04cf --- /dev/null +++ b/packages/docs/src/_includes/components/tile.njk @@ -0,0 +1,9 @@ +
+
+

+ {{ title }} +

+
{{ description | safe }}
+
+
+
\ No newline at end of file diff --git a/packages/docs/src/_includes/components/tree-nav.njk b/packages/docs/src/_includes/components/tree-nav.njk new file mode 100644 index 000000000..83492e7f0 --- /dev/null +++ b/packages/docs/src/_includes/components/tree-nav.njk @@ -0,0 +1,33 @@ + + + + diff --git a/packages/docs/src/_includes/components/tree-subnav.njk b/packages/docs/src/_includes/components/tree-subnav.njk new file mode 100644 index 000000000..9265520ab --- /dev/null +++ b/packages/docs/src/_includes/components/tree-subnav.njk @@ -0,0 +1,11 @@ +{% set navPages = collections.docs | eleventyNavigation %} + + diff --git a/packages/docs/src/_includes/icons/arrow.svg b/packages/docs/src/_includes/icons/arrow.svg new file mode 100644 index 000000000..153b9eed7 --- /dev/null +++ b/packages/docs/src/_includes/icons/arrow.svg @@ -0,0 +1,13 @@ + diff --git a/packages/docs/src/_includes/layouts/archive.njk b/packages/docs/src/_includes/layouts/archive.njk new file mode 100644 index 000000000..d1b328ed8 --- /dev/null +++ b/packages/docs/src/_includes/layouts/archive.njk @@ -0,0 +1,17 @@ +{% extends 'layouts/base.njk' %} +{% set pageType = 'Post Archive' %} + +{# Intro content #} +{% set introHeading = title %} +{% set introSummary %}{{ content | safe }}{% endset %} + +{# Post list content #} +{% set postListHeading = 'All posts' %} +{% set postListItems = collections.posts %} + +{% block content %} +
+ {% include "partials/components/intro.njk" %} + {% include "partials/components/post-list.njk" %} +
+{% endblock %} diff --git a/packages/docs/src/_includes/layouts/base.njk b/packages/docs/src/_includes/layouts/base.njk new file mode 100644 index 000000000..6d21c8837 --- /dev/null +++ b/packages/docs/src/_includes/layouts/base.njk @@ -0,0 +1,37 @@ + + + + + + + + + + {% include "components/meta-info.njk" %} + + + {% block head %} + {% endblock %} + + +
+
+ {% include "components/header.njk" %} +
+ + +
+
+ {% block content %} + {% endblock content %} +
+ + {% include "components/footer.njk" %} + {% block foot %} + {% endblock %} +
+ +
+ + + diff --git a/packages/docs/src/_includes/layouts/blog.njk b/packages/docs/src/_includes/layouts/blog.njk new file mode 100644 index 000000000..bc7b10eef --- /dev/null +++ b/packages/docs/src/_includes/layouts/blog.njk @@ -0,0 +1,17 @@ +{% extends 'layouts/base.njk' %} + +{# Intro content #} +{% set introHeading = title %} +{% set introDescription = description %} + +{# Post list content #} +{% set postListItems = collections.posts %} + +{% block content %} +
+
+ {% include "components/page-header.njk" %} + {% include "partials/components/post-list.njk" %} +
+
+{% endblock %} diff --git a/packages/docs/src/_includes/layouts/demos.njk b/packages/docs/src/_includes/layouts/demos.njk new file mode 100644 index 000000000..f16878645 --- /dev/null +++ b/packages/docs/src/_includes/layouts/demos.njk @@ -0,0 +1,25 @@ +{% extends 'layouts/base.njk' %} +{% set pageType = 'Post Archive' %} + +{# Intro content #} +{% set introHeading = title %} +{% set introSummary %}{{ content | safe }}{% endset %} + +{# Post list content #} +{% set demoListItems = collections.demos %} + +{% block content %} +
+
+ {% include "components/page-header.njk" %} + +

In the wild

+ {% set demoListCategory = 'example' %} + {% include "partials/components/demo-list.njk" %} + +

Starterkits

+ {% set demoListCategory = 'starterkit' %} + {% include "partials/components/demo-list.njk" %} +
+
+{% endblock %} \ No newline at end of file diff --git a/packages/docs/src/_includes/layouts/docs.njk b/packages/docs/src/_includes/layouts/docs.njk new file mode 100644 index 000000000..e29cc063e --- /dev/null +++ b/packages/docs/src/_includes/layouts/docs.njk @@ -0,0 +1,28 @@ +--- +permalink: docs/{{ title | slug }}/ +--- + +{% extends 'layouts/base.njk' %} +{% set pageType = 'Docs' %} + +{# Intro content #} +{% set introHeading = title %} +{% set introSummary %} + +{% endset %} + +{% block content %} +
+
+
+ {% include "components/page-header.njk" %} +
+ {{ content | safe }} +
+ +
+
+
+{% endblock %} + +{{ content | safe }} diff --git a/packages/docs/src/_includes/layouts/home.njk b/packages/docs/src/_includes/layouts/home.njk new file mode 100644 index 000000000..1172fcd55 --- /dev/null +++ b/packages/docs/src/_includes/layouts/home.njk @@ -0,0 +1,93 @@ +{% extends 'layouts/base.njk' %} +{% set pageType = 'Homepage' %} + +{% block content %} + + {% include "components/hero.njk" %} + +
+
    +
  • + {% set styleModifier = 'c-tile--green' %} + {% set title = 'Read the docs' %} + {% set link = '/docs/installing-pattern-lab' %} + {% set description = "Learn how to get up and running with Pattern Lab, work with patterns, design with dynamic data, and use Pattern Lab's advanced features." %} + {% include "components/tile.njk" %} +
  • +
  • + {% set styleModifier = 'c-tile--orange' %} + {% set title = 'Demos' %} + {% set link = '/demos' %} + {% set description = "Demos of pattern starterkits for your project as well as a gallery of Pattern Lab projects in the wild" %} + {% include "components/tile.njk" %} +
  • +
  • + {% set styleModifier = 'c-tile--purple' %} + {% set title = 'Resources' %} + {% set link = '/resources' %} + {% set description = "Links to articles and resources around Pattern Lab and design systems" %} + {% include "components/tile.njk" %} +
  • +
+
+ +
+

Pattern Lab features

+

At its core, Pattern Lab is a Node-powered static site generator that stitches together UI components. But there's a whole lot more to it than that!

+
    +
  • + {% set title="Nested Patterns" %} + {% set description="Include UI patterns inside each other like Russian nesting dolls. Make a change to a pattern and immediately see those changes reflected anywhere it is included." %} + {% include "components/stacked-block.njk" %} +
  • +
  • + {% set title="Design With Dynamic Data" %} + {% set description="Create living UI prototypes using dynamic data to ensure your components can handle the dynamic nature of your content." %} + {% include "components/stacked-block.njk" %} +
  • +
  • + {% set title="Tool Agnostic" %} + {% set description="Pattern Lab doesn't impose any tools or libraries on you, which means you have full control over how author your project." %} + {% include "components/stacked-block.njk" %} +
  • +
  • + {% set title="Language Agnostic" %} + {% set description="Use atomic design language, or don't! it's totally up to you how you name, structure, and organize your Pattern Lab project." %} + {% include "components/stacked-block.njk" %} +
  • +
  • + {% set title="Pattern Documentation" %} + {% set description="Define and describe your UI patterns so your entire team can start speaking the same language to collaborate more effectively." %} + {% include "components/stacked-block.njk" %} +
  • +
  • + {% set title="Viewport Resizer Tools" %} + {% set description="Pattern Lab includes viewport resizing tools to ensure your design system's components and pages are fully responsive." %} + {% include "components/stacked-block.njk" %} +
  • +
  • + {% set title="Pattern Lineage" %} + {% set description="X-ray vision! Quickly view where patterns where components are used, speeding up design, development, and QA time." %} + {% include "components/stacked-block.njk" %} +
  • +
  • + {% set title="Pattern Starter Kits" %} + {% set description="Start your Pattern Lab project with a blank slate, a few sample components, or a full-on demo project." %} + {% include "components/stacked-block.njk" %} +
  • +
  • + {% set title="Flexible and Extensible" %} + {% set description="Pattern Lab supports Handlebars and Twig templating engines. Also you can or build a plugin to extend Pattern Lab's capabilities even further." %} + {% include "components/stacked-block.njk" %} +
  • +
+ + {% set styleModifier = 'c-tile--orange' %} + {% set title = 'Open source and community driven' %} + {% set link = '/support' %} + {% set description = "Pattern Lab is (and will always be) an open source project. Check out the project on
GitHub and join the Pattern Lab Gitter community for conversation and support." %} + {% include "components/tile.njk" %} + +
+ +{% endblock %} diff --git a/packages/docs/src/_includes/layouts/page-base.njk b/packages/docs/src/_includes/layouts/page-base.njk new file mode 100644 index 000000000..4a921e27f --- /dev/null +++ b/packages/docs/src/_includes/layouts/page-base.njk @@ -0,0 +1,24 @@ +{% extends 'layouts/base.njk' %} +{% set pageType = 'Resources' %} + +{# Intro content #} +{% set introHeading = title %} +{% set introSummary %} + +{% endset %} + +{% block content %} +
+
+
+ {% include "components/page-header.njk" %} +
+ {{ content | safe }} +
+ +
+
+
+{% endblock %} + +{{ content | safe }} diff --git a/packages/docs/src/_includes/layouts/page.njk b/packages/docs/src/_includes/layouts/page.njk new file mode 100644 index 000000000..0714fa10c --- /dev/null +++ b/packages/docs/src/_includes/layouts/page.njk @@ -0,0 +1,19 @@ +{% extends 'layouts/base.njk' %} +{% set pageType = 'Page' %} + +{# Intro content #} +{% set introHeading = title %} + +{% block content %} +
+
+
+ {% include "components/page-header.njk" %} + {{ content | safe }} +
+
+
+ +{% endblock %} + +{{ content | safe }} diff --git a/packages/docs/src/_includes/layouts/post.njk b/packages/docs/src/_includes/layouts/post.njk new file mode 100644 index 000000000..6a4702769 --- /dev/null +++ b/packages/docs/src/_includes/layouts/post.njk @@ -0,0 +1,25 @@ +{% extends 'layouts/base.njk' %} +{% set pageType = 'Post' %} + +{# Intro content #} +{% if date %} +{% set introKicker = date | dateFilter %} +{% endif %} +{% set introHeading = title %} + + +{% block content %} +
+
+
+ {% include "components/page-header.njk" %} + +
+ {{ content | safe }} +
+
+
+
+{% endblock %} + +{{ content | safe }} diff --git a/packages/docs/src/_includes/partials/components/demo-list.njk b/packages/docs/src/_includes/partials/components/demo-list.njk new file mode 100644 index 000000000..d220b7a9a --- /dev/null +++ b/packages/docs/src/_includes/partials/components/demo-list.njk @@ -0,0 +1,25 @@ +{% if demoListItems.length %} + +
    + {% for item in demoListItems %} + + {% if item.data.category == demoListCategory %} +
  1. + + + {{item.data.title}} +

    + {{ item.data.title }} +

    +

    + {{ item.data.description}} +

    +
    + +
  2. + {% endif %} + + {% endfor %} +
+ +{% endif %} diff --git a/packages/docs/src/_includes/partials/components/intro.njk b/packages/docs/src/_includes/partials/components/intro.njk new file mode 100644 index 000000000..e5cc10c13 --- /dev/null +++ b/packages/docs/src/_includes/partials/components/intro.njk @@ -0,0 +1,8 @@ +
+
+

{{ introHeading }}

+ {% if introSummary %} +
{{ introSummary | safe }}
+ {% endif %} +
+
diff --git a/packages/docs/src/_includes/partials/components/nav.njk b/packages/docs/src/_includes/partials/components/nav.njk new file mode 100644 index 000000000..0b9cbcf6f --- /dev/null +++ b/packages/docs/src/_includes/partials/components/nav.njk @@ -0,0 +1,22 @@ +{% if navigation.items %} + +{% endif %} diff --git a/packages/docs/src/_includes/partials/components/pagination.njk b/packages/docs/src/_includes/partials/components/pagination.njk new file mode 100644 index 000000000..d78236a2a --- /dev/null +++ b/packages/docs/src/_includes/partials/components/pagination.njk @@ -0,0 +1,23 @@ +{% set paginationLinkTokens = 'leading-tight text-500 weight-mid box-inline-flex align-center pad-bottom-300' %} + +{% if paginationNextUrl or paginationPrevUrl %} +
+ +{% endif %} diff --git a/packages/docs/src/_includes/partials/components/post-list.njk b/packages/docs/src/_includes/partials/components/post-list.njk new file mode 100644 index 000000000..7a241e2e1 --- /dev/null +++ b/packages/docs/src/_includes/partials/components/post-list.njk @@ -0,0 +1,22 @@ + +{% if postListItems.length %} + +
    + {% for item in postListItems %} +
  1. + + +

    + + {{ item.data.title }} +

    +

    + {{ item.data.description}} +

    +
    + +
  2. + {% endfor %} +
+ +{% endif %} diff --git a/packages/docs/src/admin.njk b/packages/docs/src/admin.njk new file mode 100644 index 000000000..c1c5517d1 --- /dev/null +++ b/packages/docs/src/admin.njk @@ -0,0 +1,22 @@ +--- +permalink: '/admin/index.html' +--- + + + + + + Content Manager + + + + + + + + + + + + + diff --git a/packages/docs/src/admin/config.yml b/packages/docs/src/admin/config.yml new file mode 100644 index 000000000..15abea8bc --- /dev/null +++ b/packages/docs/src/admin/config.yml @@ -0,0 +1,243 @@ +backend: + name: git-gateway + branch: master +publish_mode: editorial_workflow +site_url: 'https://example.com' +media_folder: 'src/images' +public_folder: 'images' +collections: + - name: 'pages' + label: 'Pages' + files: + - name: 'home' + label: 'Homepage' + delete: false + file: 'src/index.md' + slug: '{{slug}}' + create: false + fields: + - { + label: 'Layout', + name: 'layout', + widget: 'hidden', + default: 'layouts/njk.njk', + } + - {label: 'Title', name: 'title', widget: 'string'} + - { + label: 'SEO Meta Title', + name: 'metaTitle', + widget: 'string', + required: false, + } + - { + label: 'SEO Meta Description', + name: 'metaDesc', + widget: 'string', + required: false, + } + - { + label: 'Post Feed Heading', + name: 'postsHeading', + widget: 'string', + default: 'Latest posts', + } + - { + label: 'Archive Link Text', + name: 'archiveButtonText', + widget: 'string', + default: 'See all posts', + } + - {label: 'Social Image', name: 'socialImage', widget: 'image', required: false} + - {label: 'Body', name: 'body', widget: 'markdown'} + - name: 'generic_pages' + label: 'Generic Pages' + folder: 'src/pages' + slug: '{{slug}}' + preview_path: 'pages/{{slug}}' + create: true + fields: + - {label: 'Layout', name: 'layout', widget: 'hidden', default: 'layouts/page.njk'} + - {label: 'Title', name: 'title', widget: 'string'} + - { + label: "Permalink Override (Pattern: '/your-slug/index.html')", + name: 'permalink', + widget: 'string', + required: false, + } + - {label: 'SEO Meta Title', name: 'metaTitle', widget: 'string', required: false} + - { + label: 'SEO Meta Description', + name: 'metaDesc', + widget: 'string', + required: false, + } + - {label: 'Social Image', name: 'socialImage', widget: 'image', required: false} + - {label: 'Body', name: 'body', widget: 'markdown'} + - name: 'posts' + label: 'Posts' + folder: 'src/posts' + slug: '{{slug}}' + preview_path: 'posts/{{slug}}' + create: true + fields: + - {label: 'Layout', name: 'layout', widget: 'hidden', default: 'layouts/post.njk'} + - {label: 'Title', name: 'title', widget: 'string'} + - {label: 'SEO Meta Title', name: 'metaTitle', widget: 'string', required: false} + - { + label: 'SEO Meta Description', + name: 'metaDesc', + widget: 'string', + required: false, + } + - {label: 'Social Image', name: 'socialImage', widget: 'image', required: false} + - {label: 'Publish Date', name: 'date', widget: 'datetime'} + - {label: 'Tags', name: 'tags', widget: 'list', allow_add: true} + - {label: 'Body', name: 'body', widget: 'markdown'} + - name: 'docs' + label: 'Docs' + folder: 'src/docs' + slug: '{{slug}}' + preview_path: 'docs/{{slug}}' + create: true + fields: + - {label: 'Layout', name: 'layout', widget: 'hidden', default: 'layouts/post.njk'} + - {label: 'Title', name: 'title', widget: 'string'} + - {label: 'Publish Date', name: 'date', widget: 'datetime'} + - {label: 'Tags', name: 'tags', widget: 'list', allow_add: true} + - {label: 'Body', name: 'body', widget: 'markdown'} + - label: 'Globals' + name: 'globals' + files: + - label: 'Site Data' + name: 'site_data' + delete: false + file: 'src/_data/site.json' + fields: + - {label: 'Site Name', name: 'name', widget: 'string'} + - {label: 'Site Url', name: 'url', widget: 'string'} + - {label: 'Author Name', name: 'authorName', widget: 'string'} + - {label: 'Author Email Address', name: 'authorEmail', widget: 'string'} + - { + label: 'Author Twitter Handle', + name: 'authorHandle', + widget: 'string', + required: false, + } + - {label: 'Footer Short Description', name: 'shortDesc', widget: 'string'} + - { + label: 'Maximum Posts Per Page', + name: 'maxPostsPerPage', + widget: 'number', + default: 5, + } + - { + label: 'Show Theme Credit', + name: 'showThemeCredit', + widget: 'boolean', + default: true, + } + - { + label: 'Enable Third Party Comments Area', + name: 'enableThirdPartyComments', + widget: 'boolean', + default: false, + } + - { + label: 'Payment Pointer (Web Monetization: https://bit.ly/2kTRI1b)', + name: 'paymentPointer', + widget: 'string', + } + - { + label: 'Favicon path (EG: /images/favicon.png)', + name: 'faviconPath', + widget: 'string', + } + - label: 'Navigation' + name: 'nav' + delete: false + file: 'src/_data/navigation.json' + fields: + - label: 'Items' + name: 'items' + widget: 'list' + fields: + - {label: 'Text', name: 'text', widget: 'string'} + - {label: 'Url', name: 'url', widget: 'string'} + - { + label: 'Is url to external site?', + name: 'external', + widget: 'boolean', + required: false, + } + - label: 'Theme Settings' + name: 'theme' + delete: false + file: 'src/_data/tokens.json' + fields: + - label: 'Size Scale' + name: 'size-scale' + widget: 'object' + fields: + - {label: 'Base Size', name: 'base', widget: 'string', default: '1rem'} + - {label: 'Ratio: 300', name: '300', widget: 'string', default: '0.8rem'} + - {label: 'Ratio: 500', name: '500', widget: 'string', default: '1.25rem'} + - {label: 'Ratio: 600', name: '600', widget: 'string', default: '1.56rem'} + - {label: 'Ratio: 700', name: '700', widget: 'string', default: '1.95rem'} + - {label: 'Ratio: 800', name: '800', widget: 'string', default: '2.44rem'} + - {label: 'Ratio: 900', name: '900', widget: 'string', default: '3.05rem'} + - {label: 'Max Size', name: 'max', widget: 'string', default: '4rem'} + - label: 'Colors' + name: 'colors' + widget: 'object' + fields: + - { + label: 'Primary', + name: 'primary', + widget: 'string', + default: 'hsl(208, 57%, 21%)', + } + - { + label: 'Primary Shade (darker)', + name: 'primary-shade', + widget: 'string', + default: 'hsl(208, 56%, 14%)', + } + - { + label: 'Primary Glare (lighter)', + name: 'primary-glare', + widget: 'string', + default: 'hsl(207, 57%, 31%)', + } + - { + label: 'Highlight', + name: 'highlight', + widget: 'string', + default: 'hsl(42, 98%, 77%)', + } + - { + label: 'Light', + name: 'light', + widget: 'string', + default: 'hsl(0, 0%, 100%)', + } + - {label: 'Mid', name: 'mid', widget: 'string', default: 'hsl(0, 0%, 80%)'} + - { + label: 'Dark', + name: 'dark', + widget: 'string', + default: 'hsl(0, 0%, 20%)', + } + - { + label: 'Slate', + name: 'slate', + widget: 'string', + default: 'hsl(0, 0%, 25%)', + } + - label: 'Fonts' + name: 'fonts' + widget: 'hidden' + default: + { + 'base': '"-apple-system, BlinkMacSystemFont, ''Segoe UI'', Roboto, Helvetica, Arial, sans-serif, ''Apple Color Emoji'', ''Segoe UI Emoji'', ''Segoe UI Symbol''"', + 'serif': '"''Lora'', serif"', + } diff --git a/packages/docs/src/admin/previews.js b/packages/docs/src/admin/previews.js new file mode 100644 index 000000000..82dcd1f2c --- /dev/null +++ b/packages/docs/src/admin/previews.js @@ -0,0 +1,92 @@ +const {w3DateFilter, markdownFilter, dateFilter, helpers} = previewUtil; + +const env = nunjucks.configure(); + +env.addFilter('w3DateFilter', w3DateFilter); +env.addFilter('markdownFilter', markdownFilter); +env.addFilter('dateFilter', dateFilter); + +const Preview = ({entry, path, context}) => { + const data = context(entry.get('data').toJS()); + const html = env.render(path, {...data, helpers}); + return
; +}; + +const Home = ({entry}) => ( + ({ + title, + content: markdownFilter(body), + postsHeading, + archiveButtonText, + collections: { + postFeed: [ + { + url: 'javascript:void(0)', + date: new Date(), + data: { + title: 'Sample Post' + } + } + ] + } + })} + /> +); + +const Post = ({entry}) => ( + ({ + title, + date, + content: markdownFilter(body || '') + })} + /> +); + +const Page = ({entry}) => ( + ({ + title, + content: markdownFilter(body || '') + })} + /> +); + +const SiteData = ({entry}) => ( + ({ + site: { + name, + shortDesc, + showThemeCredit + } + })} + /> +); + +const Nav = ({entry}) => ( + ({ + navigation: { + items + } + })} + /> +); + +CMS.registerPreviewTemplate('home', Home); +CMS.registerPreviewTemplate('posts', Post); +CMS.registerPreviewTemplate('generic_pages', Page); +CMS.registerPreviewTemplate('site_data', SiteData); +CMS.registerPreviewTemplate('nav', Nav); diff --git a/packages/docs/src/admin/util.js b/packages/docs/src/admin/util.js new file mode 100644 index 000000000..302b159cf --- /dev/null +++ b/packages/docs/src/admin/util.js @@ -0,0 +1,11 @@ +import helpers from '../_data/helpers'; +import dateFilter from '../filters/date-filter'; +import markdownFilter from '../filters/markdown-filter'; +import w3DateFilter from '../filters/w3-date-filter'; + +export { + helpers, + dateFilter, + markdownFilter, + w3DateFilter, +}; diff --git a/packages/docs/src/archive.md b/packages/docs/src/archive.md new file mode 100644 index 000000000..76533c10f --- /dev/null +++ b/packages/docs/src/archive.md @@ -0,0 +1,4 @@ +--- +title: 'Posts Archive' +layout: 'layouts/archive.njk' +--- diff --git a/packages/docs/src/demos.md b/packages/docs/src/demos.md new file mode 100644 index 000000000..441563fc1 --- /dev/null +++ b/packages/docs/src/demos.md @@ -0,0 +1,13 @@ +--- +layout: layouts/demos.njk +title: Pattern Lab Demos +category: getting-started +--- + + + +Here’s a list of Pattern Lab’s UI starterkits that you can use to kickstart your UI design system project, as well as a list of Pattern Labs from around the web. + + + + diff --git a/packages/docs/src/demos/bolt-design-systems.md b/packages/docs/src/demos/bolt-design-systems.md new file mode 100644 index 000000000..54d091aa6 --- /dev/null +++ b/packages/docs/src/demos/bolt-design-systems.md @@ -0,0 +1,11 @@ +--- +title: Bolt Design System +description: The Bolt Design System provides robust Twig and Web Component-powered UI components, reusable visual styles, and powerful tooling to help developers, designers, and content authors build, maintain, and scale best of class digital experiences. +url: https://boltdesignsystem.com/ +category: example +tags: + - demo-in-the-wild + - demo-content + - code +image: /images/800x600.png +--- diff --git a/packages/docs/src/demos/brad-frosts-website-pl.md b/packages/docs/src/demos/brad-frosts-website-pl.md new file mode 100644 index 000000000..41bbe38c1 --- /dev/null +++ b/packages/docs/src/demos/brad-frosts-website-pl.md @@ -0,0 +1,11 @@ +--- +title: Brad Frost’s website PL +description: Pattern Lab helps you and your team build thoughtful, pattern-driven user interfaces using atomic design principles. +url: https://bradfrostdotcom-pl.netlify.com/ +category: example +tags: + - demo-in-the-wild + - demo-content + - code +image: /images/800x600.png +--- diff --git a/packages/docs/src/demos/handlebars-base-starterkit.md b/packages/docs/src/demos/handlebars-base-starterkit.md new file mode 100644 index 000000000..44ea634e4 --- /dev/null +++ b/packages/docs/src/demos/handlebars-base-starterkit.md @@ -0,0 +1,11 @@ +--- +title: Handlebars Base Starter Kit +description: The StarterKit for Handlebars is meant to be used as a demonstration of a Handlebars-based project in Pattern Lab. +url: https://patternlab-handlebars-preview.netlify.com/?p=all +category: starterkit +tags: + - demo-hbs-starter-kits + - demo-content + - code +image: /images/800x600.png +--- diff --git a/packages/docs/src/demos/handlebars-demo-starterkit.md b/packages/docs/src/demos/handlebars-demo-starterkit.md new file mode 100644 index 000000000..28387c606 --- /dev/null +++ b/packages/docs/src/demos/handlebars-demo-starterkit.md @@ -0,0 +1,11 @@ +--- +title: Handlebars Demo Starter Kit +description: The Demo StarterKit for Handlebars is meant to be used as a demonstration of a Handlebars-based project in Pattern Lab. +url: https://www.npmjs.com/package/@pattern-lab/starterkit-handlebars-demo +category: starterkit +tags: + - demo-hbs-starter-kits + - demo-content + - code +image: /images/800x600.png +--- diff --git a/packages/docs/src/demos/handlebars-vanilla-starterkit.md b/packages/docs/src/demos/handlebars-vanilla-starterkit.md new file mode 100644 index 000000000..4f7485a52 --- /dev/null +++ b/packages/docs/src/demos/handlebars-vanilla-starterkit.md @@ -0,0 +1,11 @@ +--- +title: Handlebars Vanilla Starter Kit +description: The Vanilla StarterKit for Handlebars is meant to be used as a demonstration of a Handlebars-based project in Pattern Lab. +url: https://www.npmjs.com/package/@pattern-lab/starterkit-handlebars-vanilla +category: starterkit +tags: + - demo-hbs-starter-kits + - demo-content + - code +image: /images/800x600.png +--- diff --git a/packages/docs/src/demos/pattern-lab-two-mustache-demo.md b/packages/docs/src/demos/pattern-lab-two-mustache-demo.md new file mode 100644 index 000000000..56fda1e32 --- /dev/null +++ b/packages/docs/src/demos/pattern-lab-two-mustache-demo.md @@ -0,0 +1,11 @@ +--- +title: Pattern Lab 2 Mustache Demo +description: Pattern Lab 1 projects should work with minimal changes in Pattern Lab 2. +url: http://demo.patternlab.io/ +category: starterkit +tags: + - demo-PL2 + - demo-content + - code +image: /images/800x600.png +--- diff --git a/packages/docs/src/demos/twig-base.md b/packages/docs/src/demos/twig-base.md new file mode 100644 index 000000000..0da6259b6 --- /dev/null +++ b/packages/docs/src/demos/twig-base.md @@ -0,0 +1,13 @@ +--- +title: Twig Base +description: The Base StarterKit for Twig is meant to be used as a near-blank starting point for Twig-based projects in Pattern Lab. +url: https://github.com/pattern-lab/starterkit-twig-base +category: starterkit +tags: + - demo-twig + - demo-content + - code +image: /images/800x600.png +--- + +loremi diff --git a/packages/docs/src/docs/a-post-with-code-samples.md b/packages/docs/src/docs/a-post-with-code-samples.md new file mode 100644 index 000000000..d5c83f6c7 --- /dev/null +++ b/packages/docs/src/docs/a-post-with-code-samples.md @@ -0,0 +1,67 @@ +--- +title: DOCS DOCS DOCS +tags: + - demo-content + - code + - blog +eleventyNavigation: + key: DOCS DOCS DOCS + order: 300 +--- + +The best way to demo a code post is to display a real life post, so check out this one from [andy-bell.design](https://andy-bell.design/wrote/creating-a-full-bleed-css-utility/) about a full bleed CSS utility. + +--- + +Sometimes you want to break your components out of the constraints that they find themselves in. A common situation where this occurs is when you don’t have much control of the container that it exists in, such as a CMS main content area. + +This is even more the case with editing tools such as the [WordPress Gutenberg editor](https://wordpress.org/gutenberg/), where in theory, you could pull in a component from a design system and utilise it in the main content of your web page. In these situations, it can be pretty darn handy to have a little utility that makes the element 100% of the viewport’s width _and_ still maintain its flow within its parent container. + +This is when I normally pull the `.full-bleed` utility class out of my back pocket. + +## The `.full-bleed` utility + +It’s small, but hella mighty: + +```css +.full-bleed { + width: 100vw; + margin-left: 50%; + transform: translateX(-50%); +} +``` + +Here it is in a context where it makes a fancy `
+ +{{> organisms-footer }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/checkout.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/checkout.hbs new file mode 100644 index 000000000..41046e131 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/checkout.hbs @@ -0,0 +1,29 @@ +{{> organisms-header }} +
+ +
+ {{> molecules-progress-tracker }} + {{> molecules-page-header }} +
+
+
+
+ {{#with definitionListSection }} + {{> organisms-definition-list-list }} + {{/ with }} + {{# totalPrice }} +
+ + {{ number }} +
+ {{/ totalPrice }} +
+
+
+ {{> organisms-form }} +
+
+
+
+
+{{> organisms-footer }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/confirmation.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/confirmation.hbs new file mode 100644 index 000000000..67c83694c --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/confirmation.hbs @@ -0,0 +1,14 @@ +{{> organisms-header }} +
+ +
+ {{> molecules-progress-tracker }} + {{> molecules-page-header }} +
+ {{> organisms-text-passage }} +
+ +
+ +
+{{> organisms-footer }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/detail.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/detail.hbs new file mode 100644 index 000000000..4b35ddde0 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/detail.hbs @@ -0,0 +1,32 @@ +{{> organisms-header }} +
+
+ {{> molecules-page-header }} +
+ +
+
+ +
+ {{# featureImage }} + {{ alt }} + {{/ featureImage }} +
+ +
+
+ {{> organisms-text-passage }} +
+ {{# checkoutForm }} + {{> organisms-form }} + {{/ checkoutForm }} +
+ +
+ {{#with stackedBlockSection }} + {{> organisms-stacked-block-section }} + {{/ with }} +
+ +
+{{> organisms-footer }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/homepage.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/homepage.hbs new file mode 100644 index 000000000..16de05be2 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/homepage.hbs @@ -0,0 +1,22 @@ +{{> organisms-header }} +
+ +
+ {{# hero }} + {{> molecules-hero }} + {{/ hero }} + + {{# promoBlock }} + {{> molecules-promo-block }} + {{/ promoBlock }} + + + {{#with saleSection}} + {{> organisms-stacked-block-section }} + {{/with}} + +
+ + +
+{{> organisms-footer }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/page.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/page.hbs new file mode 100644 index 000000000..ae4791473 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/page.hbs @@ -0,0 +1,15 @@ +{{> organisms-header }} + +
+ +
+ + {{> molecules-page-header }} + + {{> organisms-text-passage }} + +
+ +
+ +{{> organisms-footer }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/review.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/review.hbs new file mode 100644 index 000000000..ef60c06a8 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/03-templates/review.hbs @@ -0,0 +1,32 @@ +{{> organisms-header }} +
+ +
+ {{> molecules-progress-tracker }} + {{> molecules-page-header }} +
+
+
+
+ {{#with definitionListSection }} + {{> organisms-definition-list-list }} + {{/ with }} + {{# totalPrice }} +
+ + {{ number }} +
+ {{/ totalPrice }} +
+
+
+
+ {{> organisms-text-passage }} +
+ {{> organisms-button-group }} +
+
+
+
+
+{{> organisms-footer }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/00-homepage.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/00-homepage.hbs new file mode 100644 index 000000000..d9894d4a7 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/00-homepage.hbs @@ -0,0 +1 @@ +{{> templates-homepage }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/00-homepage.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/00-homepage.json new file mode 100644 index 000000000..b6b30dd08 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/00-homepage.json @@ -0,0 +1,126 @@ +{ + "hero" : { + "url": "link.pages-detail", + "src": "../../images/group-shoes.jpg", + "alt": "Group of shoes", + "heading" : "Shoes For The Soul" + }, + "promoBlock": [ + { + "promoBlockLink": { + "url": "#" + }, + "src": "../../images/women-boots.jpg", + "alt": "Women's boots", + "heading": "Go Out In Style", + "description": "Dive into the Fall season with these stylish casual boots that are great for a jump in the leaves or a night out on the town." + }, + { + "promoBlockLink": { + "url": "#" + }, + "styleModifier": "c-promo-block--right", + "src": "../../images/men-casual-shoes.jpg", + "alt": "striped sneakers", + "heading": "Casual But Classy", + "description": "Feel comfortable in your own shoes no matter what the situation." + }, + { + "promoBlockLink": { + "url": "#" + }, + "src": "../../images/women-hiking-boots.jpg", + "alt": "hiking boots in woods", + "heading": "Immerse Yourself In Nature", + "description": "Explore the outdoors and clear your head with a hike in one of our sleek boot styles." + }, + { + "promoBlockLink": { + "url": "#" + }, + "styleModifier": "c-promo-block--right u-margin-bottom-large", + "src": "../../images/kids-velcro.jpg", + "alt": "kid's velcro shoes", + "heading": "Discover the Unknown", + "description": "Take the path least traveled and write your own story with our shoes." + } + ], + "toutSection" : { + "styleModifier" : "l-grid--4up", + "toutList" : [ + { + "url": "link.pages-category", + "headline": "Women's fashion", + "imgLandscapeSrc" : "../../images/woman-outside-brownstone.jpg", + "imgLandscapeAlt" : "Woman in sweater" + }, + { + "url": "link.pages-category", + "headline": "Men's fashion", + "imgLandscapeSrc" : "../../images/smiling-man-in-formalwear.jpg", + "imgLandscapeAlt" : "Man in formalwear" + }, + { + "url": "link.pages-category", + "headline": "Kid's Fashion", + "imgLandscapeSrc" : "../../images/father-daughter-fashion.jpg", + "imgLandscapeAlt" : "Child with father" + } + ] + }, + "saleSection" : { + "sectionTitle": "On Sale", + "stackedBlockList": [ + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/men-blue-shoes.jpg", + "alt" : "blue walking shoes" + }, + "headline" : "Walking Shoes", + "excerpt" : "This casual pair of shoes will have you looking dapper in the city", + "label" : "$60", + "onSale": { + "badge": "Sale", + "price": "$90" + } + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/women-hiking-boots.jpg", + "alt" : "leather boots" + }, + "headline" : "Leather Boots", + "excerpt" : "Hike down the trail in these durable, 100% authentic leather boots.", + "label" : "$120", + "onSale": { + "badge": "Sale", + "price": "$150" + } + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/women-blue-heels.jpg", + "alt" : "blue heels" + }, + "headline" : "Blue Suede Heels", + "excerpt" : "Strut your stuff on the streets with these stunning high heels", + "label" : "$80", + "onSale": { + "badge": "Sale", + "price": "$110" + } + + } + ], + "btnTag": false, + "linkTag": true, + "url": "link.pages-category-on-sale", + "buttonText" : "See All Sale Items" + } +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category.hbs new file mode 100644 index 000000000..f0585d1e0 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category.hbs @@ -0,0 +1 @@ +{{> templates-category }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category.json new file mode 100644 index 000000000..4320309db --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category.json @@ -0,0 +1,142 @@ +{ + "page": { + "title" : "Women's Shoes", + "description": false + }, + "stackedBlockSection" : { + "styleModifier": "u-margin-bottom-large", + "stackedBlockList": [ + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/women-blue-heels.jpg", + "alt" : "blue high heels" + }, + "headline" : "Blue Suede Heels", + "excerpt" : "Available in 5 colors", + "label" : "$80", + "onSale": { + "badge": "Sale", + "price": "$110" + } + + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/women-hiking-boots.jpg", + "alt" : "brown leather boots" + }, + "headline" : "Leather Boots", + "excerpt" : "Available in 2 colors. Overnight shipping available", + "label" : "$120", + "onSale": { + "badge": "Sale", + "price": "$150" + } + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/women-leather-booties.jpg", + "alt" : "black leather booties" + }, + "headline" : "Black Leather Booties", + "excerpt" : "Available in 3 colors.", + "label" : "$95", + "onSale": { + "badge": "Sale", + "price": "$135" + } + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/women-boot-wedges.jpg", + "alt" : "women's wedge boots" + }, + "headline" : "Suede Booties", + "excerpt" : "Available in 2 colors. Overnight shipping available", + "label" : "$120" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/women-adidas.jpg", + "alt" : "Adidas shoes" + }, + "headline" : "White Adidas Sneakers", + "excerpt" : "Available in 2 colors", + "label" : "$90" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/women-boots.jpg", + "alt" : "light tan leather boots" + }, + "headline" : "Light Tan Boots", + "excerpt" : "Available in 3 colors", + "label" : "$70" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/women-colorful.jpg", + "alt" : "technicolor sneakers" + }, + "headline" : "Multicolored Running Shoes", + "excerpt": false, + "label" : "$75" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/women-combat-boots.jpg", + "alt" : "black leather high ankle boots" + }, + "headline" : "Black Leather Boots", + "excerpt" : "Made of 100% authentic leather", + "label" : "$180" + + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/women-white-heels.jpg", + "alt" : "white high heels" + }, + "headline" : "Shiny High Heels", + "excerpt" : "Available in 5 colors", + "label" : "$95" + } + ] + }, + "paginationList" : [ + { + "itemModifier": "is-disabled", + "number" : "Prev" + }, + { + "itemModifier": "is-active", + "number" : "1" + }, + { + "url": "link.pages-category-page-two", + "number" : "2" + }, + { + "number" : "Next", + "url": "link.pages-category-page-two" + } + ] +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~holiday-guide.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~holiday-guide.json new file mode 100644 index 000000000..e06b4a3da --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~holiday-guide.json @@ -0,0 +1,12 @@ +{ + "page": { + "title": false + }, + "hero": { + "url": "link.pages-detail", + "src": "../../images/holiday.jpg", + "alt": "Group of shoes", + "heading": "2019 Holiday Gift Guide" + }, + "paginationList": false +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~kids.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~kids.json new file mode 100644 index 000000000..f7a28e47e --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~kids.json @@ -0,0 +1,83 @@ +{ + "page": { + "title" : "Kids' Shoes", + "description": false + }, + "stackedBlockSection" : { + "stackedBlockList": [ + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/men-chucks.jpg", + "alt" : "chuck taylor shoes" + }, + "headline" : "Converse Chuck Taylor's", + "excerpt" : "Available in both boys' and girls' sizes", + "label" : "$50", + "onSale": { + "badge": "Sale", + "price": "$85" + } + + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/girls-high-tops.jpg", + "alt" : "girl's high tops" + }, + "headline" : "Girl's High Tops", + "excerpt" : "Available in 4 color combinations", + "label" : "$90" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/kids-green-shoes.jpg", + "alt" : "toddler green shoes" + }, + "headline" : "Toddler Green Shoes", + "excerpt" : "Suitable for ages 0-2 years", + "label" : "$230" + + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/kids-technicolor-nikes.jpg", + "alt" : "multicolored Nike shoes" + }, + "headline" : "Multicolored Nikes", + "excerpt" : "Available in toddler and youth sizes", + "label" : "$120" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/girls-nike.jpg", + "alt" : "girl's nike sneakers" + }, + "headline" : "Girl's Nike Sneakers", + "excerpt" : "Available in 5 colors", + "label" : "$70" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/kids-velcro.jpg", + "alt" : "velcro shoes" + }, + "headline" : "Velcro Shoes", + "excerpt" : "Available in both boys' and girls' sizes", + "label" : "$70" + } + ] + }, + "paginationList": false +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~men.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~men.json new file mode 100644 index 000000000..e4f142e13 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~men.json @@ -0,0 +1,110 @@ +{ + "page": { + "title" : "Men's Shoes", + "description": false + }, + "stackedBlockSection" : { + "styleModifier": "u-margin-bottom-large", + "stackedBlockList": [ + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/men-blue-shoes.jpg", + "alt" : "blue walking shoes" + }, + "headline" : "Walking Shoes", + "excerpt" : "This casual pair of shoes will have you looking dapper in the city", + "label" : "$60", + "onSale": { + "badge": "Sale", + "price": "$90" + } + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/men-brown-shoes.jpg", + "alt" : "brown embroidered shoes" + }, + "headline" : "Embroidered Dress Shoes", + "excerpt" : "Available in 2 colors. Made of 100% authentic leather", + "label" : "$120", + "onSale": { + "badge": "Sale", + "price": "$155" + } + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/men-brown-dress-shoes.jpg", + "alt" : "brown dress shoes" + }, + "headline" : "Brown Dress Shoes", + "excerpt" : "Made of 100% authentic leather in USA", + "label" : "$140" + + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/men-casual-shoes.jpg", + "alt" : "black striped sneakers" + }, + "headline" : "Striped Casual Walking Shoes", + "excerpt" : "Available in 3 styles", + "label" : "$70" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/men-gray-shoes.jpg", + "alt" : "Gray walking shoes." + }, + "headline" : "Gray casual walking shoes", + "excerpt" : false, + "label" : "$180" + + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/men-loafers.jpg", + "alt" : "men's loafers" + }, + "headline" : "Loafers", + "excerpt" : "Available in a variety of colors", + "label" : "$180" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/men-walking-shoes.jpg", + "alt" : "men's running shoes" + }, + "headline" : "Running Shoes", + "excerpt" : "Available in low-top and high-top.", + "label" : "$95" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/men-winter-boots.jpg", + "alt" : "men's winter boots" + }, + "headline" : "Steel Toe Boots", + "excerpt" : "Available in 2 colors. Made in USA", + "label" : "$190" + } + ] + }, + "paginationList": false +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~on-sale.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~on-sale.json new file mode 100644 index 000000000..994278d7e --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~on-sale.json @@ -0,0 +1,104 @@ +{ + "page": { + "title" : "On Sale", + "description": false + }, + "stackedBlockSection" : { + "styleModifier": "u-margin-bottom-large", + "stackedBlockList": [ + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/men-blue-shoes.jpg", + "alt" : "blue walking shoes" + }, + "headline" : "Walking Shoes", + "excerpt" : "This casual pair of shoes will have you looking dapper in the city", + "label" : "$60", + "onSale": { + "badge": "Sale", + "price": "$90" + } + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/women-hiking-boots.jpg", + "alt" : "leather boots" + }, + "headline" : "Leather Boots", + "excerpt" : "Hike down the trail in these durable, 100% authentic leather boots.", + "label" : "$120", + "onSale": { + "badge": "Sale", + "price": "$150" + } + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/women-blue-heels.jpg", + "alt" : "blue heels" + }, + "headline" : "Blue Suede Heels", + "excerpt" : "Strut your stuff on the streets with these stunning high heels", + "label" : "$80", + "onSale": { + "badge": "Sale", + "price": "$110" + } + + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/men-chucks.jpg", + "alt" : "chuck taylor shoes" + }, + "headline" : "Boys Chucks", + "excerpt" : "These shoes are a great mix between athlete and trend setter", + "label" : "$50", + "onSale": { + "badge": "Sale", + "price": "$85" + } + + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/men-brown-shoes.jpg", + "alt" : "woman in sweater" + }, + "headline" : "Pinstripe Skirt and Tank Top", + "excerpt" : "This pinstripe skirt and tank top are a great pairing for a friendly get-together", + "label" : "$120", + "onSale": { + "badge": "Sale", + "price": "$155" + } + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/women-leather-booties.jpg", + "alt" : "woman in sweater" + }, + "headline" : "Long Button-Down Sweater", + "excerpt" : "Comes in 2 colors", + "label" : "$95", + "onSale": { + "badge": "Sale", + "price": "$135" + } + } + ] + }, + "paginationList": false +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~page-two.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~page-two.json new file mode 100644 index 000000000..adb64d14d --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/01-category~page-two.json @@ -0,0 +1,41 @@ +{ + "page": { + "title" : "Women's Shoes", + "description": false + }, + "stackedBlockSection" : { + "styleModifier": "u-margin-bottom-large", + "stackedBlockList": [ + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../../images/women-shiny-sneakers.jpg", + "alt" : "patent leather sneakers" + }, + "headline" : "Patent Leather Everyday Shoes", + "excerpt" : "Available in brown and black. Overnight shipping available", + "label" : "$190" + } + ] + }, + "paginationList" : [ + { + "url": "link.pages-category", + "number" : "Prev" + }, + { + "url": "link.pages-category", + "number" : "1" + }, + { + "itemModifier": "is-active", + "url": "link.pages-category-page-two", + "number" : "2" + }, + { + "itemModifier": "is-disabled", + "number" : "Next" + } + ] +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/02-detail.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/02-detail.hbs new file mode 100644 index 000000000..c024f1f62 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/02-detail.hbs @@ -0,0 +1 @@ +{{> templates-detail }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/02-detail.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/02-detail.json new file mode 100644 index 000000000..5c8446d97 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/02-detail.json @@ -0,0 +1,125 @@ +{ + "page": { + "title": "Suede Booties", + "description": false + }, + "featureImage": { + "src": "../../images/women-boot-wedges.jpg", + "alt": "Boot wedges" + }, + "htmlText": "

These suede booties are the perfect shoes to accent your outfit whether heading to a casual dinner or an elaborate gala.

These booties come in a sepia (shown) or a stark black. These are made of 100% suede in U.S.A.

$120

", + "priceSection": { + "label": "$120" + }, + "checkoutForm": { + "formFields": { + "items": [ + { + "selectField": { + "label": "Pick a Size", + "selectOptions" : [ + { + "value" : "size-1", + "option" : "Size 5" + }, + { + "value" : "size-2", + "option" : "Size 5.5" + }, + { + "value" : "size-3", + "option" : "Size 6" + }, + { + "value" : "size-4", + "option" : "Size 6.5" + }, + { + "value" : "size-5", + "option" : "Size 7" + }, + { + "value" : "size-6", + "option" : "Size 7.5" + }, + { + "value" : "size-7", + "option" : "Size 8" + }, + { + "value" : "size-8", + "option" : "Size 8.5" + }, + { + "value" : "size-9", + "option" : "Size 9" + }, + { + "value" : "size-10", + "option" : "Size 9.5" + }, + { + "value" : "size-11", + "option" : "Size 10" + }, + { + "value" : "size-12", + "option" : "Size 10.5" + }, + { + "value" : "size-13", + "option" : "Size 11" + } + ] + } + } + ] + }, + "buttonGroup": [ + { + "buttonTag": false, + "linkTag": true, + "url": "link.pages-cart", + "buttonText": "Add To Cart" + } + ] + }, + "stackedBlockSection": { + "sectionTitle": "Related Items", + "stackedBlockList": [ + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/men-blue-shoes.jpg", + "alt" : "blue walking shoes" + }, + "headline" : "Walking Shoes", + "excerpt" : "This casual pair of shoes will have you looking dapper in the city", + "label" : "$60" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/women-hiking-boots.jpg", + "alt" : "leather boots" + }, + "headline" : "Leather Boots", + "excerpt" : "Hike down the trail in these durable, 100% authentic leather boots.", + "label" : "$120" + }, + { + "styleModifier" : "", + "url" : "link.pages-detail", + "stackedBlockMedia": { + "src" : "../../images/women-blue-heels.jpg", + "alt" : "blue heels" + }, + "headline" : "Blue Suede Heels", + "excerpt" : "Strut your stuff on the streets with these stunning high heels", + "label" : "$80" + } + ] + } +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/03-cart.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/03-cart.hbs new file mode 100644 index 000000000..b30fd0db7 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/03-cart.hbs @@ -0,0 +1 @@ +{{> templates-cart }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/03-cart.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/03-cart.json new file mode 100644 index 000000000..be228c2b9 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/03-cart.json @@ -0,0 +1,48 @@ +{ + "page": { + "title": "You have 2 items in your cart", + "description": "Please review your cart details before checking out." + }, + "stripeList": [ + { + "url": "#", + "src": "../../images/women-boot-wedges.jpg", + "alt": "placeholder image", + "stripeField": { + "styleModifier": "c-field--small", + "label": "Quantity", + "value": "1" + }, + "headline": "Suede Booties", + "cartPrice": { + "label": "Price", + "meta": "$120" + } + }, + { + "url": "#", + "src": "../../images/men-brown-dress-shoes.jpg", + "alt": "placeholder image", + "stripeField": { + "styleModifier": "c-field--small", + "label": "Quantity", + "value": "1" + }, + "headline": "Brown Dress Shoes", + "cartPrice": { + "label": "Price", + "meta": "$140" + } + } + ], + "totalPrice": { + "label": "Total", + "number": "$360" + }, + "checkoutButton": { + "buttonTag": false, + "linkTag": true, + "url": "link.pages-checkout", + "buttonText": "Checkout" + } +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout.hbs new file mode 100644 index 000000000..0788a94e1 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout.hbs @@ -0,0 +1 @@ +{{> templates-checkout }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout.json new file mode 100644 index 000000000..3a20406c0 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout.json @@ -0,0 +1,95 @@ +{ + "page": { + "title": "Shipping Information", + "description": "Please fill out your shipping information below." + }, + "formFields" : { + "items": [ + { + "textField": { + "label" : "Name" + } + }, + { + "textField": { + "label" : "Address" + } + }, + { + "textField": { + "label" : "City" + } + }, + { + "textField": { + "label" : "State" + } + }, + { + "textField": { + "label" : "Zip Code" + } + }, + { + "inlineCheckbox": { + "styleModifier": "u-margin-bottom-large", + "label": "Billing address is the same as shipping" + } + } + ] + }, + "selectField": false, + "buttonGroup" : [ + { + "buttonTag" : false, + "linkTag" : true, + "url" : "link.pages-checkout-billing", + "buttonText": "Continue to Billing" + } + ], + "definitionListSection": { + "styleModifier":"c-definition-list-list--lined", + "definitionItems": [ + { + "definitionList": { + "items": [ + { + "term": "Item", + "description": "Suede Booties" + }, + { + "term": "Quantity", + "description": "1" + }, + { + "term": "Price", + "description": "$120" + } + ] + } + }, + { + "definitionList": { + "items": [ + { + "term": "Item", + "description": "Brown Dress Shoes" + }, + { + "term": "Quantity", + "description": "1" + }, + { + "term": "Price", + "description": "$140" + } + ] + } + } + ] + }, + "totalPrice": { + "label": "Total", + "number": "$360" + } +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout~billing-same-shipping.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout~billing-same-shipping.json new file mode 100644 index 000000000..de5857c32 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout~billing-same-shipping.json @@ -0,0 +1,85 @@ +{ + "progressTracker" : { + "items": [ + { + "number": "1", + "label": "Shipping Information" + }, + { + "styleModifier": "is-current", + "number": "2", + "label": "Billing Information" + }, + { + "number": "3", + "label": "Review Order" + }, + { + "number": "4", + "label": "Confirmation" + } + ] + }, + "page": { + "title": "Billing Information", + "description": "Please fill out your billing address and payment information below." + }, + "formFields" : { + "items": [ + { + "textField": { + "label" : "Name", + "value": "John Smith" + } + }, + { + "textField": { + "label" : "Address", + "value": "99 Main Street" + } + }, + { + "textField": { + "label" : "City", + "value": "Pittsburgh" + } + }, + { + "textField": { + "label" : "State", + "value": "PA" + } + }, + { + "textField": { + "label" : "Zip Code", + "value": "15202" + } + }, + { + "textField": { + "label" : "Credit Card Number" + } + }, + { + "textField": { + "label" : "Expiration Date" + } + }, + { + "textField": { + "label" : "CVV" + } + } + ] + }, + "selectField": false, + "buttonGroup" : [ + { + "buttonTag" : false, + "linkTag" : true, + "url" : "link.pages-review", + "buttonText": "Continue To Review Order" + } + ] +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout~billing.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout~billing.json new file mode 100644 index 000000000..8d6396e92 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/04-checkout~billing.json @@ -0,0 +1,80 @@ +{ + "progressTracker" : { + "items": [ + { + "number": "1", + "label": "Shipping Information" + }, + { + "styleModifier": "is-current", + "number": "2", + "label": "Billing Information" + }, + { + "number": "3", + "label": "Review Order" + }, + { + "number": "4", + "label": "Confirmation" + } + ] + }, + "page": { + "title": "Billing Information", + "description": "Please fill out your billing address and payment information below." + }, + "formFields" : { + "items": [ + { + "textField": { + "label" : "Name" + } + }, + { + "textField": { + "label" : "Address" + } + }, + { + "textField": { + "label" : "City" + } + }, + { + "textField": { + "label" : "State" + } + }, + { + "textField": { + "label" : "Zip Code" + } + }, + { + "textField": { + "label" : "Credit Card Number" + } + }, + { + "textField": { + "label" : "Expiration Date" + } + }, + { + "textField": { + "label" : "CVV" + } + } + ] + }, + "selectField": false, + "buttonGroup" : [ + { + "buttonTag" : false, + "linkTag" : true, + "url" : "link.pages-review", + "buttonText": "Continue To Review Order" + } + ] +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/05-review.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/05-review.hbs new file mode 100644 index 000000000..f46f8b204 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/05-review.hbs @@ -0,0 +1 @@ +{{> templates-review }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/05-review.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/05-review.json new file mode 100644 index 000000000..ec0865197 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/05-review.json @@ -0,0 +1,81 @@ +{ + "progressTracker" : { + "items": [ + { + "number": "1", + "label": "Shipping Information" + }, + { + "number": "2", + "label": "Billing Information" + }, + { + "styleModifier": "is-current", + "number": "3", + "label": "Review Order" + }, + { + "number": "4", + "label": "Confirmation" + } + ] + }, + "page": { + "title": "Review Your Order", + "description": "Please make sure your information is correct below before submitting your order." + }, + "definitionListSection": { + "styleModifier":"c-definition-list-list--lined", + "definitionItems": [ + { + "definitionList": { + "items": [ + { + "term": "Item", + "description": "Suede Booties" + }, + { + "term": "Quantity", + "description": "1" + }, + { + "term": "Price", + "description": "$120" + } + ] + } + }, + { + "definitionList": { + "items": [ + { + "term": "Item", + "description": "Brown Dress Shoes" + }, + { + "term": "Quantity", + "description": "1" + }, + { + "term": "Price", + "description": "$140" + } + ] + } + } + ] + }, + "totalPrice": { + "label": "Total", + "number": "$360" + }, + "htmlText": "

Shipping Address

John Smith
99 Main Street
Pittsburgh, PA 15202

Billing Address

John Smith
99 Main Street
Pittsburgh, PA 15202

Payment Information

Card Type Visa
Card # ********0000
Expiration 12/25

", + "buttonGroup":[ + { + "buttonTag": false, + "linkTag": true, + "url": "link.pages-confirmation", + "buttonText": "Submit Order" + } + ] +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/06-confirmation.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/06-confirmation.hbs new file mode 100644 index 000000000..399d5b73c --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/06-confirmation.hbs @@ -0,0 +1 @@ +{{> templates-confirmation }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/06-confirmation.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/06-confirmation.json new file mode 100644 index 000000000..20dbfd149 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/06-confirmation.json @@ -0,0 +1,28 @@ +{ + "progressTracker" : { + "items": [ + { + "number": "1", + "label": "Shipping Information" + }, + { + "number": "2", + "label": "Billing Information" + }, + { + "number": "3", + "label": "Review Order" + }, + { + "styleModifier": "is-current", + "number": "4", + "label": "Confirmation" + } + ] + }, + "page": { + "title": "Your Order Has Been Processed", + "description": false + }, + "htmlText": "

Your confirmation number is: 1234567890

Thank you for shopping with SoulSoles!

You will receive a confirmation email shortly.

" +} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/page.hbs b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/page.hbs new file mode 100644 index 000000000..2daad69fc --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/page.hbs @@ -0,0 +1 @@ +{{> templates-page }} diff --git a/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/page.json b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/page.json new file mode 100644 index 000000000..122b65b5d --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/_patterns/04-pages/page.json @@ -0,0 +1,7 @@ +{ + "page": { + "title": "General Page", + "description": "I'm a page and I'm cool" + }, + "htmlText": "

This is a text passage. This is cool.

" +} diff --git a/packages/starterkit-handlebars-demo/dist/css/pattern-scaffolding.css b/packages/starterkit-handlebars-demo/dist/css/pattern-scaffolding.css new file mode 100644 index 000000000..a2bfd7189 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/pattern-scaffolding.css @@ -0,0 +1,275 @@ +/** + * This stylesheet is for styles you want to include only when displaying demo + * styles for grids, animations, color swatches, etc. + * These styles will not be your production CSS. + */ +#sg-patterns { + -webkit-box-sizing: border-box !important; + box-sizing: border-box !important; + max-width: 100%; + padding: 0 1rem; +} + +.demo-animate { + background: #ddd; + padding: 1em; + margin-bottom: 1em; + text-align: center; + border-radius: 8px; + cursor: pointer; +} + +.sg-colors { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + list-style: none !important; + padding: 0 !important; + margin: 0 !important; +} + +@supports (display: grid) { + .sg-colors { + display: grid; + grid-gap: 10px; + grid-template-columns: repeat( + auto-fill, + minmax(180px, 1fr) + ); + } +} + +.sg-colors li { + -webkit-box-flex: 1; + -ms-flex: auto; + flex: auto; + padding: 0.3em; + margin: 0 0.5em 0.5em 0; + min-width: 5em; + max-width: 14em; + border: 1px solid #ddd; + border-radius: 8px; +} + +.sg-swatch { + display: block; + height: 4em; + margin-bottom: 0.3em; + border-radius: 5px; +} + +.sg-label { + font-size: 90%; + line-height: 1; +} + +/** + * Icon grid + */ +.icon-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); +} + +/** + * Icon grid item + */ +.icon-grid__item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 5.5rem; + height: 5.5rem; + background: #f5f5f5; + border: 1px solid #cccccd; + border-radius: 8px; + padding: 0.5rem; + margin: 0.5rem; +} + +/** + * Icon grid item title + */ +.icon-grid__item h3 { + font-size: 0.6rem; + margin-top: 1rem; +} + +/** +* Icon grid item svg +*/ +.icon-grid__item svg { + height: 16px; + width: 16px; +} + +.sg-pattern-example > footer > p { + display: none; +} + +/** +* For placeholder only block +* 1) Used for placeholder blocks for layouts +*/ +.fpo { + padding: 1rem; + background: #f5f5f5; + text-align: center; + font-weight: bold; + margin-bottom: 0.5rem; +} + +/** +* Dark for placeholder only block +*/ +.fpo-block--dark { + background: #808080; +} + +.sg-pattern-example small { + font-size: 10px; + display: block; + margin-top: 0.5rem; +} + +.sg-pattern-example small code { + font-size: inherit; + padding: 0.2em; +} + +/** + * Add height and overflow to two column fixed layout to show functionality only in style-guide example. + */ +.sg-pattern-example .l-page-layout--two-column-fixed { + height: 10rem; + overflow: auto; +} + +/** + * Add min-height of 0 to two column fixed to help with showing functionality only in style-guide example. + */ +.sg-pattern-example .l-page-layout--two-column-fixed .l-page-layout__secondary { + min-height: 0; +} + +/** + * FPO block within two column fixed layout within secondary section + * 1) Width expands entire container at small screens + */ +.sg-pattern-example + .l-page-layout--two-column-fixed + .l-page-layout__secondary + .fpo-block { + width: 100%; /* 1 */ +} + +/** + * FPO block within two column fixed layout within secondary section larger screens + * 1) Set height to 100% of viewport height + * 2) Set width = width of vertical header + */ +@media all and (min-width: 70em) { + .sg-pattern-example + .l-page-layout--two-column-fixed + .l-page-layout__secondary + .fpo-block { + width: 20rem; /* 1 */ + height: 100vh; /* 2 */ + margin-bottom: 0; + } +} + +/** + * Two column fixed layout within pattern example + * 1) Set height to height of placeholder content secondary section + * 2) Set overflow to auto so the secondary section stays fixed while the main section scrolls + */ +.sg-pattern-example .l-page-layout--two-column-fixed { + height: 18.3rem; + overflow: auto; +} + +/** + * Secondary section within pattern example and two-column fixed at larger screens + * 1) Position absolute to make this pattern similar to fixed vertical header + * 2) Height inherits so it doesn't span longer than the pattern window on "All" PL page + * 3) Float this left to get layout sections side by side within PL "View All" section + */ + +@media all and (min-width: 70em) { + .sg-pattern-example + .l-page-layout--two-column-fixed + .l-page-layout__secondary { + position: absolute; /* 1 */ + height: inherit; /* 2 */ + float: left; /* 3 */ + } +} + +/** + * Fpo block within pattern example, two column fixed, and secondary section + * 1) Make fpo block width of the container on small screens + */ +.sg-pattern-example + .l-page-layout--two-column-fixed + .l-page-layout__secondary + .fpo-block { + width: 100%; /* 1 */ +} + +/** + * FPO Block Within pattern example, two column fixed, and secondary layout larger screens + * 1) Added width similar to style guide vertical header + * 2) Height inherits so it doesn't span longer than the pattern window on "All" PL page + */ +@media all and (min-width: 70em) { + .sg-pattern-example + .l-page-layout--two-column-fixed + .l-page-layout__secondary + .fpo-block { + width: 20rem; /* 1 */ + height: inherit; /* 2 */ + } +} + +/** + * Add height of main to get appearance of side bar staying fixed while main window scrolls. + */ +.sg-pattern-example .l-page-layout--two-column-fixed .l-page-layout__main { + height: 30rem; +} + +/** + * Add height of fpo block to equal height of main to help with scrolling main window/fixed sidebar effect. + */ +.sg-pattern-example + .l-page-layout--two-column-fixed + .l-page-layout__main + .fpo-block { + height: 30rem; + margin-bottom: 0; +} + +/** + * Vertical header in pattern example at large screens + */ +@media all and (min-width: 70em) { + .sg-pattern-example .c-header--vertical { + max-width: 20rem; + } +} + +#molecules-footer-nav .sg-pattern-example { + background: #041544; +} + +.sg-pattern .c-hero { + margin: 0; +} + +.sg-pattern .c-promo-block { + margin: 0; +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_colors.scss b/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_colors.scss new file mode 100644 index 000000000..0f790f450 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_colors.scss @@ -0,0 +1,79 @@ +/*------------------------------------*\ + #COLORS +\*------------------------------------*/ + +/** + * In this file, we take the literal colors from our palette (defined in variables.scss) + * and define them against variables that we can utilise anywhere throughout the project. + */ + + + + + +/*------------------------------------*\ + #GLOBAL TEXT COLOR +\*------------------------------------*/ + +/** + * Body text and background colors + */ +$color-body-text: $color-neutral-gray-93 !default; +$color-body-bg: $color-neutral-white !default; + +/** + * Highlight colors + */ +$color-text-highlight: $color-neutral-gray-93; +$color-text-highlight-bg: $color-neutral-gray-13; + + + + + +/*------------------------------------*\ + #LINKS +\*------------------------------------*/ + +$color-links: $color-neutral-gray-73 !default; +$color-links-hover: $color-brand-orange !default; +$color-links-active: $color-neutral-gray-93 !default; +$color-links-visited: $color-neutral-gray-93 !default; + + + + + +/*------------------------------------*\ + #BUTTONS +\*------------------------------------*/ + +$color-btn-primary: $color-neutral-white !default; +$color-btn-primary-bg: $color-neutral-gray-93 !default; +$color-btn-primary-bg-hover: $color-neutral-gray-50 !default; +$color-btn-primary-border: $color-neutral-gray-93 !default; + +$color-btn-secondary: $color-neutral-gray-93 !default; +$color-btn-secondary-bg: $color-neutral-white !default; +$color-btn-secondary-bg-hover: $color-neutral-gray-07 !default; +$color-btn-secondary-border: $color-neutral-gray-93 !default; + +$color-btn-disabled: $color-neutral-gray-50 !default; +$color-btn-disabled-bg: $color-neutral-gray-07 !default; + + + + + +/*------------------------------------*\ + #FORMS +\*------------------------------------*/ + +$color-form: $color-neutral-gray-93 !default; +$color-form-bg: $color-neutral-white !default; +$color-form-border: $color-neutral-gray-73 !default; +$color-form-border-focus: $color-neutral-gray-93 !default; +$color-form-border-error: $color-utility-error !default; +$color-form-label: $color-neutral-gray-93 !default; +$color-form-info: $color-neutral-gray-73; +$color-form-placeholder: $color-neutral-gray-50; diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_mixins.scss b/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_mixins.scss new file mode 100644 index 000000000..78d168f10 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_mixins.scss @@ -0,0 +1,70 @@ +/*------------------------------------*\ + #MIXINS +\*------------------------------------*/ + +/** + * Body Styles + * 1) Prevent Mobile Safari from scaling up text: https://blog.55minutes.com/2012/04/iphone-text-resizing/ + */ +@mixin typographyBody() { + font-size: $font-size-med; + line-height: 1.6; + -webkit-text-size-adjust: 100%; /* 1 */ +} + +/** + * XL Type Styles + */ + @mixin typographyBodyLarge() { + font-size: $font-size-med-2; + line-height: $line-height-large; +} + +/** + * XL Heading Styles + */ +@mixin typographyHeadingXXL() { + font-size: $font-size-xl; + font-weight: normal; + line-height: 1.2; + + @media all and (min-width: $bp-med) { + font-size: $font-size-xxl; + } +} + +/** + * XL Heading Styles + */ +@mixin typographyHeadingXl() { + font-size: $font-size-xl; + font-weight: normal; + line-height: 1.2; +} + +/** + * Large Heading Styles + */ + @mixin typographyHeadingLarge() { + font-size: $font-size-large; + font-weight: normal; + line-height: 1.2; +} + +/** + * Medium 2 Heading Styles + */ + @mixin typographyHeadingMed2() { + font-size: $font-size-med-2; + font-weight: normal; + line-height: 1.2; +} + +/** + * Medium Heading Styles + */ + @mixin typographyHeadingMed() { + font-size: $font-size-med; + font-weight: normal; + line-height: 1.2; +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_typography.scss b/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_typography.scss new file mode 100644 index 000000000..028b0df80 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_typography.scss @@ -0,0 +1,10 @@ +/*------------------------------------*\ + #TYPOGRAPHY +\*------------------------------------*/ + +/** + * In this file, we take the literal font sizes from our scale and define them + * against variables that we can utilise anywhere throughout the project. + */ + + $body-font-size: $font-size-med; diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_variables.scss b/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_variables.scss new file mode 100644 index 000000000..ed2f7e355 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/abstracts/_variables.scss @@ -0,0 +1,198 @@ +/*------------------------------------*\ + #VARIABLES +\*------------------------------------*/ + +/** + * CONTENTS + * + * COLORS + * Brand Colors...............Globally-available variables and config + * Neutral Colors.............Grayscale colors, including white and black + * Utility Colors.............Info, Warning, Error, Success + * + * TYPOGRAPHY + * Font Families..............The fonts used in the design system + * Sizing.....................Font sizing + * + * LAYOUT + * Max-widths.................Maximum layout container width + * + + * SPACING + * Spacing defaults...........Spacing between elements + * + * BORDERS + * Border Width...............Border thicknesses + * Border Radius..............Border radius definitions + * + * ANIMATION + * Animation Speed............Transition/animation speed variables + * Animation easing...........Easing variables + * + * BREAKPOINTS + * Breakpoints................Global breakpoint definitions + */ + + /*------------------------------------*\ + #COLORS + \*------------------------------------*/ + +/** + * Brand Colors + * 1) Brand=specific colors + */ +$color-brand-blue: #114689; +$color-brand-blue-dark: #041544; +$color-brand-orange: #ba6333; + +/** + * Neutral Colors + * 1) Neutral colors are grayscale values used throughout the UI + */ +$color-neutral-white: #fff; +$color-neutral-gray-02: #f9f9f9; +$color-neutral-gray-07: #eee; +$color-neutral-gray-13: #ddd; +$color-neutral-gray-27: #bbb; +$color-neutral-gray-50: #808080; +$color-neutral-gray-60: #666; +$color-neutral-gray-73: #444; +$color-neutral-gray-93: #131313; +$color-neutral-black: #000; + +/** + * Utility Colors + * 1) Utility colors are colors used to provide feedback, such as alert messages, + * form validation, etc. + */ +$color-utility-info: #0192d0; +$color-utility-info-light: #d3f2ff; +$color-utility-error: #b12a0b; +$color-utility-error-light: #fdded8; +$color-utility-success: #03804d; +$color-utility-success-light: #d4f3e6; +$color-utility-warning: #a59b15; +$color-utility-warning-light: #fffecf; + + + + + +/*------------------------------------*\ + #TYPOGRAPHY +\*------------------------------------*/ + +/** + * Font Family + */ +$font-family-primary: 'Raleway', "HelveticaNeue", "Helvetica", "Arial", sans-serif; +$font-family-secondary: 'Abel', serif; + + +/** + * Font Sizing + */ +$font-size-sm: 0.75rem; +$font-size-sm-2: 0.85rem; +$font-size-med: 1rem; +$font-size-med-2: 1.2rem; +$font-size-large: 2rem; +$font-size-xl: 3rem; +$font-size-xxl: 4rem; + +/** + * Line Height + */ +$line-height-sm: 0.8; +$line-height-sm-2: 0.9; +$line-height-med: 1; +$line-height-med-2: 1.2; +$line-height-large: 1.6; +$line-height-xl: 1.8; + + + + + +/*------------------------------------*\ + #LAYOUT +\*------------------------------------*/ + +/** + * Max Width + */ +$l-max-width: 80rem !default; +$l-max-width-narrow: 45rem !default; +$l-linelength-width: 36rem !default; + + + + + +/*------------------------------------*\ + #SPACING +\*------------------------------------*/ + +/** + * Spacing and offsets + * 1) Used to space grids and body padding + */ + +$spacing: 1rem; +$spacing-small: round(0.5 * $spacing); +$spacing-large: round(2 * $spacing); + + + + + +/*------------------------------------*\ + #BORDERS +\*------------------------------------*/ + +/** + * Border + */ + $border-thickness: 1px; + +/** + * Border radius + */ +$border-radius: 4px; + + + + + +/*------------------------------------*\ + #ANIMATION +\*------------------------------------*/ + +/** + * Transition Speed + */ +$anim-fade-quick: 0.15s; +$anim-fade-long: 0.5s; + +/** + * Transition Ease + */ +$anim-ease: ease-out; + + + + + +/*------------------------------------*\ + #BREAKPOINTS +\*------------------------------------*/ + +/** + * Breakpoints used in media queries + * 1) These are not the only breakpoints used, but they provide a few defaults + */ +$bp-small: 28em; +$bp-small-2: 35em; +$bp-med: 47em; +$bp-large: 60em; +$bp-xl: 70em; diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_body.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_body.scss new file mode 100644 index 000000000..f215c85b2 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_body.scss @@ -0,0 +1,25 @@ +/*------------------------------------*\ + #BODY +\*------------------------------------*/ + +/** + * HTML base styles + * 1) Set the html element's height to at least 100% of the viewport. + * This is used to achieve a sticky footer + */ +html { + min-height: 100vh; /* 1 */ +} + +/** + * Body base styles + * 1) Set the body element's height to at least 100% of the viewport. + * This is used to achieve a sticky footer + */ +body { + min-height: 100vh; /* 1 */ + font-family: $font-family-primary; + @include typographyBody(); + background-color: $color-body-bg; + color: $color-body-text; +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_buttons.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_buttons.scss new file mode 100644 index 000000000..d3019f197 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_buttons.scss @@ -0,0 +1,11 @@ +/*------------------------------------*\ + #BUTTONS +\*------------------------------------*/ + +/** + * Button and submit inputs reset + * 1) These should be styled using c-btn + */ +button { + cursor: pointer; +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_forms.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_forms.scss new file mode 100644 index 000000000..7e1d1a154 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_forms.scss @@ -0,0 +1,128 @@ +/*------------------------------------*\ + #FORMS +\*------------------------------------*/ + +/** + * 1) Form element base styles + */ + +/** + * Input placeholder text base styles + */ +::-webkit-input-placeholder { + color: $color-form-placeholder; +} + +::-moz-placeholder { + color: $color-form-placeholder; +} + +:-ms-input-placeholder { + color: $color-form-placeholder; +} + +/** + * Fieldset base styles + */ +fieldset { + border: 0; + padding: 0; + margin: 0; +} + +/** + * Legend base styles + */ +legend { + margin-bottom: 0.25rem; +} + +/** + * Label base styles + */ +label { + display: block; + padding-bottom: 0.25rem; + color: $color-form-label; +} + +/** + * Add font size 100% of form element and margin 0 to these elements + */ +button, input, select, textarea { + font-family: inherit; + font-size: $font-size-med; + margin: 0; +} + +/** + * Text area base styles + */ +textarea { + resize: none; +} + +/** + * Input and text area base styles + */ +input, textarea { + width: 100%; + padding: 0.5rem; + border: $border-thickness solid $color-form-border; + background: $color-form-bg; + + &:focus { + border-color: $color-form-border-focus; + } + + &:disabled { + border-color: $color-neutral-gray-07; + background: $color-neutral-gray-07; + color: $color-neutral-gray-50; + } +} + +/** + * Remove webkit appearance styles from these elements + */ +input[type=text], input[type=search], input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration, input[type=url], input[type=number], textarea { + -webkit-appearance: none; +} + +/** + * Checkbox and radio button base styles + */ +input[type="checkbox"], +input[type="radio"] { + width: auto; + margin-right: 0.3rem; + border-color: $color-form-border; +} + +/** + * Search input base styles + */ +input[type="search"] { + -webkit-appearance: none; + border-radius: 0; +} + +/** + * Select + * 1) Remove default styling + */ +select { + display: block; + font-size: $font-size-med; + width: 100%; + border: $border-thickness solid $color-form-border; + border-radius: 0; + padding: 0.5rem; + height: 2rem; + background: $color-form-bg; + color: $color-form; + + &:focus { + border-color: $color-form-border-focus; + } +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_headings.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_headings.scss new file mode 100644 index 000000000..af247fac2 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_headings.scss @@ -0,0 +1,36 @@ +/*------------------------------------*\ + #HEADINGS +\*------------------------------------*/ + +h1, h2, h3, h4 { + font-family: $font-family-secondary; + text-transform: uppercase; +} + +/** + * Heading 1 base styles + */ +h1 { + @include typographyHeadingXXL(); +} + +/** + * Heading 2 base styles + */ +h2 { + @include typographyHeadingMed2(); +} + +/** + * Heading 3 base styles + */ +h3 { + @include typographyHeadingMed2(); +} + +/** + * Heading 4 base styles + */ +h4 { + @include typographyHeadingMed(); +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_links.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_links.scss new file mode 100644 index 000000000..b0f287694 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_links.scss @@ -0,0 +1,21 @@ +/*------------------------------------*\ + #LINKS +\*------------------------------------*/ + +/** + * Link base styles + */ +a { + color: $color-links; + text-decoration: none; + outline: 0; + transition: color $anim-fade-quick $anim-ease; + + &:hover, &:focus { + color: $color-links-hover; + } + + &:active { + color: $color-links-active; + } +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_lists.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_lists.scss new file mode 100644 index 000000000..597596c90 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_lists.scss @@ -0,0 +1,14 @@ +/*------------------------------------*\ + #LISTS +\*------------------------------------*/ + +/** + * 1) List base styles + */ + + /** + * Remove list styles from unordered and ordered lists + */ +ol, ul { + list-style: none; +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_main.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_main.scss new file mode 100644 index 000000000..3f6da2cb8 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_main.scss @@ -0,0 +1,11 @@ +/*------------------------------------*\ + #MAIN ELEMENT +\*------------------------------------*/ + +/** + * Main element + */ +[role=main] { + display: block; + flex: 1 0 auto; +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_media.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_media.scss new file mode 100644 index 000000000..45fc33c5c --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_media.scss @@ -0,0 +1,12 @@ +/*------------------------------------*\ + #MEDIA +\*------------------------------------*/ + +/** + * Responsive image styling + * 1) Allows for images to flex with varying screen size + */ +img { + max-width: 100%; + height: auto; +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_reset.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_reset.scss new file mode 100644 index 000000000..82c641215 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_reset.scss @@ -0,0 +1,25 @@ +/*------------------------------------*\ + #RESET +\*------------------------------------*/ + +/** + * Border-Box http:/paulirish.com/2012/box-sizing-border-box-ftw/ + */ +* { + box-sizing: border-box; +} + +/** + * 1) Zero out margins and padding for elements + */ +html, body, div, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, ol, ul, li, form, legend, label, table, header, footer, nav, section, figure { + margin: 0; + padding: 0; +} + +/** + * 1) Set HTML5 elements to display: block + */ +header, footer, nav, section, article, figure { + display: block; +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_table.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_table.scss new file mode 100644 index 000000000..fa3215c54 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_table.scss @@ -0,0 +1,26 @@ +/*------------------------------------*\ + #TABLES +\*------------------------------------*/ + +/** + * Table + */ +table { + border-collapse: collapse; + border-spacing: 0; + width: 100%; +} + +/** + * Table header cell + */ +th { + text-align: left; +} + +/** + * Table row + */ +tr { + vertical-align: top; +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/base/_text.scss b/packages/starterkit-handlebars-demo/dist/css/scss/base/_text.scss new file mode 100644 index 000000000..b5c3c5525 --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/base/_text.scss @@ -0,0 +1,83 @@ +/*------------------------------------*\ + #TEXT +\*------------------------------------*/ + +/** + * Paragraph base styles + */ +p { + margin-bottom: $spacing; +} + +/** + * Blockquote base styles + */ +blockquote { + font-style:italic; + border-left: 1px solid $color-neutral-gray-50; + color: $color-neutral-gray-50; + padding-left: 1rem; + margin-bottom: $spacing; +} + +/** + * Horizontal rule base styles + */ +hr { + border: 0; + height: 1px; + background: $color-neutral-gray-13; + margin: 1rem 0; +} + +/** + * Selection styles + */ +::-moz-selection { + color: $color-text-highlight; + background: $color-text-highlight-bg; /* Gecko Browsers */ +} + +::selection { + color: $color-text-highlight; + background: $color-text-highlight-bg; /* WebKit/Blink Browsers */ +} + +/** + * Code base styles + */ +code { + display: inline-block; + background: $color-neutral-gray-02; + border: 1px solid $color-neutral-gray-13; + padding: .2rem .5rem; + line-height: 1.2; + font-size: .85rem; +} + +/** + * Preformatted text base styles + */ +pre { + background: $color-neutral-gray-02; + border: 1px solid $color-neutral-gray-13; + font-size: $font-size-med; + padding: 1rem; + overflow-x: auto; + + /** + * Remove border from code within preformatted text block + */ + code { + border: 0; + } +} + +/** + * Code with languages associated with them + * 1) Override Prism sysles for code blocks with language + */ +code[class*="language-"], +pre[class*="language-"] { + font-family:monospace !important; +} diff --git a/packages/starterkit-handlebars-demo/dist/css/scss/components/_buttons.scss b/packages/starterkit-handlebars-demo/dist/css/scss/components/_buttons.scss new file mode 100644 index 000000000..517fbb9ae --- /dev/null +++ b/packages/starterkit-handlebars-demo/dist/css/scss/components/_buttons.scss @@ -0,0 +1,59 @@ +/*------------------------------------*\ + #BUTTONS +\*------------------------------------*/ + +/** + * + * 1) Button or link that has functionality to it + */ +.c-btn { + display: inline-block; + border: $border-thickness solid $color-btn-primary-border; + background: $color-brand-blue; + color: $color-neutral-white; + line-height: 1; + padding: 1rem 2rem; + border: 0; + text-transform: uppercase; + text-align: center; + transition: all $anim-fade-quick $anim-ease; + + &:hover, &:focus { + color: $color-neutral-white; + background: $color-brand-blue-dark; + } +} + +.c-btn--bare { + background: none; + color: $color-brand-blue; + border: 1px solid transparent; + + &:hover, &:focus { + background: none; + color: $color-brand-blue; + border-color: $color-brand-blue; + } +} + +/* + * Inner container wrapper + * 1) Container is direct decendant of + + \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/04-forms/01-comment-form.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/04-forms/01-comment-form.twig new file mode 100755 index 000000000..e4360727b --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/04-forms/01-comment-form.twig @@ -0,0 +1,20 @@ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
\ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/04-forms/02-newsletter.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/04-forms/02-newsletter.twig new file mode 100755 index 000000000..041e2319e --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/04-forms/02-newsletter.twig @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/00-primary-nav.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/00-primary-nav.twig new file mode 100755 index 000000000..84942ffb7 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/00-primary-nav.twig @@ -0,0 +1,8 @@ + diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/01-footer-nav.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/01-footer-nav.twig new file mode 100755 index 000000000..d46a36b0c --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/01-footer-nav.twig @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/02-breadcrumbs.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/02-breadcrumbs.twig new file mode 100755 index 000000000..3b5c15351 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/02-breadcrumbs.twig @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/03-pagination.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/03-pagination.twig new file mode 100755 index 000000000..8b6de64a3 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/03-pagination.twig @@ -0,0 +1,9 @@ +
    +
  1. 1
  2. +
  3. 2
  4. +
  5. 3
  6. +
  7. 4
  8. +
  9. 5
  10. +
  11. 6
  12. +
  13. 7
  14. +
\ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/04-tabs.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/04-tabs.twig new file mode 100755 index 000000000..8e4ec10e2 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/05-navigation/04-tabs.twig @@ -0,0 +1,7 @@ +
+ +
\ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/06-components/00-social-share.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/06-components/00-social-share.twig new file mode 100755 index 000000000..120527d78 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/06-components/00-social-share.twig @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/06-components/01-accordion.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/06-components/01-accordion.twig new file mode 100755 index 000000000..253962dcb --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/06-components/01-accordion.twig @@ -0,0 +1,19 @@ +
+ +
\ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/06-components/02-single-comment.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/06-components/02-single-comment.twig new file mode 100755 index 000000000..4566255a8 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/06-components/02-single-comment.twig @@ -0,0 +1,9 @@ +
  • +
    + {% include "@atoms/04-images/04-avatar.twig" %} +

    {{ name.first }} {{ name.last }}

    +
    +
    +

    {{ description }}

    +
    +
  • diff --git a/packages/starterkit-twig-demo/dist/_patterns/01-molecules/07-messaging/00-alert.twig b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/07-messaging/00-alert.twig new file mode 100755 index 000000000..971484885 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/01-molecules/07-messaging/00-alert.twig @@ -0,0 +1,3 @@ +
    + {{ excerpt.short }} +
    \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/02-organisms/00-global/00-header.twig b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/00-global/00-header.twig new file mode 100755 index 000000000..706bd8d59 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/00-global/00-header.twig @@ -0,0 +1,9 @@ + + + diff --git a/packages/starterkit-twig-demo/dist/_patterns/02-organisms/00-global/01-footer.twig b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/00-global/01-footer.twig new file mode 100755 index 000000000..63f6fa5ef --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/00-global/01-footer.twig @@ -0,0 +1,7 @@ + +
    +
    + +
    +
    + \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/02-organisms/01-article/00-article-body.twig b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/01-article/00-article-body.twig new file mode 100755 index 000000000..218bf5c2c --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/01-article/00-article-body.twig @@ -0,0 +1,22 @@ +

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer fringilla sem a urna porttitor fringilla. Nulla eget justo felis.

    + +

    Aliquam erat volutpat. Mauris vulputate scelerisque feugiat. Cras a erat a diam venenatis aliquam. Sed tempus, purus ac pretium varius, risus orci sagittis purus, quis auctor libero magna nec magna. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Maecenas eros dolor, rutrum eu sollicitudin eu, commodo at leo. Suspendisse potenti. Sed eu nibh sit amet quam auctor feugiat vel et risus. Maecenas eu urna adipiscing neque dictum mollis suscipit in augue. Praesent pulvinar condimentum sagittis. Maecenas laoreet neque non eros consectetur fringilla. Donec vitae risus leo, vitae pharetra ipsum. Sed placerat eros eget elit iaculis semper. Aliquam congue blandit orci ac pretium.

    + +{% include "@atoms/04-images/02-landscape-16x9.twig" %} + +

    Aliquam ultrices cursus mauris, eget volutpat justo mattis nec. Sed a orci turpis. Aliquam aliquet placerat dui, consectetur tincidunt leo tristique et. Vivamus enim nisi, blandit a venenatis quis, convallis et arcu. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris libero sapien, placerat in sodales eu, tempor quis dui. Vivamus egestas faucibus pulvinar. Maecenas eget diam nunc. Phasellus at sem eros, ac suscipit neque. Phasellus sollicitudin libero a odio dignissim scelerisque. Aliquam purus nulla, tempor eget ullamcorper quis, rhoncus non dui. +

    + +{% include "@atoms/02-text/02-blockquote.twig" %} + +

    Cras at fringilla ipsum. Donec nec libero eget est blandit dignissim a eu ante. Morbi augue nulla, luctus eu sagittis vel, malesuada ut felis. Aliquam erat volutpat. Morbi malesuada augue ac massa hendrerit fermentum. Integer scelerisque lacus a dolor convallis lobortis. Curabitur mollis ante in massa ultricies dignissim. +

    + +{% include "@atoms/03-lists/00-unordered.twig" %} + +{% include "@atoms/03-lists/01-ordered.twig" %} + +

    Donec posuere fringilla nunc, vitae venenatis diam scelerisque vel. Nullam vitae mauris magna. Mauris et diam quis justo volutpat tincidunt congue nec magna. Curabitur vitae orci elit. Ut mollis massa id magna vestibulum consequat. Proin rutrum lectus justo, sit amet tincidunt est. Vivamus vitae lacinia risus. +

    + +

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Reiciendis, suscipit repellendus nulla accusantium deserunt sed explicabo voluptate sapiente ratione inventore molestiae nihil earum repellat quia odit vitae perspiciatis aliquam amet?

    diff --git a/packages/starterkit-twig-demo/dist/_patterns/02-organisms/02-comments/00-comment-thread.twig b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/02-comments/00-comment-thread.twig new file mode 100755 index 000000000..f6d5a8c60 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/02-comments/00-comment-thread.twig @@ -0,0 +1,12 @@ +
    +
    +

    59 Comments

    + {% include "@molecules/04-forms/01-comment-form.twig" %} +
      + {% for item in 1..5 %} + {% include "@molecules/06-components/02-single-comment.twig" %} + {% endfor %} +
    +
    + {% include "@molecules/05-navigation/03-pagination.twig" %} +
    diff --git a/packages/starterkit-twig-demo/dist/_patterns/02-organisms/03-sections/00-latest-posts.twig b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/03-sections/00-latest-posts.twig new file mode 100755 index 000000000..c85c91804 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/03-sections/00-latest-posts.twig @@ -0,0 +1,9 @@ +
    +

    Latest Posts

    +
      + {% for post in latest_posts %} +
    • {% include "@molecules/02-blocks/00-media-block.twig" %}
    • + {% endfor %} +
    + View more posts +
    diff --git a/packages/starterkit-twig-demo/dist/_patterns/02-organisms/03-sections/01-recent-tweets.twig b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/03-sections/01-recent-tweets.twig new file mode 100755 index 000000000..d2d655305 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/03-sections/01-recent-tweets.twig @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/02-organisms/03-sections/02-related-posts.twig b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/03-sections/02-related-posts.twig new file mode 100755 index 000000000..b4a75cbed --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/02-organisms/03-sections/02-related-posts.twig @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/03-templates/00-layouts/00-site.twig b/packages/starterkit-twig-demo/dist/_patterns/03-templates/00-layouts/00-site.twig new file mode 100755 index 000000000..18d596f1c --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/03-templates/00-layouts/00-site.twig @@ -0,0 +1,9 @@ +
    + {% include "@organisms/00-global/00-header.twig" %} +
    + {% block hero %}{% endblock %} + {% block title %}{% endblock %} + {% block content %}{% endblock %} +
    + {% include "@organisms/00-global/01-footer.twig" %} +
    diff --git a/packages/starterkit-twig-demo/dist/_patterns/03-templates/00-layouts/01-page-1col.twig b/packages/starterkit-twig-demo/dist/_patterns/03-templates/00-layouts/01-page-1col.twig new file mode 100755 index 000000000..ad48b2f37 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/03-templates/00-layouts/01-page-1col.twig @@ -0,0 +1,10 @@ +{% extends "@templates/00-layouts/00-site.twig" %} +{% block content %} +
    +
    + {% block main %} +

    Content Placeholder

    + {% endblock %} +
    +
    +{% endblock %} diff --git a/packages/starterkit-twig-demo/dist/_patterns/03-templates/00-layouts/01-page-2col.twig b/packages/starterkit-twig-demo/dist/_patterns/03-templates/00-layouts/01-page-2col.twig new file mode 100755 index 000000000..802647d7a --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/03-templates/00-layouts/01-page-2col.twig @@ -0,0 +1,15 @@ +{% extends "@templates/00-layouts/00-site.twig" %} +{% block content %} +
    +
    + {% block main %} +

    Content Placeholder

    + {% endblock %} +
    + +
    +{% endblock %} diff --git a/packages/starterkit-twig-demo/dist/_patterns/03-templates/01-homepage.twig b/packages/starterkit-twig-demo/dist/_patterns/03-templates/01-homepage.twig new file mode 100755 index 000000000..bf96fb39f --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/03-templates/01-homepage.twig @@ -0,0 +1,35 @@ +{% extends "@templates/00-layouts/01-page-2col.twig" %} +{% block hero %} + {% if emergency %} + {% include "@molecules/07-messaging/00-alert.twig" with emergency %} + {% endif %} + {% if hero %} + {% include "@molecules/02-blocks/02-block-hero.twig" %} + {% endif %} + +
    + {% for tout in touts %} +
    + {% include "@molecules/02-blocks/05-block-inset.twig" %} +
    + {% endfor %} +
    + +
    +{% endblock %} + +{% block main %} +
    +

    Latest Posts

    +
      + {% for post in latest_posts %} +
    • {% include "@molecules/02-blocks/00-media-block.twig" %}
    • + {% endfor %} +
    + View more posts +
    +{% endblock %} + +{% block sidebar %} + {% include "@organisms/03-sections/01-recent-tweets.twig" %} +{% endblock %} diff --git a/packages/starterkit-twig-demo/dist/_patterns/03-templates/02-blog.twig b/packages/starterkit-twig-demo/dist/_patterns/03-templates/02-blog.twig new file mode 100755 index 000000000..6690bc73e --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/03-templates/02-blog.twig @@ -0,0 +1,11 @@ +{% extends "@templates/00-layouts/00-site.twig" %} +{% block title %}

    Our Outdoor Blog

    {% endblock %} + +{% block main %} + {% include "@organisms/03-sections/00-latest-posts.twig" %} + {% include "@molecules/05-navigation/03-pagination.twig" %} +{% endblock %} + +{% block sidebar %} + {% include "@organisms/03-sections/01-recent-tweets.twig" %} +{% endblock %} diff --git a/packages/starterkit-twig-demo/dist/_patterns/03-templates/03-article-2col.twig b/packages/starterkit-twig-demo/dist/_patterns/03-templates/03-article-2col.twig new file mode 100755 index 000000000..825ca9471 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/03-templates/03-article-2col.twig @@ -0,0 +1,17 @@ +{% extends "@templates/00-layouts/01-page-2col.twig" %} +{% block main %} +
    +
    +

    Article Headline Lorem ipsum dolor sit aweofij

    + {% include "@molecules/00-text/00-byline.twig" %} +
    + {% include "@organisms/01-article/00-article-body.twig" %} +
    + {% include "@molecules/06-components/00-social-share.twig" %} + {% include "@organisms/02-comments/00-comment-thread.twig" %} +{% endblock %} + +{% block sidebar %} + {% include "@organisms/03-sections/02-related-posts.twig" %} + {% include "@organisms/03-sections/01-recent-tweets.twig" %} +{% endblock %} diff --git a/packages/starterkit-twig-demo/dist/_patterns/03-templates/03-article.twig b/packages/starterkit-twig-demo/dist/_patterns/03-templates/03-article.twig new file mode 100755 index 000000000..0b960e162 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/03-templates/03-article.twig @@ -0,0 +1,12 @@ +{% extends "@templates/00-layouts/01-page-1col.twig" %} +{% block main %} +
    +
    +

    Article Headline Lorem ipsum dolor sit aweofij

    + {% include "@molecules/00-text/00-byline.twig" %} +
    + {% include "@organisms/01-article/00-article-body.twig" %} +
    + {% include "@molecules/06-components/00-social-share.twig" %} + {% include "@organisms/02-comments/00-comment-thread.twig" %} +{% endblock %} diff --git a/packages/starterkit-twig-demo/dist/_patterns/04-pages/00-homepage.json b/packages/starterkit-twig-demo/dist/_patterns/04-pages/00-homepage.json new file mode 100755 index 000000000..8a879bd07 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/04-pages/00-homepage.json @@ -0,0 +1,135 @@ +{ + "title" : "Home Page", + "bodyClass": "home", + "emergency" : false, + "hero" : [ + { + "img": { + "landscape_16x9": { + "src": "../../images/sample/landscape-16x9-mountains.jpg", + "alt": "Mountains" + } + }, + "headline" : { + "medium" : "Top 10 mountain ranges for hiking" + } + } + ], + "touts" : [ + { + "img": { + "landscape_4x3": { + "src": "../../images/sample/tout-4x3-climber.jpg", + "alt": "Climber" + } + }, + "headline" : { + "short" : "Extreme climbing tips" + } + }, + { + "img": { + "landscape_4x3": { + "src": "../../images/sample/tout-4x3-stream.jpg", + "alt": "Stream hiking" + } + }, + "headline" : { + "short" : "Stream hiking" + } + }, + { + "img": { + "landscape_4x3": { + "src": "../../images/sample/tout-4x3-climbers.jpg", + "alt": "Explore hiking trails" + } + }, + "headline" : { + "short" : "Explore hiking trails" + } + } + ], + "latest-posts" : [ + { + "img": { + "square": { + "src": "../../images/sample/thumb-square-river.jpg", + "alt": "Post Thumbnail" + } + }, + "headline" : { + "short" : "Navigate the Allegheny River" + }, + "excerpt" : { + "medium" : "The Allegheny River is a principal tributary of the Ohio River; it is located in the Eastern United States. The Allegheny River joins with the Monongahela River to form the Ohio River at the Point of Point State Park in Downtown Pittsburgh, Pennsylvania." + } + }, + { + "img": { + "square": { + "src": "../../images/sample/thumb-square-ivy.jpg", + "alt": "Ivy" + } + }, + "headline" : { + "short" : "How to detect and avoid poison ivy" + }, + "excerpt" : { + "medium" : "Toxicodendron radicans, commonly known as poison ivy is a poisonous North American plant that is well known for its production of urushiol" + } + }, + { + "img": { + "square": { + "src": "../../images/sample/thumb-square-yosemite.jpg", + "alt": "Yosemite" + } + }, + "headline" : { + "short" : "Top 10 hiking mountains" + }, + "excerpt" : { + "medium" : "Yosemite National Park is a United States National Park spanning eastern portions of Tuolumne, Mariposa and Madera counties in the central eastern portion of the U.S. state of California." + } + }, + { + "img": { + "square": { + "src": "../../images/sample/thumb-square-fire.jpg", + "alt": "Fire" + } + }, + "headline" : { + "short" : "How to build a campfire" + }, + "excerpt" : { + "medium" : "A campfire is a fire lit at a campsite, to serve the following functions: light, warmth, a beacon, an insect and/or apex predator deterrent, to cook, and for a psychological sense of security. " + } + }, + { + "img": { + "square": { + "src": "../../images/sample/thumb-square-gear.jpg", + "alt": "Camping Gear" + } + }, + "headline" : { + "short" : "Pick the right camping gear" + }, + "excerpt" : { + "medium" : "The equipment used in camping varies with the particular type of camping. For instance, in survival camping the equipment consists of small items which have the purpose of helping the camper in providing food, heat and safety." + } + } + ], + "inset-blocks" : [ + { + "headline" : "This is the headline for the inset block" + } + ], + "latest-block" : [ + { + "headline" : "This is the headline for the latest block" + } + ] +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/04-pages/00-homepage.twig b/packages/starterkit-twig-demo/dist/_patterns/04-pages/00-homepage.twig new file mode 100755 index 000000000..41c4fe12a --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/04-pages/00-homepage.twig @@ -0,0 +1 @@ +{% include "@templates/01-homepage.twig" %} diff --git a/packages/starterkit-twig-demo/dist/_patterns/04-pages/00-homepage~emergency.json b/packages/starterkit-twig-demo/dist/_patterns/04-pages/00-homepage~emergency.json new file mode 100755 index 000000000..14255064f --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/04-pages/00-homepage~emergency.json @@ -0,0 +1,8 @@ +{ + "emergency": { + "styleModifier" : "error", + "excerpt" : { + "short" : "Emergency! This is a variation of the core homepage template." + } + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/04-pages/01-blog.json b/packages/starterkit-twig-demo/dist/_patterns/04-pages/01-blog.json new file mode 100755 index 000000000..c5067e3e7 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/04-pages/01-blog.json @@ -0,0 +1,86 @@ +{ + "title" : "Blog", + "bodyClass": "blog", + "latest-posts" : [ + { + "img": { + "square": { + "src": "../../images/sample/thumb-square-river.jpg", + "alt": "Post Thumbnail" + } + }, + "headline" : { + "short" : "Navigate the Allegheny River" + }, + "excerpt" : { + "medium" : "The Allegheny River is a principal tributary of the Ohio River; it is located in the Eastern United States. The Allegheny River joins with the Monongahela River to form the Ohio River at the Point of Point State Park in Downtown Pittsburgh, Pennsylvania." + } + }, + { + "img": { + "square": { + "src": "../../images/sample/thumb-square-ivy.jpg", + "alt": "Ivy" + } + }, + "headline" : { + "short" : "How to detect and avoid poison ivy" + }, + "excerpt" : { + "medium" : "Toxicodendron radicans, commonly known as poison ivy is a poisonous North American plant that is well known for its production of urushiol" + } + }, + { + "img": { + "square": { + "src": "../../images/sample/thumb-square-yosemite.jpg", + "alt": "Yosemite" + } + }, + "headline" : { + "short" : "Top 10 hiking mountains" + }, + "excerpt" : { + "medium" : "Yosemite National Park is a United States National Park spanning eastern portions of Tuolumne, Mariposa and Madera counties in the central eastern portion of the U.S. state of California." + } + }, + { + "img": { + "square": { + "src": "../../images/sample/thumb-square-fire.jpg", + "alt": "Fire" + } + }, + "headline" : { + "short" : "How to build a campfire" + }, + "excerpt" : { + "medium" : "A campfire is a fire lit at a campsite, to serve the following functions: light, warmth, a beacon, an insect and/or apex predator deterrent, to cook, and for a psychological sense of security. " + } + }, + { + "img": { + "square": { + "src": "../../images/sample/thumb-square-gear.jpg", + "alt": "Camping Gear" + } + }, + "headline" : { + "short" : "Pick the right camping gear" + }, + "excerpt" : { + "medium" : "The equipment used in camping varies with the particular type of camping. For instance, in survival camping the equipment consists of small items which have the purpose of helping the camper in providing food, heat and safety." + } + } + ], + "inset_blocks" : [ + { + "headline" : "This is the headline for the inset block" + } + ], + "latest_block" : [ + { + "headline" : "This is the headline for the latest block" + } + ] +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/04-pages/01-blog.twig b/packages/starterkit-twig-demo/dist/_patterns/04-pages/01-blog.twig new file mode 100755 index 000000000..89884f577 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/04-pages/01-blog.twig @@ -0,0 +1 @@ +{% include "@templates/02-blog.twig" %} diff --git a/packages/starterkit-twig-demo/dist/_patterns/04-pages/02-article.json b/packages/starterkit-twig-demo/dist/_patterns/04-pages/02-article.json new file mode 100755 index 000000000..1d8574600 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/04-pages/02-article.json @@ -0,0 +1,28 @@ +{ + "title" : "Top 10 Trails You Have To Hike Before You Die", + "bodyClass": "article", + "author" : { + "first-name" : "Julianne", + "last-name" : "McCormick" + }, + "img": { + "landscape_4x3": { + "src": "../../images/sample/landscape-16x9-mountains.jpg", + "alt": "Mountains" + }, + "landscape_16x9": { + "src": "../../images/sample/landscape-16x9-mountains.jpg", + "alt": "Mountains" + } + }, + "inset_blocks" : [ + { + "headline" : "This is the headline for the inset block" + } + ], + "latest_block" : [ + { + "headline" : "This is the headline for the latest block" + } + ] +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/_patterns/04-pages/02-article.twig b/packages/starterkit-twig-demo/dist/_patterns/04-pages/02-article.twig new file mode 100755 index 000000000..eb40c4bfd --- /dev/null +++ b/packages/starterkit-twig-demo/dist/_patterns/04-pages/02-article.twig @@ -0,0 +1 @@ +{% include "@templates/03-article-2col.twig" %} diff --git a/packages/starterkit-twig-demo/dist/css/pattern-scaffolding.css b/packages/starterkit-twig-demo/dist/css/pattern-scaffolding.css new file mode 100755 index 000000000..b09172fc8 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/pattern-scaffolding.css @@ -0,0 +1,54 @@ +/** + * This stylesheet is for styles you want to include only when displaying demo + * styles for grids, animations, color swatches, etc. + * These styles will not be your production CSS. + */ +#sg-patterns { + -webkit-box-sizing: border-box !important; + box-sizing: border-box !important; + max-width: 100%; + padding: 0 0.5em; +} + +.demo-animate { + background: #ddd; + padding: 1em; + margin-bottom: 1em; + text-align: center; + border-radius: 8px; + cursor: pointer; +} + +.sg-colors { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + list-style: none !important; + padding: 0 !important; + margin: 0 !important; +} +.sg-colors li { + -webkit-box-flex: 1; + -ms-flex: auto; + flex: auto; + padding: 0.3em; + margin: 0 0.5em 0.5em 0; + min-width: 5em; + max-width: 14em; + border: 1px solid #ddd; + border-radius: 8px; +} + +.sg-swatch { + display: block; + height: 4em; + margin-bottom: 0.3em; + border-radius: 5px; +} + +.sg-label { + font-size: 90%; + line-height: 1; +} diff --git a/packages/starterkit-twig-demo/dist/css/scss/base/_animation.scss b/packages/starterkit-twig-demo/dist/css/scss/base/_animation.scss new file mode 100755 index 000000000..d28bc9136 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/base/_animation.scss @@ -0,0 +1,13 @@ +.animate-fade { + @include transition(opacity, 0.3s, ease-out); + + &:hover { + opacity: 0; + } +} + +.animate-move { + > .demo-shape { + @include transition(all, 0.8s, ease-in-out); + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/base/_forms.scss b/packages/starterkit-twig-demo/dist/css/scss/base/_forms.scss new file mode 100755 index 000000000..1c02c6a83 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/base/_forms.scss @@ -0,0 +1,111 @@ +/*------------------------------------*\ + $FORMS +\*------------------------------------*/ +form ol, form ul { + list-style: none; + margin-left: 0; +} + +fieldset { + border: 0; + padding: 0; + margin: 0; +} + +label { + display: block; + padding-bottom: $space-half; +} + +button, input, select, textarea { + font-family: inherit; + font-size: 100%; + margin: 0 1px 0; +} + +input, textarea { + width: 100%; + border: 1px solid $gray; + padding: $pad-half 0.65rem; +} + +input[type=text], input[type=search], input[type=url], input[type=number], textarea { + -webkit-appearance: none; +} + +button, input[type="submit"] { + padding: $pad-half; + background: $gray-dark; + border: 1px solid $gray; + cursor: pointer; +} + +input[type="checkbox"], +input[type="radio"] { + width: auto; + margin-right: 0.3em; +} + +input[type="search"] { + -webkit-appearance: none; + border-radius: 0; +} + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +//Form Field Container +.field-container { + margin-bottom: $space; +} + +.inline-form { + fieldset, .inline-container { + position: relative; + } + + input[type=submit], button, .btn { + font-size: $font-size-small-2; + padding: 0.65rem 1.3rem; + background: $gray-dark; + position: absolute; + top: 0; + right: 0; + z-index: 1; + width: auto; + + &:hover, &:focus { + background: $gray; + color: $white; + } + } +} + +/* Validation */ +.has-error { + border-color: $error; +} +.is-valid { + border-color: $valid; +} + + + + +/*------------------------------------*\ + $SPECIFIC FORMS +\*------------------------------------*/ + +/* Search Form */ +.search-field { + padding-right: 3em; +} +.inline-form .search-submit { + background: none; + padding: 0.78em 1em; + border: 0; + border-left: 1px solid $gray; + color: $gray; +} diff --git a/packages/starterkit-twig-demo/dist/css/scss/base/_global-classes.scss b/packages/starterkit-twig-demo/dist/css/scss/base/_global-classes.scss new file mode 100755 index 000000000..2e13f8858 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/base/_global-classes.scss @@ -0,0 +1,104 @@ +/*------------------------------------*\ + $GLOBAL CLASSES +\*------------------------------------*/ + +/* Clearfix */ +.cf { + *zoom: 1; +} +.cf:before, .cf:after { + content: " "; /* 1 */ + display: table; /* 2 */ +} + +.cf:after { + clear: both; +} + +/* Completely remove from the flow and screen readers. */ +.is-hidden { + display: none !important; + visibility: hidden !important; +} + +/* Completely remove from the flow but leave available to screen readers. */ +.is-vishidden { + position: absolute !important; + overflow: hidden; + width: 1px; + height: 1px; + padding: 0; + border: 0; + clip: rect(1px, 1px, 1px, 1px); +} + +/* Floats */ +.right { + float: right; + padding: 0 0 $pad $pad; +} + +.right-search { + float: right; + padding: 0 0 $pad 0; +} + +.left { + float: left; + padding: 0 $pad $pad 0; +} + +/* Text-Align */ +.align-right { + text-align: right; +} + +.align-center { + text-align: center; +} + +.align-left { + text-align: left; +} + +/* Display Classes */ +.hide-small { + @media all and (max-width: $bp-med) { + display: none; + } +} + +.hide-med { + @media all and (min-width: $bp-med) and (max-width: $bp-large) { + display: none; + } +} + +.hide-large { + @media all and (min-width: $bp-large) { + display: none; + } +} + +//States +.valid { + color: $valid; +} + +.error { + color: $error; +} + +.warning { + color: $warning; +} + +.information { + color: $information; +} + +.font-secondary { + font-family: $font-secondary; +} + + diff --git a/packages/starterkit-twig-demo/dist/css/scss/base/_headings.scss b/packages/starterkit-twig-demo/dist/css/scss/base/_headings.scss new file mode 100755 index 000000000..5f2433645 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/base/_headings.scss @@ -0,0 +1,31 @@ +/* Headings */ +/*Further Reading: http:/csswizardry.com/2012/02/pragmatic-practical-font-sizing-in-css/ */ +h1, .alpha { + line-height: 1.2; +} + +h2, .beta { + line-height: 1.2; +} + +h3, .gamma { + line-height: 1.2; +} + +h4, .delta { + +} + +h5, .epsilon { + +} + +h6, .zeta { + +} + +/* Subheadings */ +.subheading { + font-family: $font-secondary; + font-weight: normal; +} diff --git a/packages/starterkit-twig-demo/dist/css/scss/base/_links.scss b/packages/starterkit-twig-demo/dist/css/scss/base/_links.scss new file mode 100755 index 000000000..d70545d76 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/base/_links.scss @@ -0,0 +1,10 @@ +/* Links */ +a { + color: $gray-dark; + text-decoration: none; + outline: 0; + + &:hover, &:focus { + color: $gray; + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/base/_lists.scss b/packages/starterkit-twig-demo/dist/css/scss/base/_lists.scss new file mode 100755 index 000000000..4ceb7cf5c --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/base/_lists.scss @@ -0,0 +1,19 @@ +ol, ul { + margin: 0; + padding: 0; + list-style: none; +} + +/* Definition Lists */ +dl { + overflow: hidden; + margin: 0 0 $space; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 0; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/base/_main.scss b/packages/starterkit-twig-demo/dist/css/scss/base/_main.scss new file mode 100755 index 000000000..f92fd1b34 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/base/_main.scss @@ -0,0 +1,6 @@ +body { + background: $white; + font: 100%/1.5 $font; + -webkit-text-size-adjust: 100%; + color: $gray-dark; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/base/_media.scss b/packages/starterkit-twig-demo/dist/css/scss/base/_media.scss new file mode 100755 index 000000000..ec74c8ffc --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/base/_media.scss @@ -0,0 +1,23 @@ +/*------------------------------------*\ + $MEDIA ELEMENTS +\*------------------------------------*/ + +/* Flexible Media */ +img, video, object { + max-width: 100%; + height: auto; +} + +iframe { + margin-bottom: $space; +} + +figure { + margin-bottom: $space; + img { + margin-bottom: $space-half; + } +} +figcaption { + font-style: italic; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/base/_tables.scss b/packages/starterkit-twig-demo/dist/css/scss/base/_tables.scss new file mode 100755 index 000000000..1b2a2b2c4 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/base/_tables.scss @@ -0,0 +1,18 @@ +/*------------------------------------*\ + $Table +\*------------------------------------*/ +table { + border-collapse: collapse; + border-spacing: 0; + border: 1px solid $gray; + width: 100%; +} +th { + text-align: left; + border: 1px solid $gray; + padding: 0.2em; +} +td { + border: 1px solid $gray; + padding: 0.2em; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/base/_text.scss b/packages/starterkit-twig-demo/dist/css/scss/base/_text.scss new file mode 100755 index 000000000..de17e3ec6 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/base/_text.scss @@ -0,0 +1,26 @@ +/* Text-Related Elements */ +p { + margin-bottom: $space; +} + +/* Blockquote */ +blockquote { + font-style:italic; + border-left: 1px solid $gray; + color: $gray; + padding-left: $pad; + margin-bottom: $space; +} + +/* Horizontal Rule */ +hr { + border: 0; + height: 2px; + background: $gray-light-2; + margin: $space-double 0; +} + +abbr { + border-bottom: 1px dotted $gray; + cursor: help; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/generic/_mixins.scss b/packages/starterkit-twig-demo/dist/css/scss/generic/_mixins.scss new file mode 100755 index 000000000..14d8a2a3d --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/generic/_mixins.scss @@ -0,0 +1,23 @@ +/*------------------------------------*\ + $MIXINS +\*------------------------------------*/ + +/* CSS Transition + Usage: @include transition(width,0.3s,ease-out); + */ +@mixin transition($transition-property, $transition-time, $method) { + -webkit-transition: $transition-property $transition-time $method; + -moz-transition: $transition-property $transition-time $method; + -ms-transition: $transition-property $transition-time $method; + -o-transition: $transition-property $transition-time $method; + transition: $transition-property $transition-time $method; +} + +/* Rem Unit font sizes with relative fallback http:/seesparkbox.com/foundry/scss_rem_mixin_now_with_a_better_fallback + Usage: @include font-size(1, large); +*/ +@mixin font-size( $decimal-size, $keyword: null ) { + @if $keyword{ font-size: $keyword; } + @else { font-size: $decimal-size * $base-font-multiplier * 16px;} + font-size: $decimal-size * 1rem; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/generic/_reset.scss b/packages/starterkit-twig-demo/dist/css/scss/generic/_reset.scss new file mode 100755 index 000000000..e7807ec72 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/generic/_reset.scss @@ -0,0 +1,17 @@ +/*------------------------------------*\ + $RESET +\*------------------------------------*/ + +/* Border-Box http:/paulirish.com/2012/box-sizing-border-box-ftw/ */ +* { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} +html, body, div, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, ol, ul, li, form, legend, label, table, header, footer, nav, section, figure { + margin: 0; + padding: 0; +} +header, footer, nav, section, article, hgroup, figure { + display: block; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/generic/_variables.scss b/packages/starterkit-twig-demo/dist/css/scss/generic/_variables.scss new file mode 100755 index 000000000..3e7ccbcba --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/generic/_variables.scss @@ -0,0 +1,65 @@ +/*------------------------------------*\ + $VARIABLES +\*------------------------------------*/ + +//Colors +$gray : #808080; +$gray-light : #f9f9f9; +$gray-light-2 : #eee; +$gray-light-3 : #ddd; +$gray-med : #666; +$gray-dark : #333; +$gray-dark-2 : #131313; +$white : #fff; +$black : #000; +$dim : rgba(0,0,0,0.5); +$error : #f00; +$valid : #089e00; +$warning : #fff664; +$information : #000db5; + + +//Typography +$font : "HelveticaNeue", "Helvetica", "Arial", sans-serif; +$font-secondary : Georgia, Times, "Times New Roman", serif; + +$font-size-small : 0.75em; +$font-size-small-2 : 0.875em; +$font-size-med : 1em; +$font-size-med-2: 1.0625em; +$font-size-large : 1.4375em; + + +//Layout +$max-width: 72em; + +//Defaults +$space : 1em; +$space-and-half : $space*1.5; +$space-double : $space*2; +$space-quad : $space*4; +$space-half : $space/2; +$pad : 1em; +$pad-and-half : $pad*1.5; +$pad-double : $pad*2; +$pad-half : $pad/2; +$pad-quarter :$pad/4; + +//Borders +$border-med: 3px; +$border-thick: 7px; + + +//Breakpoints +$bp-small : 24em; +$bp-small-2 : 29.75em; +$bp-small-3 : 39.8em; +$bp-med : 46.8em; +$bp-med-2 : 48em; +$bp-large : 50em; +$bp-large-2 : 66em; +$bp-xl : 73em; +$bp-xxl : 89em; +$bp-xxxl : 93em; + +// \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_accordion.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_accordion.scss new file mode 100755 index 000000000..3423544c9 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_accordion.scss @@ -0,0 +1,33 @@ +.accordion { + margin-bottom: $space; +} + +.acc-handle { + background: $gray-dark-2; + color: $white; + font-family: $font-secondary; + font-weight: bold; + display: block; + position: relative; + padding: $pad-half; + border-bottom: 1px solid $gray-light-3; + + &:after { + content:"+"; + float: right; + } + + &:hover { + color: $white; + background: $gray-dark; + + } + + &.active { + background: $gray-dark; + + &:after { + content:"-"; + } + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_article.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_article.scss new file mode 100755 index 000000000..55387309e --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_article.scss @@ -0,0 +1,34 @@ +//Article +.article-header { + h1 { + font-size: 2.5em; + } +} + +.byline { + font-size: $font-size-small-2; + font-style: italic; + margin-bottom: $space-half; +} + +//Social Share +.social-share { + overflow: hidden; + margin-bottom: $space; + + li { + float: left; + margin-right: $space-half; + } + + a { + background: $gray-dark; + color: $white; + display: block; + padding: $pad-half; + + &:hover { + background: $gray; + } + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_blocks.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_blocks.scss new file mode 100755 index 000000000..160963bd6 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_blocks.scss @@ -0,0 +1,138 @@ +/* Generic Placeholder Brick: REMOVE FOR PRODUCTION */ +.brick { + background: #dcdddc; + padding: $pad-double; + text-align: center; + font-weight: bold; + border-bottom: 1px solid $gray-light-2; +} + +/* Block */ +.block { + overflow: hidden; + + p:last-child { + margin-bottom: 0; + } +} + +.headline { + line-height: 1.2; +} + +/* Hero Block */ +.block-hero { + margin-bottom: $space-half; + + .b-thumb { + img { + display: block; + } + } + + @media all and (min-width: $bp-large) { + position: relative; + + .b-text { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + background: $dim; + color: $white; + padding: $pad-and-half; + } + } +} + +/* Block Thumbnail with Headline */ +.block-thumb { + display: table; + width: 100%; + border-collapse: collapse; + + .b-inner { + display: table-row; + vertical-align: top; + overflow: hidden; + } + + .b-thumb { + @media all and (min-width: $bp-small-2) { + display: table-cell; + vertical-align: top; + width: 30%; + max-width: 10em; + + img { + display: block; + width: 100%; + height: auto; + } + } + } + + .b-text { + @media all and (min-width: $bp-small-2) { + display: table-cell; + width: 70%; + padding: 0 $pad; + } + } +} + + +/* Block Headline Summary */ +.block-headline-summary { + a { + display: block; + padding: $pad-half; + } +} + +/* Block Inset */ +.block-inset { + position: relative; + + .b-thumb { + position: relative; + z-index: 0; + + img { + display: block; + } + } +} + +/* Hero Block */ +.block-inset { + margin-bottom: $space-half; + position: relative; + + .headline { + font-size: 1.1em; + } + + .b-text { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + background: $dim; + color: $white; + padding: $pad-half; + } +} + +/* Block Thumb with Summary */ +.block-thumb-summary { + .b-thumb { + float: left; + width: 50%; + } + + .b-text { + margin-left: 50%; + padding: $pad-half; + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_buttons.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_buttons.scss new file mode 100755 index 000000000..0ae2805e1 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_buttons.scss @@ -0,0 +1,40 @@ +/*------------------------------------*\ + $BUTTONS +\*------------------------------------*/ +.btn { + display: inline-block; + background: $gray-dark; + color: $white; + line-height: 1; + font-weight: bold; + padding: $pad; + border: 0; + text-align: center; + + &:hover, &:focus { + background: $gray; + color: $white; + } + + &.disabled { + background: $gray-light-2; + color: $gray; + } +} + +.btn-small { + padding: $pad-half; +} + +.btn-large { + padding: $pad-half; + text-transform: uppercase; + background: $gray; + font-size: 1.4rem; + font-weight: normal; +} + + +.text-btn { + font-style: italic; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_carousels.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_carousels.scss new file mode 100755 index 000000000..77e3f197b --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_carousels.scss @@ -0,0 +1,38 @@ +/* Horizontal Carousel */ +.carousel-horizontal { + margin-bottom: $space; + overflow: hidden; + position: relative; +} + +.carousel-island-container { + overflow: hidden; +} + +.carousel-container { + position: relative; +} + +.carousel-list { + +} + +.carousel-controls { + display: table; + width: 100%; + margin: (-$space-half) 0 $space; + + a, div { + display: table-cell; + padding: $pad-half; + } + + .carousel-pagination { + text-align: center; + } + + .carousel-next { + text-align: right; + } + +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_comments.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_comments.scss new file mode 100755 index 000000000..9959b7abf --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_comments.scss @@ -0,0 +1,30 @@ +.comments { + li { + margin-bottom: $space; + } +} + +.comment-container { + overflow: hidden; + margin-bottom: $space; + list-style: none; +} + +.comment-meta { + float: left; + width: 6.5em; + + img { + display: block; + border: 1px solid $gray-light-2; + margin-bottom: $space-half; + } +} + +.comment-name { + font-size: $font-size-small; +} + +.comment-text { + margin-left: 9em; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_footer.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_footer.scss new file mode 100755 index 000000000..850f94bf4 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_footer.scss @@ -0,0 +1,39 @@ +/*------------------------------------*\ + $FOOTER +\*------------------------------------*/ +.footer { + clear: both; + overflow: hidden; + background: $gray-dark-2; + color: $white; + line-height: 1.2; + + a { + color: $gray-light; + } +} + +//Footer Nav +.nav-footer { + margin: (-$pad) (-$pad) $space; + + li { + border-bottom: 1px solid $gray-dark; + + @media all and (min-width: $bp-med) { + border: 0; + float: left; + } + } + + a { + display: block; + padding: $pad; + } +} + +.copyright { + @media all and (min-width: $bp-med) { + float: right; + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_header.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_header.scss new file mode 100755 index 000000000..8fb78d7ce --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_header.scss @@ -0,0 +1,45 @@ +.header { + background: $white; + padding: 0; + border-bottom: 1px solid #dbdbdb; + @extend .lc; +} + +.logo { + float: left; + max-width: 8rem; + margin: 0.4rem; + + @media all and (min-width: $bp-med) { + max-width: 9rem; + } +} + +.nav-toggle { + float: right; + display: block; + padding: 0.9rem 1rem 0.7rem; + font-size: 1.3rem; + line-height: 1; + border-left: 1px solid #dbdbdb; + + @media all and (min-width: $bp-med) { + display: none; + } + + @media all and (max-width: 17em) { + padding-left: 0.2rem; + padding-right: 0.2rem; + } +} + +.search-form { + overflow: hidden; + max-height: 0; + + @media all and (min-width: $bp-med) { + float: right; + max-height: none; + margin: 0.65em 0.5em 0 0; + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_icons.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_icons.scss new file mode 100755 index 000000000..cacbf1fbd --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_icons.scss @@ -0,0 +1,125 @@ + +/* Icon Font */ +@font-face { + font-family: 'icons'; + src:url('../fonts/icons.eot'); + src:url('../fonts/icons.eot?#iefix') format('embedded-opentype'), + url('../fonts/icons.woff') format('woff'), + url('../fonts/icons.ttf') format('truetype'), + url('../fonts/icons.svg#icons') format('svg'); + font-weight: normal; + font-style: normal; +} + +/* Use the following CSS code if you want to use data attributes for inserting your icons */ +[data-icon]:before { + font-family: 'icons'; + content: attr(data-icon); + speak: none; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; +} + +.icon-twitter:before, .icon-stumbleupon:before, .icon-pinterest:before, .icon-linkedin:before, .icon-google-plus:before, .icon-search:before, .icon-play:before, .icon-menu:before, .icon-arrow-left:before, .icon-arrow-right:before, .icon-bubble:before, .icon-facebook:before, .icon-feed:before, .icon-youtube:before, .icon-tag:before, .icon-tumblr:before, .icon-instagram, .icon-podcast, .icon-apple,.icon-android, .icon-arrow:after, .icon-envelope:before { + font-family: 'icons'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} +.icon-twitter:before { + content: "\74"; +} +.icon-stumbleupon:before { + content: "\75"; +} +.icon-pinterest:before { + content: "\70"; +} +.icon-linkedin:before { + content: "\69"; +} +.icon-google-plus:before { + content: "\67"; +} +.icon-search:before { + content: "\73"; +} +.icon-play:before { + content: "\61"; +} +.icon-menu:before { + content: "\21"; +} +.icon-arrow-left:before { + content: "\23"; +} +.icon-arrow-right:before { + content: "\24"; +} +.icon-bubble:before { + content: "\25"; +} +.icon-facebook:before { + content: "\66"; +} +.icon-feed:before { + content: "\27"; +} +.icon-youtube:before { + content: "\79"; +} +.icon-tag:before { + content: "\28"; +} +.icon-tumblr:before { + content: "\6d"; +} +.icon-instagram:before { + content: "\22"; +} +.icon-podcast:before { + content: "\26"; +} +.icon-android:before { + content: "\29"; +} +.icon-apple:before { + content: "\2a"; +} +.icon-envelope:before { + content: "\2b"; +} + + +.icon-arrow:after { + content: "\61"; + display: inline-block; + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -ms-transform: rotate(90deg); + -o-transform: rotate(90deg); + transform: rotate(90deg); +} + +.icon-play:before { + font-size: 0.7rem; + padding-left: 0.2em; +} + +.icon-play-box { + display: block; + margin-left: $pad-double; +} + +.icon-play-box:before { + padding: $pad-quarter; + background: $gray; + color: $white; + margin-left: -1.7rem; + margin-right: $pad-half; +} diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_layout.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_layout.scss new file mode 100755 index 000000000..4929d28e3 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_layout.scss @@ -0,0 +1,288 @@ +/*------------------------------------*\ + $LAYOUT +\*------------------------------------*/ + +/* Layout Container */ +.lc { + max-width: $max-width; + margin: 0 auto; + padding: $pad-half; +} + +/*------------------------------------*\ + $TEMPLATES +\*------------------------------------*/ + +/* Two Column Layout */ +.l-two-col { + @extend .cf; + + .l-main { + @media all and (min-width: $bp-large) { + float: left; + width: 70%; + padding-right: $pad; + } + } + + .l-sidebar { + @media all and (max-width: $bp-large) { + clear: both; + } + + @media all and (min-width: $bp-large) { + float: left; + width: 30%; + padding: 0 0 0 $pad; + } + } +} + + +/*------------------------------------*\ + $GRIDS +\*------------------------------------*/ + +/* Grid Container */ +.g { + overflow: hidden; + margin: 0 (-$pad-half); +} + +/* Grid Item */ +.gi { + padding: $pad-half; + + img { + display: block; + } + + @media all and (min-width: $bp-med) { + float: left; + } +} + +/* Grid 1up */ +.g-1up { + .gi { + width: 100%; + } +} + +/* Grid 2up */ +.g-2up { + @media all and (min-width: $bp-med) { + > .gi { + float: left; + width: 50%; + + &:nth-of-type(odd) { + clear: left; + } + } + } +} + +/* Grid Half (Always displayed side by side) */ +.g-half { + > .gi { + float: left; + width: 50%; + + &:nth-of-type(odd) { + clear: left; + } + } +} + +/* Grid 3up */ +.g-3up { + @media all and (min-width: $bp-med) { + > .gi { + float: left; + width: 50%; + + &:nth-of-type(2n+1) { + clear: left; + } + } + } + + @media all and (min-width: $bp-large) { + > .gi { + width: 33.3333333%; + + &:nth-of-type(2n+1) { + clear: none; + } + + &:nth-of-type(3n+1) { + clear: left; + } + } + } +} + +/* Grid 4up */ +.g-4up { + @media all and (min-width: $bp-med) { + >.gi { + float: left; + width: 50%; + + &:nth-of-type(2n+1) { + clear: both; + } + } + } + + @media all and (min-width: $bp-large) { + >.gi { + width: 25%; + + &:nth-of-type(2n+1) { + clear: none; + } + + &:nth-of-type(4n+1) { + clear: left; + } + } + } +} + +/* Grid Quarter (Always displayed side by side) */ +.g-quarter { + > .gi { + float: left; + width: 24%; + + &:nth-of-type(4n+1) { + clear: left; + } + } +} + +.g-max4 { + + @media all and (min-width: $bp-small-2) { + >.gi { + float: left; + width: 50%; + + &:nth-of-type(2n+1) { + clear: both; + } + } + } + + @media all and (min-width: $bp-small-3) { + >.gi { + width: 33.3333333%; + + &:nth-of-type(2n+1) { + clear: none; + } + + &:nth-of-type(3n+1) { + clear: left; + } + } + } + + @media all and (min-width: $bp-large) { + >.gi { + width: 25%; + + &:nth-of-type(3n+1) { + clear: none; + } + + &:nth-of-type(4n+1) { + clear: left; + } + } + } +} + +/* Grid 5up */ +.g-max5 { + + >.gi { + float: left; + width: 50%; + + &:nth-of-type(2n+1) { + clear: both; + } + } + + @media all and (min-width: $bp-small-3) { + >.gi { + width: 33.3333333%; + + &:nth-of-type(2n+1) { + clear: none; + } + + &:nth-of-type(3n+1) { + clear: left; + } + } + } + + @media all and (min-width: $bp-med) { + >.gi { + width: 25%; + + &:nth-of-type(3n+1) { + clear: none; + } + + &:nth-of-type(4n+1) { + clear: left; + } + } + } + + @media all and (min-width: $bp-med-2) { + >.gi { + width: 20%; + + &:nth-of-type(4n+1) { + clear: none; + } + + &:nth-of-type(5n+1) { + clear: left; + } + } + } +} + +/* Grid 2/3 */ +.gi-2-3 { + @media all and (min-width: $bp-med) { + float: left; + width: 66.666666%; + } +} + +.gi-1-3 { + @media all and (min-width: $bp-med) { + float: left; + width: 33.333333%; + } +} + +/* Grid 4up block */ +.g-opposites { + .gi { + float: left; + + &:last-child { + float: right; + text-align: right; + } + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_lists.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_lists.scss new file mode 100755 index 000000000..0c4e8beaa --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_lists.scss @@ -0,0 +1,63 @@ +.inline-list { + li { + display: inline-block; + } +} + +/* Social List */ +.social-list { + li { + margin: 0 0.4rem $space 0; + } + + a { + font-size: 1.6em; + } +} + + +/* Headline List */ +.headline-list { + margin-bottom: $space; + + &.flush { + margin: 0; + } + + h4 { + font-weight: normal; + } + + li { + padding: $pad-quarter 0; + border-top: 1px solid $gray-light-3; + } +} + +/* Post List */ +.post-list { + li { + margin-bottom: $space; + } +} + +/* Bullet List */ +.bullet-list { + list-style: square; + margin: 0 0 1em 1.2em; + line-height: 1.3; + + li { + margin-bottom: $space; + } +} + +/* Text List */ +.text-list { + margin: 0 0 1em; + line-height: 1.3; + + li { + margin-bottom: $space; + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_main.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_main.scss new file mode 100755 index 000000000..592c01dea --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_main.scss @@ -0,0 +1,9 @@ +/*------------------------------------*\ + $MAIN CONTENT AREA +\*------------------------------------*/ +[role=main] { + padding: $pad-half $pad-half $pad-double; + overflow: hidden; + @extend .lc; + @extend .cf; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_messaging.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_messaging.scss new file mode 100755 index 000000000..a2d31a92e --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_messaging.scss @@ -0,0 +1,18 @@ +/*------------------------------------*\ + $MESSAGING +\*------------------------------------*/ + +// Alerts +.alert { + text-align: center; + padding: $pad; + margin-bottom: $space-half; + border: 1px solid $gray; + background: $gray-light; +} + +.alert-error { + color: $error; + border-color: $error; + background: #ffbebe; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_nav.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_nav.scss new file mode 100755 index 000000000..b98e3300e --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_nav.scss @@ -0,0 +1,52 @@ +/*------------------------------------*\ + $NAVIGATION +\*------------------------------------*/ + +.nav { + clear: both; + overflow: hidden; + max-height: 0; + + a { + display: block; + padding: $pad; + border-top: 1px solid $gray-light-2; + } + + &.active { + max-height: 40em; + } + + @media all and (min-width: $bp-med) { + max-height: none; + float: right; + clear: none; + + li { + float: left; + } + + a { + border: 0; + } + + } +} + +//Pagination +.pagination { + overflow: hidden; + + li { + float: left; + border-right: 1px solid $gray-light-2; + + &:last-child { + border: 0; + } + } + + a { + padding: $pad; + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_sections.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_sections.scss new file mode 100755 index 000000000..7235af263 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_sections.scss @@ -0,0 +1,7 @@ +.section { + margin: 0 0 $space; +} + +.section-title { + margin-bottom: $space-half; +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_tabs.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_tabs.scss new file mode 100755 index 000000000..b22ca7438 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_tabs.scss @@ -0,0 +1,33 @@ +.tabs { + overflow: hidden; + + ul { + display: table; + width: 100%; + } + + li { + display: table-cell; + text-align: center; + border-right: 1px solid $gray-light-3; + + &:last-child { + border-right: 0; + } + } + + a { + display: block; + padding: $pad-half; + background: $gray; + + &:hover, &:focus { + background: $gray-light-3; + } + + &.active { + background: $gray-dark; + color: $white; + } + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_text.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_text.scss new file mode 100755 index 000000000..d645f5646 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_text.scss @@ -0,0 +1,41 @@ +//Intro text +.intro { + font-size: $font-size-med-2; + font-weight: bold; +} + +//Pullquote +.pullquote { + font-family: $font-secondary; + font-size: $font-size-large; +} + +//Caption +.caption { + font-style: italic; +} + +//Passages of text +.text { + a { + text-decoration: underline; + } + + ul { + list-style: disc; + margin: 0 0 $space 1.2em; + + ul { + margin-bottom: 0; + } + } + + ol { + list-style: decimal; + margin: 0 0 $space 1.5em; + + ol { + margin-bottom: 0; + } + } +} diff --git a/packages/starterkit-twig-demo/dist/css/scss/objects/_tooltip.scss b/packages/starterkit-twig-demo/dist/css/scss/objects/_tooltip.scss new file mode 100755 index 000000000..e0fdd95da --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/scss/objects/_tooltip.scss @@ -0,0 +1,42 @@ +.tooltip-container { + display: inline-block; + position: relative; + + &:hover { + .tooltip { + display: block; + } + } +} + +.tooltip-link { + background: $gray-light; +} + +.tooltip { + display: none; + position: absolute; + top: 1.5em; + left: 0; + width: 18em; + padding: $pad; + background: $white; + border: 1px solid $gray; + box-shadow: 0.3em 0.3em 1em 0 rgba(0,0,0,0.2); + + h2 { + margin-top: 0; + } + + @media all and (min-width: $bp-small) { + width: 22em; + } + + @media all and (min-width: $bp-small-2) { + width: 27em; + } + + @media all and (min-width: $bp-small-3) { + width: 30em; + } +} \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/css/style.css b/packages/starterkit-twig-demo/dist/css/style.css new file mode 100755 index 000000000..e176c1aae --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/style.css @@ -0,0 +1,1481 @@ +/* + ,, ,, ,, + .M"""bgd mm db `7MM mm mm `7MM OO OO OO +,MI "Y MM MM MM MM MM 88 88 88 +`MMb. mmMMmm ,pW"Wq.`7MMpdMAo. `7Mb,od8 `7MM .P"Ybmmm MMpMMMb.mmMMmm mmMMmm MMpMMMb. .gP"Ya `7Mb,od8 .gP"Ya || || || + `YMMNq. MM 6W' `Wb MM `Wb MM' "' MM :MI I8 MM MM MM MM MM MM ,M' Yb MM' "',M' Yb || || || +. `MM MM 8M M8 MM M8 MM MM WmmmP" MM MM MM MM MM MM 8M"""""" MM 8M"""""" `' `' `' +Mb dM MM YA. ,A9 MM ,AP MM MM 8M MM MM MM MM MM MM YM. , MM YM. , ,, ,, ,, +P"Ybmmd" `Mbmo`Ybmd9' MMbmmd' .JMML. .JMML.YMMMMMb .JMML JMML.`Mbmo `Mbmo.JMML JMML.`Mbmmd'.JMML. `Mbmmd' db db db + MM 6' dP + .JMML. Ybmmmd' + +Pattern Lab doesn't have any CSS requirements, which means you can write your styles however you want. Hooray! +You can use Sass, Less, vanilla CSS, or some other crazy thing I haven't heard of yet. +So please don't use these styles. They're just here to put together the demo, and nothing more. +They're intentionally gray, boring, and crappy because you're supposed to do this stuff yourself. + +Atomic design is philosophically complimentary with these CSS approaches: + +* SMACSS by Jonathan Snook http://smacss.com/ +* OOCSS by Nicole Sullivan http://oocss.org/ +* BEM CSS Methology : http://bem.info/method/ +* CSS Guidelines by Harry Roberts : https://cssguidelin.es/ + +So feel free to use any of these approaches. Or don't. It's totally up to you. + +*/ +/*------------------------------------*\ + $TABLE OF CONTENTS + based generally on Harry Roberts excellent CSS Guidelines https://cssguidelin.es/ +\*------------------------------------*/ +/** + * VARIABLES..............................Declarations of Sass variables + * .....Colors + * .....Typography + * .....Layout + * .....Defaults + * .....Breakpoints + * MIXINS.................................Sass mixins + * RESET..................................Set reset defaults + * GLOBAL CLASSES.........................Set reset defaults + * GLOBAL ELEMENTS........................Establish global styles + * .....Main + * .....Headings + * .....Text-related elements (p, blockquote, lists) + * .....Defaults + * .....Breakpoints + * TYPOGRAPHY------------------------------ + * MEDIA------------------------------ + * LAYOUT------------------------------ + * NAVIGATION------------------------------ + * TOC To Be Continued + */ +/*------------------------------------*\ + $VARIABLES +\*------------------------------------*/ +/*------------------------------------*\ + $MIXINS +\*------------------------------------*/ +/* CSS Transition + Usage: @include transition(width,0.3s,ease-out); + */ +/* Rem Unit font sizes with relative fallback http:/seesparkbox.com/foundry/scss_rem_mixin_now_with_a_better_fallback + Usage: @include font-size(1, large); +*/ +/*------------------------------------*\ + $RESET +\*------------------------------------*/ +/* Border-Box http:/paulirish.com/2012/box-sizing-border-box-ftw/ */ +* { + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +html, +body, +div, +object, +iframe, +h1, +h2, +h3, +h4, +h5, +h6, +p, +blockquote, +ol, +ul, +li, +form, +legend, +label, +table, +header, +footer, +nav, +section, +figure { + margin: 0; + padding: 0; +} + +header, +footer, +nav, +section, +article, +hgroup, +figure { + display: block; +} + +/*------------------------------------*\ + $GLOBAL ELEMENTS +\*------------------------------------*/ +/*------------------------------------*\ + $GLOBAL CLASSES +\*------------------------------------*/ +/* Clearfix */ +.cf, +.l-two-col, +[role='main'] { + *zoom: 1; +} + +.cf:before, +.l-two-col:before, +[role='main']:before, +.cf:after, +.l-two-col:after, +[role='main']:after { + content: ' '; + /* 1 */ + display: table; + /* 2 */ +} + +.cf:after, +.l-two-col:after, +[role='main']:after { + clear: both; +} + +/* Completely remove from the flow and screen readers. */ +.is-hidden { + display: none !important; + visibility: hidden !important; +} + +/* Completely remove from the flow but leave available to screen readers. */ +.is-vishidden { + position: absolute !important; + overflow: hidden; + width: 1px; + height: 1px; + padding: 0; + border: 0; + clip: rect(1px, 1px, 1px, 1px); +} + +/* Floats */ +.right { + float: right; + padding: 0 0 1em 1em; +} + +.right-search { + float: right; + padding: 0 0 1em 0; +} + +.left { + float: left; + padding: 0 1em 1em 0; +} + +/* Text-Align */ +.align-right { + text-align: right; +} + +.align-center { + text-align: center; +} + +.align-left { + text-align: left; +} + +/* Display Classes */ +@media all and (max-width: 46.8em) { + .hide-small { + display: none; + } +} + +@media all and (min-width: 46.8em) and (max-width: 50em) { + .hide-med { + display: none; + } +} + +@media all and (min-width: 50em) { + .hide-large { + display: none; + } +} + +.valid { + color: #089e00; +} + +.error { + color: red; +} + +.warning { + color: #fff664; +} + +.information { + color: #000db5; +} + +.font-secondary { + font-family: Georgia, Times, 'Times New Roman', serif; +} + +body { + background: white; + font: 100%/1.5 'HelveticaNeue', 'Helvetica', 'Arial', sans-serif; + -webkit-text-size-adjust: 100%; + color: #333333; +} + +/* Links */ +a { + color: #333333; + text-decoration: none; + outline: 0; +} +a:hover, +a:focus { + color: gray; +} + +/* Headings */ +/*Further Reading: http:/csswizardry.com/2012/02/pragmatic-practical-font-sizing-in-css/ */ +h1, +.alpha { + line-height: 1.2; +} + +h2, +.beta { + line-height: 1.2; +} + +h3, +.gamma { + line-height: 1.2; +} + +/* Subheadings */ +.subheading { + font-family: Georgia, Times, 'Times New Roman', serif; + font-weight: normal; +} + +/* Text-Related Elements */ +p { + margin-bottom: 1em; +} + +/* Blockquote */ +blockquote { + font-style: italic; + border-left: 1px solid gray; + color: gray; + padding-left: 1em; + margin-bottom: 1em; +} + +/* Horizontal Rule */ +hr { + border: 0; + height: 2px; + background: #eeeeee; + margin: 2em 0; +} + +abbr { + border-bottom: 1px dotted gray; + cursor: help; +} + +ol, +ul { + margin: 0; + padding: 0; + list-style: none; +} + +/* Definition Lists */ +dl { + overflow: hidden; + margin: 0 0 1em; +} + +dt { + font-weight: bold; +} + +dd { + margin-left: 0; +} + +/*------------------------------------*\ + $MEDIA ELEMENTS +\*------------------------------------*/ +/* Flexible Media */ +img, +video, +object { + max-width: 100%; + height: auto; +} + +iframe { + margin-bottom: 1em; +} + +figure { + margin-bottom: 1em; +} +figure img { + margin-bottom: 0.5em; +} + +figcaption { + font-style: italic; +} + +/*------------------------------------*\ + $FORMS +\*------------------------------------*/ +form ol, +form ul { + list-style: none; + margin-left: 0; +} + +fieldset { + border: 0; + padding: 0; + margin: 0; +} + +label { + display: block; + padding-bottom: 0.5em; +} + +button, +input, +select, +textarea { + font-family: inherit; + font-size: 100%; + margin: 0 1px 0; +} + +input, +textarea { + width: 100%; + border: 1px solid gray; + padding: 0.5em 0.65rem; +} + +input[type='text'], +input[type='search'], +input[type='url'], +input[type='number'], +textarea { + -webkit-appearance: none; +} + +button, +input[type='submit'] { + padding: 0.5em; + background: #333333; + border: 1px solid gray; + cursor: pointer; +} + +input[type='checkbox'], +input[type='radio'] { + width: auto; + margin-right: 0.3em; +} + +input[type='search'] { + -webkit-appearance: none; + border-radius: 0; +} + +input[type='search']::-webkit-search-cancel-button, +input[type='search']::-webkit-search-decoration { + -webkit-appearance: none; +} + +.field-container { + margin-bottom: 1em; +} + +.inline-form fieldset, +.inline-form .inline-container { + position: relative; +} +.inline-form input[type='submit'], +.inline-form button, +.inline-form .btn { + font-size: 0.875em; + padding: 0.65rem 1.3rem; + background: #333333; + position: absolute; + top: 0; + right: 0; + z-index: 1; + width: auto; +} +.inline-form input[type='submit']:hover, +.inline-form input[type='submit']:focus, +.inline-form button:hover, +.inline-form button:focus, +.inline-form .btn:hover, +.inline-form .btn:focus { + background: gray; + color: white; +} + +/* Validation */ +.has-error { + border-color: red; +} + +.is-valid { + border-color: #089e00; +} + +/*------------------------------------*\ + $SPECIFIC FORMS +\*------------------------------------*/ +/* Search Form */ +.search-field { + padding-right: 3em; +} + +.inline-form .search-submit { + background: none; + padding: 0.78em 1em; + border: 0; + border-left: 1px solid gray; + color: gray; +} + +/*------------------------------------*\ + $Table +\*------------------------------------*/ +table { + border-collapse: collapse; + border-spacing: 0; + border: 1px solid gray; + width: 100%; +} + +th { + text-align: left; + border: 1px solid gray; + padding: 0.2em; +} + +td { + border: 1px solid gray; + padding: 0.2em; +} + +.animate-fade { + -webkit-transition: opacity 0.3s ease-out; + -moz-transition: opacity 0.3s ease-out; + -ms-transition: opacity 0.3s ease-out; + -o-transition: opacity 0.3s ease-out; + transition: opacity 0.3s ease-out; +} +.animate-fade:hover { + opacity: 0; +} + +.animate-move > .demo-shape { + -webkit-transition: all 0.8s ease-in-out; + -moz-transition: all 0.8s ease-in-out; + -ms-transition: all 0.8s ease-in-out; + -o-transition: all 0.8s ease-in-out; + transition: all 0.8s ease-in-out; +} + +/*------------------------------------*\ + $LAYOUT +\*------------------------------------*/ +/*------------------------------------*\ + $LAYOUT +\*------------------------------------*/ +/* Layout Container */ +.lc, +.header, +[role='main'] { + max-width: 72em; + margin: 0 auto; + padding: 0.5em; +} + +/*------------------------------------*\ + $TEMPLATES +\*------------------------------------*/ +/* Two Column Layout */ +@media all and (min-width: 50em) { + .l-two-col .l-main { + float: left; + width: 70%; + padding-right: 1em; + } +} +@media all and (max-width: 50em) { + .l-two-col .l-sidebar { + clear: both; + } +} +@media all and (min-width: 50em) { + .l-two-col .l-sidebar { + float: left; + width: 30%; + padding: 0 0 0 1em; + } +} + +/*------------------------------------*\ + $GRIDS +\*------------------------------------*/ +/* Grid Container */ +.g { + overflow: hidden; + margin: 0 -0.5em; +} + +/* Grid Item */ +.gi { + padding: 0.5em; +} +.gi img { + display: block; +} +@media all and (min-width: 46.8em) { + .gi { + float: left; + } +} + +/* Grid 1up */ +.g-1up .gi { + width: 100%; +} + +/* Grid 2up */ +@media all and (min-width: 46.8em) { + .g-2up > .gi { + float: left; + width: 50%; + } + .g-2up > .gi:nth-of-type(odd) { + clear: left; + } +} + +/* Grid Half (Always displayed side by side) */ +.g-half > .gi { + float: left; + width: 50%; +} +.g-half > .gi:nth-of-type(odd) { + clear: left; +} + +/* Grid 3up */ +@media all and (min-width: 46.8em) { + .g-3up > .gi { + float: left; + width: 50%; + } + .g-3up > .gi:nth-of-type(2n + 1) { + clear: left; + } +} +@media all and (min-width: 50em) { + .g-3up > .gi { + width: 33.3333333%; + } + .g-3up > .gi:nth-of-type(2n + 1) { + clear: none; + } + .g-3up > .gi:nth-of-type(3n + 1) { + clear: left; + } +} + +/* Grid 4up */ +@media all and (min-width: 46.8em) { + .g-4up > .gi { + float: left; + width: 50%; + } + .g-4up > .gi:nth-of-type(2n + 1) { + clear: both; + } +} +@media all and (min-width: 50em) { + .g-4up > .gi { + width: 25%; + } + .g-4up > .gi:nth-of-type(2n + 1) { + clear: none; + } + .g-4up > .gi:nth-of-type(4n + 1) { + clear: left; + } +} + +/* Grid Quarter (Always displayed side by side) */ +.g-quarter > .gi { + float: left; + width: 24%; +} +.g-quarter > .gi:nth-of-type(4n + 1) { + clear: left; +} + +@media all and (min-width: 29.75em) { + .g-max4 > .gi { + float: left; + width: 50%; + } + .g-max4 > .gi:nth-of-type(2n + 1) { + clear: both; + } +} +@media all and (min-width: 39.8em) { + .g-max4 > .gi { + width: 33.3333333%; + } + .g-max4 > .gi:nth-of-type(2n + 1) { + clear: none; + } + .g-max4 > .gi:nth-of-type(3n + 1) { + clear: left; + } +} +@media all and (min-width: 50em) { + .g-max4 > .gi { + width: 25%; + } + .g-max4 > .gi:nth-of-type(3n + 1) { + clear: none; + } + .g-max4 > .gi:nth-of-type(4n + 1) { + clear: left; + } +} + +/* Grid 5up */ +.g-max5 > .gi { + float: left; + width: 50%; +} +.g-max5 > .gi:nth-of-type(2n + 1) { + clear: both; +} +@media all and (min-width: 39.8em) { + .g-max5 > .gi { + width: 33.3333333%; + } + .g-max5 > .gi:nth-of-type(2n + 1) { + clear: none; + } + .g-max5 > .gi:nth-of-type(3n + 1) { + clear: left; + } +} +@media all and (min-width: 46.8em) { + .g-max5 > .gi { + width: 25%; + } + .g-max5 > .gi:nth-of-type(3n + 1) { + clear: none; + } + .g-max5 > .gi:nth-of-type(4n + 1) { + clear: left; + } +} +@media all and (min-width: 48em) { + .g-max5 > .gi { + width: 20%; + } + .g-max5 > .gi:nth-of-type(4n + 1) { + clear: none; + } + .g-max5 > .gi:nth-of-type(5n + 1) { + clear: left; + } +} + +/* Grid 2/3 */ +@media all and (min-width: 46.8em) { + .gi-2-3 { + float: left; + width: 66.666666%; + } +} + +@media all and (min-width: 46.8em) { + .gi-1-3 { + float: left; + width: 33.333333%; + } +} + +/* Grid 4up block */ +.g-opposites .gi { + float: left; +} +.g-opposites .gi:last-child { + float: right; + text-align: right; +} + +/*------------------------------------*\ + $PAGE STRUCTURE +\*------------------------------------*/ +.header { + background: white; + padding: 0; + border-bottom: 1px solid #dbdbdb; +} + +.logo { + float: left; + max-width: 8rem; + margin: 0.4rem; +} +@media all and (min-width: 46.8em) { + .logo { + max-width: 9rem; + } +} + +.nav-toggle { + float: right; + display: block; + padding: 0.9rem 1rem 0.7rem; + font-size: 1.3rem; + line-height: 1; + border-left: 1px solid #dbdbdb; +} +@media all and (min-width: 46.8em) { + .nav-toggle { + display: none; + } +} +@media all and (max-width: 17em) { + .nav-toggle { + padding-left: 0.2rem; + padding-right: 0.2rem; + } +} + +.search-form { + overflow: hidden; + max-height: 0; +} +@media all and (min-width: 46.8em) { + .search-form { + float: right; + max-height: none; + margin: 0.65em 0.5em 0 0; + } +} + +/*------------------------------------*\ + $NAVIGATION +\*------------------------------------*/ +.nav { + clear: both; + overflow: hidden; + max-height: 0; +} +.nav a { + display: block; + padding: 1em; + border-top: 1px solid #eeeeee; +} +.nav.active { + max-height: 40em; +} +@media all and (min-width: 46.8em) { + .nav { + max-height: none; + float: right; + clear: none; + } + .nav li { + float: left; + } + .nav a { + border: 0; + } +} + +.pagination { + overflow: hidden; +} +.pagination li { + float: left; + border-right: 1px solid #eeeeee; +} +.pagination li:last-child { + border: 0; +} +.pagination a { + padding: 1em; +} + +/*------------------------------------*\ + $MAIN CONTENT AREA +\*------------------------------------*/ +[role='main'] { + padding: 0.5em 0.5em 2em; + overflow: hidden; +} + +/*------------------------------------*\ + $FOOTER +\*------------------------------------*/ +.footer { + clear: both; + overflow: hidden; + background: #131313; + color: white; + line-height: 1.2; +} +.footer a { + color: #f9f9f9; +} + +.nav-footer { + margin: -1em -1em 1em; +} +.nav-footer li { + border-bottom: 1px solid #333333; +} +@media all and (min-width: 46.8em) { + .nav-footer li { + border: 0; + float: left; + } +} +.nav-footer a { + display: block; + padding: 1em; +} + +@media all and (min-width: 46.8em) { + .copyright { + float: right; + } +} + +/*------------------------------------*\ + $TEXT Styles +\*------------------------------------*/ +.intro { + font-size: 1.0625em; + font-weight: bold; +} + +.pullquote { + font-family: Georgia, Times, 'Times New Roman', serif; + font-size: 1.4375em; +} + +.caption { + font-style: italic; +} + +.text a { + text-decoration: underline; +} +.text ul { + list-style: disc; + margin: 0 0 1em 1.2em; +} +.text ul ul { + margin-bottom: 0; +} +.text ol { + list-style: decimal; + margin: 0 0 1em 1.5em; +} +.text ol ol { + margin-bottom: 0; +} + +/*------------------------------------*\ + $COMPONENTS +\*------------------------------------*/ +/* Icon Font */ +@font-face { + font-family: 'icons'; + src: url('../fonts/icons.eot'); + src: url('../fonts/icons.eot?#iefix') format('embedded-opentype'), + url('../fonts/icons.woff') format('woff'), + url('../fonts/icons.ttf') format('truetype'), + url('../fonts/icons.svg#icons') format('svg'); + font-weight: normal; + font-style: normal; +} + +/* Use the following CSS code if you want to use data attributes for inserting your icons */ +[data-icon]:before { + font-family: 'icons'; + content: attr(data-icon); + speak: none; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; +} + +.icon-twitter:before, +.icon-stumbleupon:before, +.icon-pinterest:before, +.icon-linkedin:before, +.icon-google-plus:before, +.icon-search:before, +.icon-play:before, +.icon-menu:before, +.icon-arrow-left:before, +.icon-arrow-right:before, +.icon-bubble:before, +.icon-facebook:before, +.icon-feed:before, +.icon-youtube:before, +.icon-tag:before, +.icon-tumblr:before, +.icon-instagram, +.icon-podcast, +.icon-apple, +.icon-android, +.icon-arrow:after, +.icon-envelope:before { + font-family: 'icons'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; +} + +.icon-twitter:before { + content: '\74'; +} + +.icon-stumbleupon:before { + content: '\75'; +} + +.icon-pinterest:before { + content: '\70'; +} + +.icon-linkedin:before { + content: '\69'; +} + +.icon-google-plus:before { + content: '\67'; +} + +.icon-search:before { + content: '\73'; +} + +.icon-play:before { + content: '\61'; +} + +.icon-menu:before { + content: '\21'; +} + +.icon-arrow-left:before { + content: '\23'; +} + +.icon-arrow-right:before { + content: '\24'; +} + +.icon-bubble:before { + content: '\25'; +} + +.icon-facebook:before { + content: '\66'; +} + +.icon-feed:before { + content: '\27'; +} + +.icon-youtube:before { + content: '\79'; +} + +.icon-tag:before { + content: '\28'; +} + +.icon-tumblr:before { + content: '\6d'; +} + +.icon-instagram:before { + content: '\22'; +} + +.icon-podcast:before { + content: '\26'; +} + +.icon-android:before { + content: '\29'; +} + +.icon-apple:before { + content: '\2a'; +} + +.icon-envelope:before { + content: '\2b'; +} + +.icon-arrow:after { + content: '\61'; + display: inline-block; + -webkit-transform: rotate(90deg); + -moz-transform: rotate(90deg); + -ms-transform: rotate(90deg); + -o-transform: rotate(90deg); + transform: rotate(90deg); +} + +.icon-play:before { + font-size: 0.7rem; + padding-left: 0.2em; +} + +.icon-play-box { + display: block; + margin-left: 2em; +} + +.icon-play-box:before { + padding: 0.25em; + background: gray; + color: white; + margin-left: -1.7rem; + margin-right: 0.5em; +} + +/*------------------------------------*\ + $BUTTONS +\*------------------------------------*/ +.btn { + display: inline-block; + background: #333333; + color: white; + line-height: 1; + font-weight: bold; + padding: 1em; + border: 0; + text-align: center; +} +.btn:hover, +.btn:focus { + background: gray; + color: white; +} +.btn.disabled { + background: #eeeeee; + color: gray; +} + +.btn-small { + padding: 0.5em; +} + +.btn-large { + padding: 0.5em; + text-transform: uppercase; + background: gray; + font-size: 1.4rem; + font-weight: normal; +} + +.text-btn { + font-style: italic; +} + +/* Generic Placeholder Brick: REMOVE FOR PRODUCTION */ +.brick { + background: #dcdddc; + padding: 2em; + text-align: center; + font-weight: bold; + border-bottom: 1px solid #eeeeee; +} + +/* Block */ +.block { + overflow: hidden; +} +.block p:last-child { + margin-bottom: 0; +} + +.headline { + line-height: 1.2; +} + +/* Hero Block */ +.block-hero { + margin-bottom: 0.5em; +} +.block-hero .b-thumb img { + display: block; +} +@media all and (min-width: 50em) { + .block-hero { + position: relative; + } + .block-hero .b-text { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + background: rgba(0, 0, 0, 0.5); + color: white; + padding: 1.5em; + } +} + +/* Block Thumbnail with Headline */ +.block-thumb { + display: table; + width: 100%; + border-collapse: collapse; +} +.block-thumb .b-inner { + display: table-row; + vertical-align: top; + overflow: hidden; +} +@media all and (min-width: 29.75em) { + .block-thumb .b-thumb { + display: table-cell; + vertical-align: top; + width: 30%; + max-width: 10em; + } + .block-thumb .b-thumb img { + display: block; + width: 100%; + height: auto; + } +} +@media all and (min-width: 29.75em) { + .block-thumb .b-text { + display: table-cell; + width: 70%; + padding: 0 1em; + } +} + +/* Block Headline Summary */ +.block-headline-summary a { + display: block; + padding: 0.5em; +} + +/* Block Inset */ +.block-inset { + position: relative; +} +.block-inset .b-thumb { + position: relative; + z-index: 0; +} +.block-inset .b-thumb img { + display: block; +} + +/* Hero Block */ +.block-inset { + margin-bottom: 0.5em; + position: relative; +} +.block-inset .headline { + font-size: 1.1em; +} +.block-inset .b-text { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + background: rgba(0, 0, 0, 0.5); + color: white; + padding: 0.5em; +} + +/* Block Thumb with Summary */ +.block-thumb-summary .b-thumb { + float: left; + width: 50%; +} +.block-thumb-summary .b-text { + margin-left: 50%; + padding: 0.5em; +} + +.inline-list li { + display: inline-block; +} + +/* Social List */ +.social-list li { + margin: 0 0.4rem 1em 0; +} +.social-list a { + font-size: 1.6em; +} + +/* Headline List */ +.headline-list { + margin-bottom: 1em; +} +.headline-list.flush { + margin: 0; +} +.headline-list h4 { + font-weight: normal; +} +.headline-list li { + padding: 0.25em 0; + border-top: 1px solid #dddddd; +} + +/* Post List */ +.post-list li { + margin-bottom: 1em; +} + +/* Bullet List */ +.bullet-list { + list-style: square; + margin: 0 0 1em 1.2em; + line-height: 1.3; +} +.bullet-list li { + margin-bottom: 1em; +} + +/* Text List */ +.text-list { + margin: 0 0 1em; + line-height: 1.3; +} +.text-list li { + margin-bottom: 1em; +} + +.tooltip-container { + display: inline-block; + position: relative; +} +.tooltip-container:hover .tooltip { + display: block; +} + +.tooltip-link { + background: #f9f9f9; +} + +.tooltip { + display: none; + position: absolute; + top: 1.5em; + left: 0; + width: 18em; + padding: 1em; + background: white; + border: 1px solid gray; + box-shadow: 0.3em 0.3em 1em 0 rgba(0, 0, 0, 0.2); +} +.tooltip h2 { + margin-top: 0; +} +@media all and (min-width: 24em) { + .tooltip { + width: 22em; + } +} +@media all and (min-width: 29.75em) { + .tooltip { + width: 27em; + } +} +@media all and (min-width: 39.8em) { + .tooltip { + width: 30em; + } +} + +.accordion { + margin-bottom: 1em; +} + +.acc-handle { + background: #131313; + color: white; + font-family: Georgia, Times, 'Times New Roman', serif; + font-weight: bold; + display: block; + position: relative; + padding: 0.5em; + border-bottom: 1px solid #dddddd; +} +.acc-handle:after { + content: '+'; + float: right; +} +.acc-handle:hover { + color: white; + background: #333333; +} +.acc-handle.active { + background: #333333; +} +.acc-handle.active:after { + content: '-'; +} + +.tabs { + overflow: hidden; +} +.tabs ul { + display: table; + width: 100%; +} +.tabs li { + display: table-cell; + text-align: center; + border-right: 1px solid #dddddd; +} +.tabs li:last-child { + border-right: 0; +} +.tabs a { + display: block; + padding: 0.5em; + background: gray; +} +.tabs a:hover, +.tabs a:focus { + background: #dddddd; +} +.tabs a.active { + background: #333333; + color: white; +} + +.section { + margin: 0 0 1em; +} + +.section-title { + margin-bottom: 0.5em; +} + +.article-header h1 { + font-size: 2.5em; +} + +.byline { + font-size: 0.875em; + font-style: italic; + margin-bottom: 0.5em; +} + +.social-share { + overflow: hidden; + margin-bottom: 1em; +} +.social-share li { + float: left; + margin-right: 0.5em; +} +.social-share a { + background: #333333; + color: white; + display: block; + padding: 0.5em; +} +.social-share a:hover { + background: gray; +} + +.comments li { + margin-bottom: 1em; +} + +.comment-container { + overflow: hidden; + margin-bottom: 1em; + list-style: none; +} + +.comment-meta { + float: left; + width: 6.5em; +} +.comment-meta img { + display: block; + border: 1px solid #eeeeee; + margin-bottom: 0.5em; +} + +.comment-name { + font-size: 0.75em; +} + +.comment-text { + margin-left: 9em; +} + +/*------------------------------------*\ + $MESSAGING +\*------------------------------------*/ +.alert { + text-align: center; + padding: 1em; + margin-bottom: 0.5em; + border: 1px solid gray; + background: #f9f9f9; +} + +.alert-error { + color: red; + border-color: red; + background: #ffbebe; +} diff --git a/packages/starterkit-twig-demo/dist/css/style.scss b/packages/starterkit-twig-demo/dist/css/style.scss new file mode 100755 index 000000000..b749f20f7 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/css/style.scss @@ -0,0 +1,121 @@ +/* + ,, ,, ,, + .M"""bgd mm db `7MM mm mm `7MM OO OO OO +,MI "Y MM MM MM MM MM 88 88 88 +`MMb. mmMMmm ,pW"Wq.`7MMpdMAo. `7Mb,od8 `7MM .P"Ybmmm MMpMMMb.mmMMmm mmMMmm MMpMMMb. .gP"Ya `7Mb,od8 .gP"Ya || || || + `YMMNq. MM 6W' `Wb MM `Wb MM' "' MM :MI I8 MM MM MM MM MM MM ,M' Yb MM' "',M' Yb || || || +. `MM MM 8M M8 MM M8 MM MM WmmmP" MM MM MM MM MM MM 8M"""""" MM 8M"""""" `' `' `' +Mb dM MM YA. ,A9 MM ,AP MM MM 8M MM MM MM MM MM MM YM. , MM YM. , ,, ,, ,, +P"Ybmmd" `Mbmo`Ybmd9' MMbmmd' .JMML. .JMML.YMMMMMb .JMML JMML.`Mbmo `Mbmo.JMML JMML.`Mbmmd'.JMML. `Mbmmd' db db db + MM 6' dP + .JMML. Ybmmmd' + +Pattern Lab doesn't have any CSS requirements, which means you can write your styles however you want. Hooray! +You can use Sass, Less, vanilla CSS, or some other crazy thing I haven't heard of yet. +So please don't use these styles. They're just here to put together the demo, and nothing more. +They're intentionally gray, boring, and crappy because you're supposed to do this stuff yourself. + +Atomic design is philosophically complimentary with these CSS approaches: + +* SMACSS by Jonathan Snook http://smacss.com/ +* OOCSS by Nicole Sullivan http://oocss.org/ +* BEM CSS Methology : http://bem.info/method/ +* CSS Guidelines by Harry Roberts : https://github.com/csswizardry/CSS-Guidelines + +So feel free to use any of these approaches. Or don't. It's totally up to you. + +*/ + +/*------------------------------------*\ + $TABLE OF CONTENTS + based generally on Harry Roberts excellent CSS Guidelines https://github.com/csswizardry/CSS-Guidelines +\*------------------------------------*/ +/** + * VARIABLES..............................Declarations of Sass variables + * .....Colors + * .....Typography + * .....Layout + * .....Defaults + * .....Breakpoints + * MIXINS.................................Sass mixins + * RESET..................................Set reset defaults + * GLOBAL CLASSES.........................Set reset defaults + * GLOBAL ELEMENTS........................Establish global styles + * .....Main + * .....Headings + * .....Text-related elements (p, blockquote, lists) + * .....Defaults + * .....Breakpoints + * TYPOGRAPHY------------------------------ + * MEDIA------------------------------ + * LAYOUT------------------------------ + * NAVIGATION------------------------------ + * TOC To Be Continued + */ + + + +@import "scss/generic/variables"; +@import "scss/generic/mixins"; +@import "scss/generic/reset"; + + + + + +/*------------------------------------*\ + $GLOBAL ELEMENTS +\*------------------------------------*/ +@import "scss/base/global-classes"; +@import "scss/base/main"; +@import "scss/base/links"; +@import "scss/base/headings"; +@import "scss/base/text"; +@import "scss/base/lists"; +@import "scss/base/media"; +@import "scss/base/forms"; +@import "scss/base/tables"; +@import "scss/base/animation"; + + + + + +/*------------------------------------*\ + $LAYOUT +\*------------------------------------*/ +@import "scss/objects/layout"; + + +/*------------------------------------*\ + $PAGE STRUCTURE +\*------------------------------------*/ +@import "scss/objects/header"; +@import "scss/objects/nav"; +@import "scss/objects/main"; +@import "scss/objects/footer"; + + + +/*------------------------------------*\ + $TEXT Styles +\*------------------------------------*/ +@import "scss/objects/text"; + + +/*------------------------------------*\ + $COMPONENTS +\*------------------------------------*/ +@import "scss/objects/icons"; +@import "scss/objects/buttons"; +@import "scss/objects/blocks"; +@import "scss/objects/lists"; +@import "scss/objects/tooltip"; +@import "scss/objects/accordion"; +@import "scss/objects/tabs"; +@import "scss/objects/sections"; +@import "scss/objects/article"; +@import "scss/objects/comments"; +@import "scss/objects/messaging"; + + diff --git a/packages/starterkit-twig-demo/dist/favicon.ico b/packages/starterkit-twig-demo/dist/favicon.ico new file mode 100755 index 000000000..eee4aa78f Binary files /dev/null and b/packages/starterkit-twig-demo/dist/favicon.ico differ diff --git a/packages/starterkit-twig-demo/dist/fonts/icons.dev.svg b/packages/starterkit-twig-demo/dist/fonts/icons.dev.svg new file mode 100755 index 000000000..ca8e49e3a --- /dev/null +++ b/packages/starterkit-twig-demo/dist/fonts/icons.dev.svg @@ -0,0 +1,103 @@ + + + + +This is a custom SVG font generated by IcoMoon. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/fonts/icons.eot b/packages/starterkit-twig-demo/dist/fonts/icons.eot new file mode 100755 index 000000000..81a314b08 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/fonts/icons.eot differ diff --git a/packages/starterkit-twig-demo/dist/fonts/icons.svg b/packages/starterkit-twig-demo/dist/fonts/icons.svg new file mode 100755 index 000000000..3c64026b6 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/fonts/icons.svg @@ -0,0 +1,103 @@ + + + + +This is a custom SVG font generated by IcoMoon. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/starterkit-twig-demo/dist/fonts/icons.ttf b/packages/starterkit-twig-demo/dist/fonts/icons.ttf new file mode 100755 index 000000000..e6bff0814 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/fonts/icons.ttf differ diff --git a/packages/starterkit-twig-demo/dist/fonts/icons.woff b/packages/starterkit-twig-demo/dist/fonts/icons.woff new file mode 100755 index 000000000..10e663234 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/fonts/icons.woff differ diff --git a/packages/starterkit-twig-demo/dist/images/ajax-loader.gif b/packages/starterkit-twig-demo/dist/images/ajax-loader.gif new file mode 100755 index 000000000..f2a1bc0c6 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/ajax-loader.gif differ diff --git a/packages/starterkit-twig-demo/dist/images/favicon_16x16.jpg b/packages/starterkit-twig-demo/dist/images/favicon_16x16.jpg new file mode 100755 index 000000000..05e744965 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/favicon_16x16.jpg differ diff --git a/packages/starterkit-twig-demo/dist/images/favicon_32x32.jpg b/packages/starterkit-twig-demo/dist/images/favicon_32x32.jpg new file mode 100755 index 000000000..0970980b7 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/favicon_32x32.jpg differ diff --git a/packages/starterkit-twig-demo/dist/images/fpo_16x9.png b/packages/starterkit-twig-demo/dist/images/fpo_16x9.png new file mode 100755 index 000000000..013949dcc Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/fpo_16x9.png differ diff --git a/packages/starterkit-twig-demo/dist/images/fpo_4x3.png b/packages/starterkit-twig-demo/dist/images/fpo_4x3.png new file mode 100755 index 000000000..7ff84b81b Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/fpo_4x3.png differ diff --git a/packages/starterkit-twig-demo/dist/images/fpo_avatar.png b/packages/starterkit-twig-demo/dist/images/fpo_avatar.png new file mode 100755 index 000000000..e666a40ed Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/fpo_avatar.png differ diff --git a/packages/starterkit-twig-demo/dist/images/fpo_square.png b/packages/starterkit-twig-demo/dist/images/fpo_square.png new file mode 100755 index 000000000..ce8a1e4e6 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/fpo_square.png differ diff --git a/packages/starterkit-twig-demo/dist/images/logo.png b/packages/starterkit-twig-demo/dist/images/logo.png new file mode 100755 index 000000000..93db733d2 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/logo.png differ diff --git a/packages/starterkit-twig-demo/dist/images/sample/landscape-16x9-mountains.jpg b/packages/starterkit-twig-demo/dist/images/sample/landscape-16x9-mountains.jpg new file mode 100755 index 000000000..3c0d92b29 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/sample/landscape-16x9-mountains.jpg differ diff --git a/packages/starterkit-twig-demo/dist/images/sample/thumb-square-fire.jpg b/packages/starterkit-twig-demo/dist/images/sample/thumb-square-fire.jpg new file mode 100755 index 000000000..20a5c5ed6 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/sample/thumb-square-fire.jpg differ diff --git a/packages/starterkit-twig-demo/dist/images/sample/thumb-square-gear.jpg b/packages/starterkit-twig-demo/dist/images/sample/thumb-square-gear.jpg new file mode 100755 index 000000000..052a52a17 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/sample/thumb-square-gear.jpg differ diff --git a/packages/starterkit-twig-demo/dist/images/sample/thumb-square-ivy.jpg b/packages/starterkit-twig-demo/dist/images/sample/thumb-square-ivy.jpg new file mode 100755 index 000000000..64183a7a3 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/sample/thumb-square-ivy.jpg differ diff --git a/packages/starterkit-twig-demo/dist/images/sample/thumb-square-river.jpg b/packages/starterkit-twig-demo/dist/images/sample/thumb-square-river.jpg new file mode 100755 index 000000000..f19018689 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/sample/thumb-square-river.jpg differ diff --git a/packages/starterkit-twig-demo/dist/images/sample/thumb-square-yosemite.jpg b/packages/starterkit-twig-demo/dist/images/sample/thumb-square-yosemite.jpg new file mode 100755 index 000000000..8f388d798 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/sample/thumb-square-yosemite.jpg differ diff --git a/packages/starterkit-twig-demo/dist/images/sample/tout-4x3-climber.jpg b/packages/starterkit-twig-demo/dist/images/sample/tout-4x3-climber.jpg new file mode 100755 index 000000000..1348cd7e7 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/sample/tout-4x3-climber.jpg differ diff --git a/packages/starterkit-twig-demo/dist/images/sample/tout-4x3-climbers.jpg b/packages/starterkit-twig-demo/dist/images/sample/tout-4x3-climbers.jpg new file mode 100755 index 000000000..d8461a191 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/sample/tout-4x3-climbers.jpg differ diff --git a/packages/starterkit-twig-demo/dist/images/sample/tout-4x3-stream.jpg b/packages/starterkit-twig-demo/dist/images/sample/tout-4x3-stream.jpg new file mode 100755 index 000000000..8e091d1d0 Binary files /dev/null and b/packages/starterkit-twig-demo/dist/images/sample/tout-4x3-stream.jpg differ diff --git a/packages/starterkit-twig-demo/dist/js/fitvids.js b/packages/starterkit-twig-demo/dist/js/fitvids.js new file mode 100755 index 000000000..d3fa97f50 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/js/fitvids.js @@ -0,0 +1,91 @@ +/*global jQuery */ +/*! +* FitVids 1.0 +* +* Copyright 2011, Chris Coyier - http://css-tricks.com + Dave Rupert - http://daverupert.com +* Credit to Thierry Koblentz - http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/ +* Released under the WTFPL license - http://sam.zoy.org/wtfpl/ +* +* Date: Thu Sept 01 18:00:00 2011 -0500 +*/ + +(function($) { + $.fn.fitVids = function(options) { + var settings = { + customSelector: null, + }; + + var div = document.createElement('div'), + ref = + document.getElementsByTagName('base')[0] || + document.getElementsByTagName('script')[0]; + + div.className = 'fit-vids-style'; + div.innerHTML = + '­'; + + ref.parentNode.insertBefore(div, ref); + + if (options) { + $.extend(settings, options); + } + + return this.each(function() { + var selectors = [ + "iframe[src*='player.vimeo.com']", + "iframe[src*='www.youtube.com']", + "iframe[src*='www.kickstarter.com']", + 'object', + 'embed', + ]; + + if (settings.customSelector) { + selectors.push(settings.customSelector); + } + + var $allVideos = $(this).find(selectors.join(',')); + + $allVideos.each(function() { + var $this = $(this); + if ( + (this.tagName.toLowerCase() == 'embed' && + $this.parent('object').length) || + $this.parent('.fluid-width-video-wrapper').length + ) { + return; + } + var height = + this.tagName.toLowerCase() == 'object' || $this.attr('height') + ? $this.attr('height') + : $this.height(), + width = $this.attr('width') ? $this.attr('width') : $this.width(), + aspectRatio = height / width; + if (!$this.attr('id')) { + var videoID = 'fitvid' + Math.floor(Math.random() * 999999); + $this.attr('id', videoID); + } + $this + .wrap('
    ') + .parent('.fluid-width-video-wrapper') + .css('padding-top', aspectRatio * 100 + '%'); + $this.removeAttr('height').removeAttr('width'); + }); + }); + }; +})(jQuery); diff --git a/packages/starterkit-twig-demo/dist/js/init.js b/packages/starterkit-twig-demo/dist/js/init.js new file mode 100755 index 000000000..723a99732 --- /dev/null +++ b/packages/starterkit-twig-demo/dist/js/init.js @@ -0,0 +1,26 @@ +(function(w) { + var sw = document.body.clientWidth, + sh = document.body.clientHeight; + + $(w).resize(function() { + //Update dimensions on resize + sw = document.body.clientWidth; + sh = document.body.clientHeight; + + //updateAds(); + }); + + //Navigation toggle + $('.nav-toggle-menu').click(function(e) { + e.preventDefault(); + $(this).toggleClass('active'); + $('.nav').toggleClass('active'); + }); + + //Navigation toggle + $('.nav-toggle-search').click(function(e) { + e.preventDefault(); + $(this).toggleClass('active'); + $('.header .search-form').toggleClass('active'); + }); +})(this); diff --git a/packages/starterkit-twig-demo/dist/js/jquery-2.0.0b2.js b/packages/starterkit-twig-demo/dist/js/jquery-2.0.0b2.js new file mode 100755 index 000000000..c518c3bdc --- /dev/null +++ b/packages/starterkit-twig-demo/dist/js/jquery-2.0.0b2.js @@ -0,0 +1,9462 @@ +/*! + * jQuery JavaScript Library v2.0.0b2 + * http://jquery.com/ + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * + * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors + * Released under the MIT license + * http://jquery.org/license + * + * Date: 2013-3-1 + */ +(function(window, undefined) { + // Can't do this because several apps including ASP.NET trace + // the stack via arguments.caller.callee and Firefox dies if + // you try to trace through "use strict" call chains. (#13335) + // Support: Firefox 18+ + //"use strict"; + var // A central reference to the root jQuery(document) + rootjQuery, + // The deferred used on DOM ready + readyList, + // Support: IE9 + // For `typeof xmlNode.method` instead of `xmlNode.method !== undefined` + core_strundefined = typeof undefined, + // Use the correct document accordingly with window argument (sandbox) + location = window.location, + document = window.document, + docElem = document.documentElement, + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + // Map over the $ in case of overwrite + _$ = window.$, + // [[Class]] -> type pairs + class2type = {}, + // List of deleted data cache ids, so we can reuse them + core_deletedIds = [], + core_version = '2.0.0b2', + // Save a reference to some core methods + core_concat = core_deletedIds.concat, + core_push = core_deletedIds.push, + core_slice = core_deletedIds.slice, + core_indexOf = core_deletedIds.indexOf, + core_toString = class2type.toString, + core_hasOwn = class2type.hasOwnProperty, + core_trim = core_version.trim, + // Define a local copy of jQuery + jQuery = function(selector, context) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init(selector, context, rootjQuery); + }, + // Used for matching numbers + core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + // Used for splitting on whitespace + core_rnotwhite = /\S+/g, + // A simple way to check for HTML strings + // Prioritize #id over to avoid XSS via location.hash (#9521) + // Strict HTML recognition (#11290: must start with <) + rquickExpr = /^(?:(<[\w\W]+>)[^>]*|#([\w-]*))$/, + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + // Matches dashed string for camelizing + rmsPrefix = /^-ms-/, + rdashAlpha = /-([\da-z])/gi, + // Used by jQuery.camelCase as callback to replace() + fcamelCase = function(all, letter) { + return letter.toUpperCase(); + }, + // The ready event handler and self cleanup method + completed = function() { + document.removeEventListener('DOMContentLoaded', completed, false); + window.removeEventListener('load', completed, false); + jQuery.ready(); + }; + + jQuery.fn = jQuery.prototype = { + // The current version of jQuery being used + jquery: core_version, + + constructor: jQuery, + init: function(selector, context, rootjQuery) { + var match, elem; + + // HANDLE: $(""), $(null), $(undefined), $(false) + if (!selector) { + return this; + } + + // Handle HTML strings + if (typeof selector === 'string') { + if ( + selector.charAt(0) === '<' && + selector.charAt(selector.length - 1) === '>' && + selector.length >= 3 + ) { + // Assume that strings that start and end with <> are HTML and skip the regex check + match = [null, selector, null]; + } else { + match = rquickExpr.exec(selector); + } + + // Match html or make sure no context is specified for #id + if (match && (match[1] || !context)) { + // HANDLE: $(html) -> $(array) + if (match[1]) { + context = context instanceof jQuery ? context[0] : context; + + // scripts is true for back-compat + jQuery.merge( + this, + jQuery.parseHTML( + match[1], + context && context.nodeType + ? context.ownerDocument || context + : document, + true + ) + ); + + // HANDLE: $(html, props) + if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) { + for (match in context) { + // Properties of context are called as methods if possible + if (jQuery.isFunction(this[match])) { + this[match](context[match]); + + // ...and otherwise set as attributes + } else { + this.attr(match, context[match]); + } + } + } + + return this; + + // HANDLE: $(#id) + } else { + elem = document.getElementById(match[2]); + + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if (elem && elem.parentNode) { + // Handle the case where IE and Opera return items + // by name instead of ID + if (elem.id !== match[2]) { + return rootjQuery.find(selector); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $(expr, $(...)) + } else if (!context || context.jquery) { + return (context || rootjQuery).find(selector); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return this.constructor(context).find(selector); + } + + // HANDLE: $(DOMElement) + } else if (selector.nodeType) { + this.context = this[0] = selector; + this.length = 1; + return this; + + // HANDLE: $(function) + // Shortcut for document ready + } else if (jQuery.isFunction(selector)) { + return rootjQuery.ready(selector); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray(selector, this); + }, + + // Start with an empty selector + selector: '', + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return core_slice.call(this); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function(num) { + return num == null + ? // Return a 'clean' array + this.toArray() + : // Return just the object + num < 0 ? this[this.length + num] : this[num]; + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function(elems) { + // Build a new jQuery matched element set + var ret = jQuery.merge(this.constructor(), elems); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function(callback, args) { + return jQuery.each(this, callback, args); + }, + + ready: function(fn) { + // Add the callback + jQuery.ready.promise().done(fn); + + return this; + }, + + slice: function() { + return this.pushStack(core_slice.apply(this, arguments)); + }, + + first: function() { + return this.eq(0); + }, + + last: function() { + return this.eq(-1); + }, + + eq: function(i) { + var len = this.length, + j = +i + (i < 0 ? len : 0); + return this.pushStack(j >= 0 && j < len ? [this[j]] : []); + }, + + map: function(callback) { + return this.pushStack( + jQuery.map(this, function(elem, i) { + return callback.call(elem, i, elem); + }) + ); + }, + + end: function() { + return this.prevObject || this.constructor(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: core_push, + sort: [].sort, + splice: [].splice, + }; + + // Give the init function the jQuery prototype for later instantiation + jQuery.fn.init.prototype = jQuery.fn; + + jQuery.extend = jQuery.fn.extend = function() { + var options, + name, + src, + copy, + copyIsArray, + clone, + target = arguments[0] || {}, + i = 1, + length = arguments.length, + deep = false; + + // Handle a deep copy situation + if (typeof target === 'boolean') { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if (typeof target !== 'object' && !jQuery.isFunction(target)) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if (length === i) { + target = this; + --i; + } + + for (; i < length; i++) { + // Only deal with non-null/undefined values + if ((options = arguments[i]) != null) { + // Extend the base object + for (name in options) { + src = target[name]; + copy = options[name]; + + // Prevent never-ending loop + if (target === copy) { + continue; + } + + // Recurse if we're merging plain objects or arrays + if ( + deep && + copy && + (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy))) + ) { + if (copyIsArray) { + copyIsArray = false; + clone = src && jQuery.isArray(src) ? src : []; + } else { + clone = src && jQuery.isPlainObject(src) ? src : {}; + } + + // Never move original objects, clone them + target[name] = jQuery.extend(deep, clone, copy); + + // Don't bring in undefined values + } else if (copy !== undefined) { + target[name] = copy; + } + } + } + } + + // Return the modified object + return target; + }; + + jQuery.extend({ + // Unique for each copy of jQuery on the page + expando: 'jQuery' + (core_version + Math.random()).replace(/\D/g, ''), + + noConflict: function(deep) { + if (window.$ === jQuery) { + window.$ = _$; + } + + if (deep && window.jQuery === jQuery) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // A counter to track how many items to wait for before + // the ready event fires. See #6781 + readyWait: 1, + + // Hold (or release) the ready event + holdReady: function(hold) { + if (hold) { + jQuery.readyWait++; + } else { + jQuery.ready(true); + } + }, + + // Handle when the DOM is ready + ready: function(wait) { + // Abort if there are pending holds or we're already ready + if (wait === true ? --jQuery.readyWait : jQuery.isReady) { + return; + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If a normal DOM Ready event fired, decrement, and wait if need be + if (wait !== true && --jQuery.readyWait > 0) { + return; + } + + // If there are functions bound, to execute + readyList.resolveWith(document, [jQuery]); + + // Trigger any bound ready events + if (jQuery.fn.trigger) { + jQuery(document) + .trigger('ready') + .off('ready'); + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function(obj) { + return jQuery.type(obj) === 'function'; + }, + + isArray: Array.isArray, + + isWindow: function(obj) { + return obj != null && obj == obj.window; + }, + + isNumeric: function(obj) { + return !isNaN(parseFloat(obj)) && isFinite(obj); + }, + + type: function(obj) { + if (obj == null) { + return String(obj); + } + // Support: Safari <=5.1 (functionish RegExp) + return typeof obj === 'object' || typeof obj === 'function' + ? class2type[core_toString.call(obj)] || 'object' + : typeof obj; + }, + + isPlainObject: function(obj) { + // Not plain objects: + // - Any object or value whose internal [[Class]] property is not "[object Object]" + // - DOM nodes + // - window + if ( + jQuery.type(obj) !== 'object' || + obj.nodeType || + jQuery.isWindow(obj) + ) { + return false; + } + + // Support: Firefox >16 + // The try/catch suppresses exceptions thrown when attempting to access + // the "constructor" property of certain host objects, ie. |window.location| + try { + if ( + obj.constructor && + !core_hasOwn.call(obj.constructor.prototype, 'isPrototypeOf') + ) { + return false; + } + } catch (e) { + return false; + } + + // If the function hasn't returned already, we're confident that + // |obj| is a plain object, created by {} or constructed with new Object + return true; + }, + + isEmptyObject: function(obj) { + var name; + for (name in obj) { + return false; + } + return true; + }, + + error: function(msg) { + throw new Error(msg); + }, + + // data: string of html + // context (optional): If specified, the fragment will be created in this context, defaults to document + // keepScripts (optional): If true, will include scripts passed in the html string + parseHTML: function(data, context, keepScripts) { + if (!data || typeof data !== 'string') { + return null; + } + if (typeof context === 'boolean') { + keepScripts = context; + context = false; + } + context = context || document; + + var parsed = rsingleTag.exec(data), + scripts = !keepScripts && []; + + // Single tag + if (parsed) { + return [context.createElement(parsed[1])]; + } + + parsed = jQuery.buildFragment([data], context, scripts); + + if (scripts) { + jQuery(scripts).remove(); + } + + return jQuery.merge([], parsed.childNodes); + }, + + parseJSON: JSON.parse, + + // Cross-browser xml parsing + parseXML: function(data) { + var xml, tmp; + if (!data || typeof data !== 'string') { + return null; + } + + // Support: IE9 + try { + tmp = new DOMParser(); + xml = tmp.parseFromString(data, 'text/xml'); + } catch (e) { + xml = undefined; + } + + if (!xml || xml.getElementsByTagName('parsererror').length) { + jQuery.error('Invalid XML: ' + data); + } + return xml; + }, + + noop: function() {}, + + // Evaluates a script in a global context + globalEval: function(data) { + var indirect = eval; + if (jQuery.trim(data)) { + indirect(data + ';'); + } + }, + + // Convert dashed to camelCase; used by the css and data modules + // Microsoft forgot to hump their vendor prefix (#9572) + camelCase: function(string) { + return string.replace(rmsPrefix, 'ms-').replace(rdashAlpha, fcamelCase); + }, + + nodeName: function(elem, name) { + return ( + elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase() + ); + }, + + // args is for internal usage only + each: function(obj, callback, args) { + var value, + i = 0, + length = obj.length, + isArray = isArraylike(obj); + + if (args) { + if (isArray) { + for (; i < length; i++) { + value = callback.apply(obj[i], args); + + if (value === false) { + break; + } + } + } else { + for (i in obj) { + value = callback.apply(obj[i], args); + + if (value === false) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if (isArray) { + for (; i < length; i++) { + value = callback.call(obj[i], i, obj[i]); + + if (value === false) { + break; + } + } + } else { + for (i in obj) { + value = callback.call(obj[i], i, obj[i]); + + if (value === false) { + break; + } + } + } + } + + return obj; + }, + + trim: function(text) { + return text == null ? '' : core_trim.call(text); + }, + + // results is for internal usage only + makeArray: function(arr, results) { + var ret = results || []; + + if (arr != null) { + if (isArraylike(Object(arr))) { + jQuery.merge(ret, typeof arr === 'string' ? [arr] : arr); + } else { + core_push.call(ret, arr); + } + } + + return ret; + }, + + inArray: function(elem, arr, i) { + return arr == null ? -1 : core_indexOf.call(arr, elem, i); + }, + + merge: function(first, second) { + var l = second.length, + i = first.length, + j = 0; + + if (typeof l === 'number') { + for (; j < l; j++) { + first[i++] = second[j]; + } + } else { + while (second[j] !== undefined) { + first[i++] = second[j++]; + } + } + + first.length = i; + + return first; + }, + + grep: function(elems, callback, inv) { + var retVal, + ret = [], + i = 0, + length = elems.length; + inv = !!inv; + + // Go through the array, only saving the items + // that pass the validator function + for (; i < length; i++) { + retVal = !!callback(elems[i], i); + if (inv !== retVal) { + ret.push(elems[i]); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function(elems, callback, arg) { + var value, + i = 0, + length = elems.length, + isArray = isArraylike(elems), + ret = []; + + // Go through the array, translating each of the items to their + if (isArray) { + for (; i < length; i++) { + value = callback(elems[i], i, arg); + + if (value != null) { + ret[ret.length] = value; + } + } + + // Go through every key on the object, + } else { + for (i in elems) { + value = callback(elems[i], i, arg); + + if (value != null) { + ret[ret.length] = value; + } + } + } + + // Flatten any nested arrays + return core_concat.apply([], ret); + }, + + // A global GUID counter for objects + guid: 1, + + // Bind a function to a context, optionally partially applying any + // arguments. + proxy: function(fn, context) { + var tmp, args, proxy; + + if (typeof context === 'string') { + tmp = fn[context]; + context = fn; + fn = tmp; + } + + // Quick check to determine if target is callable, in the spec + // this throws a TypeError, but we will just return undefined. + if (!jQuery.isFunction(fn)) { + return undefined; + } + + // Simulated bind + args = core_slice.call(arguments, 2); + proxy = function() { + return fn.apply( + context || this, + args.concat(core_slice.call(arguments)) + ); + }; + + // Set the guid of unique handler to the same of original handler, so it can be removed + proxy.guid = fn.guid = fn.guid || jQuery.guid++; + + return proxy; + }, + + // Multifunctional method to get and set values of a collection + // The value/s can optionally be executed if it's a function + access: function(elems, fn, key, value, chainable, emptyGet, raw) { + var i = 0, + length = elems.length, + bulk = key == null; + + // Sets many values + if (jQuery.type(key) === 'object') { + chainable = true; + for (i in key) { + jQuery.access(elems, fn, i, key[i], true, emptyGet, raw); + } + + // Sets one value + } else if (value !== undefined) { + chainable = true; + + if (!jQuery.isFunction(value)) { + raw = true; + } + + if (bulk) { + // Bulk operations run against the entire set + if (raw) { + fn.call(elems, value); + fn = null; + + // ...except when executing function values + } else { + bulk = fn; + fn = function(elem, key, value) { + return bulk.call(jQuery(elem), value); + }; + } + } + + if (fn) { + for (; i < length; i++) { + fn( + elems[i], + key, + raw ? value : value.call(elems[i], i, fn(elems[i], key)) + ); + } + } + } + + return chainable + ? elems + : // Gets + bulk ? fn.call(elems) : length ? fn(elems[0], key) : emptyGet; + }, + + now: Date.now, + }); + + jQuery.ready.promise = function(obj) { + if (!readyList) { + readyList = jQuery.Deferred(); + + // Catch cases where $(document).ready() is called after the browser event has already occurred. + // we once tried to use readyState "interactive" here, but it caused issues like the one + // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 + if (document.readyState === 'complete') { + // Handle it asynchronously to allow scripts the opportunity to delay ready + setTimeout(jQuery.ready); + } else { + // Use the handy event callback + document.addEventListener('DOMContentLoaded', completed, false); + + // A fallback to window.onload, that will always work + window.addEventListener('load', completed, false); + } + } + return readyList.promise(obj); + }; + + // Populate the class2type map + jQuery.each( + 'Boolean Number String Function Array Date RegExp Object Error'.split(' '), + function(i, name) { + class2type['[object ' + name + ']'] = name.toLowerCase(); + } + ); + + function isArraylike(obj) { + var length = obj.length, + type = jQuery.type(obj); + + if (jQuery.isWindow(obj)) { + return false; + } + + if (obj.nodeType === 1 && length) { + return true; + } + + return ( + type === 'array' || + (type !== 'function' && + (length === 0 || + (typeof length === 'number' && length > 0 && length - 1 in obj))) + ); + } + + // All jQuery objects should point back to these + rootjQuery = jQuery(document); + // String to Object options format cache + var optionsCache = {}; + + // Convert String-formatted options into Object-formatted ones and store in cache + function createOptions(options) { + var object = (optionsCache[options] = {}); + jQuery.each(options.match(core_rnotwhite) || [], function(_, flag) { + object[flag] = true; + }); + return object; + } + + /* + * Create a callback list using the following parameters: + * + * options: an optional list of space-separated options that will change how + * the callback list behaves or a more traditional option object + * + * By default a callback list will act like an event callback list and can be + * "fired" multiple times. + * + * Possible options: + * + * once: will ensure the callback list can only be fired once (like a Deferred) + * + * memory: will keep track of previous values and will call any callback added + * after the list has been fired right away with the latest "memorized" + * values (like a Deferred) + * + * unique: will ensure a callback can only be added once (no duplicate in the list) + * + * stopOnFalse: interrupt callings when a callback returns false + * + */ + jQuery.Callbacks = function(options) { + // Convert options from String-formatted to Object-formatted if needed + // (we check in cache first) + options = + typeof options === 'string' + ? optionsCache[options] || createOptions(options) + : jQuery.extend({}, options); + + var // Last fire value (for non-forgettable lists) + memory, + // Flag to know if list was already fired + fired, + // Flag to know if list is currently firing + firing, + // First callback to fire (used internally by add and fireWith) + firingStart, + // End of the loop when firing + firingLength, + // Index of currently firing callback (modified by remove if needed) + firingIndex, + // Actual callback list + list = [], + // Stack of fire calls for repeatable lists + stack = !options.once && [], + // Fire callbacks + fire = function(data) { + memory = options.memory && data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + for (; list && firingIndex < firingLength; firingIndex++) { + if ( + list[firingIndex].apply(data[0], data[1]) === false && + options.stopOnFalse + ) { + memory = false; // To prevent further calls using add + break; + } + } + firing = false; + if (list) { + if (stack) { + if (stack.length) { + fire(stack.shift()); + } + } else if (memory) { + list = []; + } else { + self.disable(); + } + } + }, + // Actual Callbacks object + self = { + // Add a callback or a collection of callbacks to the list + add: function() { + if (list) { + // First, we save the current length + var start = list.length; + (function add(args) { + jQuery.each(args, function(_, arg) { + var type = jQuery.type(arg); + if (type === 'function') { + if (!options.unique || !self.has(arg)) { + list.push(arg); + } + } else if (arg && arg.length && type !== 'string') { + // Inspect recursively + add(arg); + } + }); + })(arguments); + // Do we need to add the callbacks to the + // current firing batch? + if (firing) { + firingLength = list.length; + // With memory, if we're not firing then + // we should call right away + } else if (memory) { + firingStart = start; + fire(memory); + } + } + return this; + }, + // Remove a callback from the list + remove: function() { + if (list) { + jQuery.each(arguments, function(_, arg) { + var index; + while ((index = jQuery.inArray(arg, list, index)) > -1) { + list.splice(index, 1); + // Handle firing indexes + if (firing) { + if (index <= firingLength) { + firingLength--; + } + if (index <= firingIndex) { + firingIndex--; + } + } + } + }); + } + return this; + }, + // Check if a given callback is in the list. + // If no argument is given, return whether or not list has callbacks attached. + has: function(fn) { + return fn ? jQuery.inArray(fn, list) > -1 : !!(list && list.length); + }, + // Remove all callbacks from the list + empty: function() { + list = []; + firingLength = 0; + return this; + }, + // Have the list do nothing anymore + disable: function() { + list = stack = memory = undefined; + return this; + }, + // Is it disabled? + disabled: function() { + return !list; + }, + // Lock the list in its current state + lock: function() { + stack = undefined; + if (!memory) { + self.disable(); + } + return this; + }, + // Is it locked? + locked: function() { + return !stack; + }, + // Call all callbacks with the given context and arguments + fireWith: function(context, args) { + args = args || []; + args = [context, args.slice ? args.slice() : args]; + if (list && (!fired || stack)) { + if (firing) { + stack.push(args); + } else { + fire(args); + } + } + return this; + }, + // Call all the callbacks with the given arguments + fire: function() { + self.fireWith(this, arguments); + return this; + }, + // To know if the callbacks have already been called at least once + fired: function() { + return !!fired; + }, + }; + + return self; + }; + jQuery.extend({ + Deferred: function(func) { + var tuples = [ + // action, add listener, listener list, final state + ['resolve', 'done', jQuery.Callbacks('once memory'), 'resolved'], + ['reject', 'fail', jQuery.Callbacks('once memory'), 'rejected'], + ['notify', 'progress', jQuery.Callbacks('memory')], + ], + state = 'pending', + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done(arguments).fail(arguments); + return this; + }, + then: function(/* fnDone, fnFail, fnProgress */) { + var fns = arguments; + return jQuery + .Deferred(function(newDefer) { + jQuery.each(tuples, function(i, tuple) { + var action = tuple[0], + fn = jQuery.isFunction(fns[i]) && fns[i]; + // deferred[ done | fail | progress ] for forwarding actions to newDefer + deferred[tuple[1]](function() { + var returned = fn && fn.apply(this, arguments); + if (returned && jQuery.isFunction(returned.promise)) { + returned + .promise() + .done(newDefer.resolve) + .fail(newDefer.reject) + .progress(newDefer.notify); + } else { + newDefer[action + 'With']( + this === promise ? newDefer.promise() : this, + fn ? [returned] : arguments + ); + } + }); + }); + fns = null; + }) + .promise(); + }, + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function(obj) { + return obj != null ? jQuery.extend(obj, promise) : promise; + }, + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + jQuery.each(tuples, function(i, tuple) { + var list = tuple[2], + stateString = tuple[3]; + + // promise[ done | fail | progress ] = list.add + promise[tuple[1]] = list.add; + + // Handle state + if (stateString) { + list.add( + function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, + tuples[i ^ 1][2].disable, + tuples[2][2].lock + ); + } + + // deferred[ resolve | reject | notify ] + deferred[tuple[0]] = function() { + deferred[tuple[0] + 'With']( + this === deferred ? promise : this, + arguments + ); + return this; + }; + deferred[tuple[0] + 'With'] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise(deferred); + + // Call given func if any + if (func) { + func.call(deferred, deferred); + } + + // All done! + return deferred; + }, + + // Deferred helper + when: function(subordinate /* , ..., subordinateN */) { + var i = 0, + resolveValues = core_slice.call(arguments), + length = resolveValues.length, + // the count of uncompleted subordinates + remaining = + length !== 1 || + (subordinate && jQuery.isFunction(subordinate.promise)) + ? length + : 0, + // the master Deferred. If resolveValues consist of only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : jQuery.Deferred(), + // Update function for both resolve and progress values + updateFunc = function(i, contexts, values) { + return function(value) { + contexts[i] = this; + values[i] = + arguments.length > 1 ? core_slice.call(arguments) : value; + if (values === progressValues) { + deferred.notifyWith(contexts, values); + } else if (!--remaining) { + deferred.resolveWith(contexts, values); + } + }; + }, + progressValues, + progressContexts, + resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if (length > 1) { + progressValues = new Array(length); + progressContexts = new Array(length); + resolveContexts = new Array(length); + for (; i < length; i++) { + if (resolveValues[i] && jQuery.isFunction(resolveValues[i].promise)) { + resolveValues[i] + .promise() + .done(updateFunc(i, resolveContexts, resolveValues)) + .fail(deferred.reject) + .progress(updateFunc(i, progressContexts, progressValues)); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if (!remaining) { + deferred.resolveWith(resolveContexts, resolveValues); + } + + return deferred.promise(); + }, + }); + jQuery.support = (function(support) { + var input = document.createElement('input'), + fragment = document.createDocumentFragment(), + div = document.createElement('div'), + select = document.createElement('select'), + opt = select.appendChild(document.createElement('option')); + + // Finish early in limited environments + if (!input.type) { + return support; + } + + input.type = 'checkbox'; + + // Check the default checkbox/radio value ("" on old WebKit; "on" elsewhere) + support.checkOn = input.value === ''; + + // Must access the parent to make an option select properly + // Support: IE9, IE10 + support.optSelected = opt.selected; + + // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode + support.boxModel = document.compatMode === 'CSS1Compat'; + + // Will be defined later + support.reliableMarginRight = true; + support.boxSizingReliable = true; + support.pixelPosition = false; + + // Make sure checked status is properly cloned + // Support: IE9, IE10 + input.checked = true; + support.noCloneChecked = input.cloneNode(true).checked; + + // Make sure that the options inside disabled selects aren't marked as disabled + // (WebKit marks them as disabled) + select.disabled = true; + support.optDisabled = !opt.disabled; + + // Check if an input maintains its value after becoming a radio + // Support: IE9, IE10, Opera + input = document.createElement('input'); + input.value = 't'; + input.type = 'radio'; + support.radioValue = input.value === 't'; + + // #11217 - WebKit loses check when the name is after the checked attribute + input.setAttribute('checked', 't'); + input.setAttribute('name', 't'); + + fragment.appendChild(input); + + // old WebKit doesn't clone checked state correctly in fragments + support.checkClone = fragment + .cloneNode(true) + .cloneNode(true).lastChild.checked; + + // Support: Firefox 17+ + // Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP) + support.focusinBubbles = 'onfocusin' in window; + + div.style.backgroundClip = 'content-box'; + div.cloneNode(true).style.backgroundClip = ''; + support.clearCloneStyle = div.style.backgroundClip === 'content-box'; + + // Run tests that need a body at doc ready + jQuery(function() { + var container, + marginDiv, + divReset = + 'padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;', + body = document.getElementsByTagName('body')[0]; + + if (!body) { + // Return for frameset docs that don't have a body + return; + } + + container = document.createElement('div'); + container.style.cssText = + 'border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px'; + + // Check box-sizing and margin behavior + body.appendChild(container).appendChild(div); + div.innerHTML = ''; + div.style.cssText = + 'box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;'; + + support.boxSizing = div.offsetWidth === 4; + support.doesNotIncludeMarginInBodyOffset = body.offsetTop !== 1; + + // Use window.getComputedStyle because jsdom on node.js will break without it. + if (window.getComputedStyle) { + support.pixelPosition = + (window.getComputedStyle(div, null) || {}).top !== '1%'; + support.boxSizingReliable = + (window.getComputedStyle(div, null) || { width: '4px' }).width === + '4px'; + + // Check if div with explicit width and no margin-right incorrectly + // gets computed margin-right based on width of container. (#3333) + // Fails in WebKit before Feb 2011 nightlies + // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right + marginDiv = div.appendChild(document.createElement('div')); + marginDiv.style.cssText = div.style.cssText = divReset; + marginDiv.style.marginRight = marginDiv.style.width = '0'; + div.style.width = '1px'; + + support.reliableMarginRight = !parseFloat( + (window.getComputedStyle(marginDiv, null) || {}).marginRight + ); + } + + body.removeChild(container); + }); + + return support; + })({}); + + /* + Implementation Summary + + 1. Enforce API surface and semantic compatibility with 1.9.x branch + 2. Improve the module's maintainability by reducing the storage + paths to a single mechanism. + 3. Use the same single mechanism to support "private" and "user" data. + 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) + 5. Avoid exposing implementation details on user objects (eg. expando properties) + 6. Provide a clear path for implementation upgrade to WeakMap in 2014 +*/ + var data_user, + data_priv, + rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/, + rmultiDash = /([A-Z])/g; + + function Data() { + this.cache = {}; + this.expando = jQuery.expando + Math.random(); + } + + Data.uid = 1; + + Data.prototype = { + key: function(owner) { + var descriptor = {}, + // Check if the owner object already has a cache key + unlock = owner[this.expando]; + + // If not, create one + if (!unlock) { + unlock = Data.uid++; + descriptor[this.expando] = { value: unlock }; + + // Secure it in a non-enumerable, non-writable property + try { + Object.defineProperties(owner, descriptor); + + // Support: Android<4 + // Fallback to a less secure definition + } catch (e) { + descriptor[this.expando] = unlock; + jQuery.extend(owner, descriptor); + } + } + + // Ensure the cache object + if (!this.cache[unlock]) { + this.cache[unlock] = {}; + } + + return unlock; + }, + set: function(owner, data, value) { + var prop, + // There may be an unlock assigned to this node, + // if there is no entry for this "owner", create one inline + // and set the unlock as though an owner entry had always existed + unlock = this.key(owner), + cache = this.cache[unlock]; + + // Handle: [ owner, key, value ] args + if (typeof data === 'string') { + cache[data] = value; + + // Handle: [ owner, { properties } ] args + } else { + // Support an expectation from the old data system where plain + // objects used to initialize would be set to the cache by + // reference, instead of having properties and values copied. + // Note, this will kill the connection between + // "this.cache[ unlock ]" and "cache" + if (jQuery.isEmptyObject(cache)) { + this.cache[unlock] = data; + // Otherwise, copy the properties one-by-one to the cache object + } else { + for (prop in data) { + cache[prop] = data[prop]; + } + } + } + + return this; + }, + get: function(owner, key) { + // Either a valid cache is found, or will be created. + // New caches will be created and the unlock returned, + // allowing direct access to the newly created + // empty data object. + var cache = this.cache[this.key(owner)]; + + return key === undefined ? cache : cache[key]; + }, + access: function(owner, key, value) { + // In cases where either: + // + // 1. No key was specified + // 2. A string key was specified, but no value provided + // + // Take the "read" path and allow the get method to determine + // which value to return, respectively either: + // + // 1. The entire cache object + // 2. The data stored at the key + // + if ( + key === undefined || + (key && typeof key === 'string' && value === undefined) + ) { + return this.get(owner, key); + } + + // [*]When the key is not a string, or both a key and value + // are specified, set or extend (existing objects) with either: + // + // 1. An object of properties + // 2. A key and value + // + this.set(owner, key, value); + + // Since the "set" path can have two possible entry points + // return the expected data based on which path was taken[*] + return value !== undefined ? value : key; + }, + remove: function(owner, key) { + var i, + name, + unlock = this.key(owner), + cache = this.cache[unlock]; + + if (key === undefined) { + this.cache[unlock] = {}; + } else { + // Support array or space separated string of keys + if (jQuery.isArray(key)) { + // If "name" is an array of keys... + // When data is initially created, via ("key", "val") signature, + // keys will be converted to camelCase. + // Since there is no way to tell _how_ a key was added, remove + // both plain key and camelCase key. #12786 + // This will only penalize the array argument path. + name = key.concat(key.map(jQuery.camelCase)); + } else { + // Try the string as a key before any manipulation + if (key in cache) { + name = [key]; + } else { + // If a key with the spaces exists, use it. + // Otherwise, create an array by matching non-whitespace + name = jQuery.camelCase(key); + name = name in cache ? [name] : name.match(core_rnotwhite) || []; + } + } + + i = name.length; + while (i--) { + delete cache[name[i]]; + } + } + }, + hasData: function(owner) { + return !jQuery.isEmptyObject(this.cache[this.key(owner)]); + }, + discard: function(owner) { + delete this.cache[this.key(owner)]; + }, + }; + + // This will be used by remove()/cleanData() in manipulation to sever + // remaining references to node objects. One day we'll replace the dual + // arrays with a WeakMap and this won't be an issue. + // (Splices the data objects out of the internal cache arrays) + function data_discard(owner) { + data_user.discard(owner); + data_priv.discard(owner); + } + + // These may be used throughout the jQuery core codebase + data_user = new Data(); + data_priv = new Data(); + + jQuery.extend({ + // This is no longer relevant to jQuery core, but must remain + // supported for the sake of jQuery 1.9.x API surface compatibility. + acceptData: function() { + return true; + }, + + hasData: function(elem) { + return data_user.hasData(elem) || data_priv.hasData(elem); + }, + + data: function(elem, name, data) { + return data_user.access(elem, name, data); + }, + + removeData: function(elem, name) { + return data_user.remove(elem, name); + }, + + // TODO: Replace all calls to _data and _removeData with direct + // calls to + // + // data_priv.access( elem, name, data ); + // + // data_priv.remove( elem, name ); + // + _data: function(elem, name, data) { + return data_priv.access(elem, name, data); + }, + + _removeData: function(elem, name) { + return data_priv.remove(elem, name); + }, + }); + + jQuery.fn.extend({ + data: function(key, value) { + var attrs, + name, + elem = this[0], + i = 0, + data = null; + + // Gets all values + if (key === undefined) { + if (this.length) { + data = data_user.get(elem); + + if (elem.nodeType === 1 && !data_priv.get(elem, 'hasDataAttrs')) { + attrs = elem.attributes; + for (; i < attrs.length; i++) { + name = attrs[i].name; + + if (name.indexOf('data-') === 0) { + name = jQuery.camelCase(name.substring(5)); + dataAttr(elem, name, data[name]); + } + } + data_priv.set(elem, 'hasDataAttrs', true); + } + } + + return data; + } + + // Sets multiple values + if (typeof key === 'object') { + return this.each(function() { + data_user.set(this, key); + }); + } + + return jQuery.access( + this, + function(value) { + var data, + camelKey = jQuery.camelCase(key); + + // Get the Data... + if (value === undefined) { + // Attempt to get data from the cache + // with the key as-is + data = data_user.get(elem, key); + if (data !== undefined) { + return data; + } + // Attempt to "discover" the data in + // HTML5 custom data-* attrs + data = dataAttr(elem, key, undefined); + if (data !== undefined) { + return data; + } + + // As a last resort, attempt to find + // the data by checking AGAIN, but with + // a camelCased key. + data = data_user.get(elem, camelKey); + if (data !== undefined) { + return data; + } + + // We tried really hard, but the data doesn't exist. + return undefined; + } + + // Set the data... + this.each(function() { + // First, attempt to store a copy or reference of any + // data that might've been store with a camelCased key. + var data = data_user.get(this, camelKey); + + // For HTML5 data-* attribute interop, we have to + // store property names with dashes in a camelCase form. + // This might not apply to all properties...* + data_user.set(this, camelKey, value); + + // *... In the case of properties that might ACTUALLY + // have dashes, we need to also store a copy of that + // unchanged property. + if (/-/.test(key) && data !== undefined) { + data_user.set(this, key, value); + } + }); + }, + null, + value, + arguments.length > 1, + null, + true + ); + }, + + removeData: function(key) { + return this.each(function() { + data_user.remove(this, key); + }); + }, + }); + + function dataAttr(elem, key, data) { + var name; + + // If nothing was found internally, try to fetch any + // data from the HTML5 data-* attribute + if (data === undefined && elem.nodeType === 1) { + name = 'data-' + key.replace(rmultiDash, '-$1').toLowerCase(); + data = elem.getAttribute(name); + + if (typeof data === 'string') { + try { + data = + data === 'true' + ? true + : data === 'false' + ? false + : data === 'null' + ? null + : // Only convert to a number if it doesn't change the string + +data + '' === data + ? +data + : rbrace.test(data) ? JSON.parse(data) : data; + } catch (e) {} + + // Make sure we set the data so it isn't changed later + data_user.set(elem, key, data); + } else { + data = undefined; + } + } + return data; + } + jQuery.extend({ + queue: function(elem, type, data) { + var queue; + + if (elem) { + type = (type || 'fx') + 'queue'; + queue = jQuery._data(elem, type); + + // Speed up dequeue by getting out quickly if this is just a lookup + if (data) { + if (!queue || jQuery.isArray(data)) { + queue = jQuery._data(elem, type, jQuery.makeArray(data)); + } else { + queue.push(data); + } + } + return queue || []; + } + }, + + dequeue: function(elem, type) { + type = type || 'fx'; + + var queue = jQuery.queue(elem, type), + startLength = queue.length, + fn = queue.shift(), + hooks = jQuery._queueHooks(elem, type), + next = function() { + jQuery.dequeue(elem, type); + }; + + // If the fx queue is dequeued, always remove the progress sentinel + if (fn === 'inprogress') { + fn = queue.shift(); + startLength--; + } + + hooks.cur = fn; + if (fn) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if (type === 'fx') { + queue.unshift('inprogress'); + } + + // clear up the last queue stop function + delete hooks.stop; + fn.call(elem, next, hooks); + } + + if (!startLength && hooks) { + hooks.empty.fire(); + } + }, + + // not intended for public consumption - generates a queueHooks object, or returns the current one + _queueHooks: function(elem, type) { + var key = type + 'queueHooks'; + return ( + jQuery._data(elem, key) || + jQuery._data(elem, key, { + empty: jQuery.Callbacks('once memory').add(function() { + jQuery._removeData(elem, type + 'queue'); + jQuery._removeData(elem, key); + }), + }) + ); + }, + }); + + jQuery.fn.extend({ + queue: function(type, data) { + var setter = 2; + + if (typeof type !== 'string') { + data = type; + type = 'fx'; + setter--; + } + + if (arguments.length < setter) { + return jQuery.queue(this[0], type); + } + + return data === undefined + ? this + : this.each(function() { + var queue = jQuery.queue(this, type, data); + + // ensure a hooks for this queue + jQuery._queueHooks(this, type); + + if (type === 'fx' && queue[0] !== 'inprogress') { + jQuery.dequeue(this, type); + } + }); + }, + dequeue: function(type) { + return this.each(function() { + jQuery.dequeue(this, type); + }); + }, + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function(time, type) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || 'fx'; + + return this.queue(type, function(next, hooks) { + var timeout = setTimeout(next, time); + hooks.stop = function() { + clearTimeout(timeout); + }; + }); + }, + clearQueue: function(type) { + return this.queue(type || 'fx', []); + }, + // Get a promise resolved when queues of a certain type + // are emptied (fx is the type by default) + promise: function(type, obj) { + var tmp, + count = 1, + defer = jQuery.Deferred(), + elements = this, + i = this.length, + resolve = function() { + if (!--count) { + defer.resolveWith(elements, [elements]); + } + }; + + if (typeof type !== 'string') { + obj = type; + type = undefined; + } + type = type || 'fx'; + + while (i--) { + tmp = jQuery._data(elements[i], type + 'queueHooks'); + if (tmp && tmp.empty) { + count++; + tmp.empty.add(resolve); + } + } + resolve(); + return defer.promise(obj); + }, + }); + var nodeHook, + boolHook, + rclass = /[\t\r\n]/g, + rreturn = /\r/g, + rfocusable = /^(?:input|select|textarea|button|object)$/i, + rclickable = /^(?:a|area)$/i, + rboolean = /^(?:checked|selected|autofocus|autoplay|async|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped)$/i; + + jQuery.fn.extend({ + attr: function(name, value) { + return jQuery.access( + this, + jQuery.attr, + name, + value, + arguments.length > 1 + ); + }, + + removeAttr: function(name) { + return this.each(function() { + jQuery.removeAttr(this, name); + }); + }, + + prop: function(name, value) { + return jQuery.access( + this, + jQuery.prop, + name, + value, + arguments.length > 1 + ); + }, + + removeProp: function(name) { + name = jQuery.propFix[name] || name; + return this.each(function() { + // try/catch handles cases where IE balks (such as removing a property on window) + try { + this[name] = undefined; + delete this[name]; + } catch (e) {} + }); + }, + + addClass: function(value) { + var classes, + elem, + cur, + clazz, + j, + i = 0, + len = this.length, + proceed = typeof value === 'string' && value; + + if (jQuery.isFunction(value)) { + return this.each(function(j) { + jQuery(this).addClass(value.call(this, j, this.className)); + }); + } + + if (proceed) { + // The disjunction here is for better compressibility (see removeClass) + classes = (value || '').match(core_rnotwhite) || []; + + for (; i < len; i++) { + elem = this[i]; + cur = + elem.nodeType === 1 && + (elem.className + ? (' ' + elem.className + ' ').replace(rclass, ' ') + : ' '); + + if (cur) { + j = 0; + while ((clazz = classes[j++])) { + if (cur.indexOf(' ' + clazz + ' ') < 0) { + cur += clazz + ' '; + } + } + elem.className = jQuery.trim(cur); + } + } + } + + return this; + }, + + removeClass: function(value) { + var classes, + elem, + cur, + clazz, + j, + i = 0, + len = this.length, + proceed = + arguments.length === 0 || (typeof value === 'string' && value); + + if (jQuery.isFunction(value)) { + return this.each(function(j) { + jQuery(this).removeClass(value.call(this, j, this.className)); + }); + } + if (proceed) { + classes = (value || '').match(core_rnotwhite) || []; + + for (; i < len; i++) { + elem = this[i]; + // This expression is here for better compressibility (see addClass) + cur = + elem.nodeType === 1 && + (elem.className + ? (' ' + elem.className + ' ').replace(rclass, ' ') + : ''); + + if (cur) { + j = 0; + while ((clazz = classes[j++])) { + // Remove *all* instances + while (cur.indexOf(' ' + clazz + ' ') >= 0) { + cur = cur.replace(' ' + clazz + ' ', ' '); + } + } + elem.className = value ? jQuery.trim(cur) : ''; + } + } + } + + return this; + }, + + toggleClass: function(value, stateVal) { + var type = typeof value, + isBool = typeof stateVal === 'boolean'; + + if (jQuery.isFunction(value)) { + return this.each(function(i) { + jQuery(this).toggleClass( + value.call(this, i, this.className, stateVal), + stateVal + ); + }); + } + + return this.each(function() { + if (type === 'string') { + // toggle individual class names + var className, + i = 0, + self = jQuery(this), + state = stateVal, + classNames = value.match(core_rnotwhite) || []; + + while ((className = classNames[i++])) { + // check each className given, space separated list + state = isBool ? state : !self.hasClass(className); + self[state ? 'addClass' : 'removeClass'](className); + } + + // Toggle whole class name + } else if (type === core_strundefined || type === 'boolean') { + if (this.className) { + // store className if set + jQuery._data(this, '__className__', this.className); + } + + // If the element has a class name or if we're passed "false", + // then remove the whole classname (if there was one, the above saved it). + // Otherwise bring back whatever was previously saved (if anything), + // falling back to the empty string if nothing was stored. + this.className = + this.className || value === false + ? '' + : jQuery._data(this, '__className__') || ''; + } + }); + }, + + hasClass: function(selector) { + var className = ' ' + selector + ' ', + i = 0, + l = this.length; + for (; i < l; i++) { + if ( + this[i].nodeType === 1 && + (' ' + this[i].className + ' ') + .replace(rclass, ' ') + .indexOf(className) >= 0 + ) { + return true; + } + } + + return false; + }, + + val: function(value) { + var hooks, + ret, + isFunction, + elem = this[0]; + + if (!arguments.length) { + if (elem) { + hooks = + jQuery.valHooks[elem.type] || + jQuery.valHooks[elem.nodeName.toLowerCase()]; + + if ( + hooks && + 'get' in hooks && + (ret = hooks.get(elem, 'value')) !== undefined + ) { + return ret; + } + + ret = elem.value; + + return typeof ret === 'string' + ? // handle most common string cases + ret.replace(rreturn, '') + : // handle cases where value is null/undef or number + ret == null ? '' : ret; + } + + return; + } + + isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var val, + self = jQuery(this); + + if (this.nodeType !== 1) { + return; + } + + if (isFunction) { + val = value.call(this, i, self.val()); + } else { + val = value; + } + + // Treat null/undefined as ""; convert numbers to string + if (val == null) { + val = ''; + } else if (typeof val === 'number') { + val += ''; + } else if (jQuery.isArray(val)) { + val = jQuery.map(val, function(value) { + return value == null ? '' : value + ''; + }); + } + + hooks = + jQuery.valHooks[this.type] || + jQuery.valHooks[this.nodeName.toLowerCase()]; + + // If set returns undefined, fall back to normal setting + if ( + !hooks || + !('set' in hooks) || + hooks.set(this, val, 'value') === undefined + ) { + this.value = val; + } + }); + }, + }); + + jQuery.extend({ + valHooks: { + option: { + get: function(elem) { + // attributes.value is undefined in Blackberry 4.7 but + // uses .value. See #6932 + var val = elem.attributes.value; + return !val || val.specified ? elem.value : elem.text; + }, + }, + select: { + get: function(elem) { + var value, + option, + options = elem.options, + index = elem.selectedIndex, + one = elem.type === 'select-one' || index < 0, + values = one ? null : [], + max = one ? index + 1 : options.length, + i = index < 0 ? max : one ? index : 0; + + // Loop through all the selected options + for (; i < max; i++) { + option = options[i]; + + // oldIE doesn't update selected after form reset (#2551) + if ( + (option.selected || i === index) && + // Don't return options that are disabled or in a disabled optgroup + (jQuery.support.optDisabled + ? !option.disabled + : option.getAttribute('disabled') === null) && + (!option.parentNode.disabled || + !jQuery.nodeName(option.parentNode, 'optgroup')) + ) { + // Get the specific value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if (one) { + return value; + } + + // Multi-Selects return an array + values.push(value); + } + } + + return values; + }, + + set: function(elem, value) { + var values = jQuery.makeArray(value); + + jQuery(elem) + .find('option') + .each(function() { + this.selected = jQuery.inArray(jQuery(this).val(), values) >= 0; + }); + + if (!values.length) { + elem.selectedIndex = -1; + } + return values; + }, + }, + }, + + attr: function(elem, name, value) { + var ret, + hooks, + notxml, + nType = elem.nodeType; + + // don't get/set attributes on text, comment and attribute nodes + if (!elem || nType === 3 || nType === 8 || nType === 2) { + return; + } + + // Fallback to prop when attributes are not supported + if (typeof elem.getAttribute === core_strundefined) { + return jQuery.prop(elem, name, value); + } + + notxml = nType !== 1 || !jQuery.isXMLDoc(elem); + + // All attributes are lowercase + // Grab necessary hook if one is defined + if (notxml) { + name = name.toLowerCase(); + hooks = + jQuery.attrHooks[name] || (rboolean.test(name) ? boolHook : nodeHook); + } + + if (value !== undefined) { + if (value === null) { + jQuery.removeAttr(elem, name); + } else if ( + hooks && + notxml && + 'set' in hooks && + (ret = hooks.set(elem, value, name)) !== undefined + ) { + return ret; + } else { + elem.setAttribute(name, value + ''); + return value; + } + } else if ( + hooks && + notxml && + 'get' in hooks && + (ret = hooks.get(elem, name)) !== null + ) { + return ret; + } else { + // In IE9+, Flash objects don't have .getAttribute (#12945) + // Support: IE9+ + if (typeof elem.getAttribute !== core_strundefined) { + ret = elem.getAttribute(name); + } + + // Non-existent attributes return null, we normalize to undefined + return ret == null ? undefined : ret; + } + }, + + removeAttr: function(elem, value) { + var name, + propName, + i = 0, + attrNames = value && value.match(core_rnotwhite); + + if (attrNames && elem.nodeType === 1) { + while ((name = attrNames[i++])) { + propName = jQuery.propFix[name] || name; + + // Boolean attributes get special treatment (#10870) + // Set corresponding property to false for boolean attributes + if (rboolean.test(name)) { + elem[propName] = false; + } + + elem.removeAttribute(name); + } + } + }, + + attrHooks: { + type: { + set: function(elem, value) { + if ( + !jQuery.support.radioValue && + value === 'radio' && + jQuery.nodeName(elem, 'input') + ) { + // Setting the type on a radio button after the value resets the value in IE6-9 + // Reset value to default in case type is set after value during creation + var val = elem.value; + elem.setAttribute('type', value); + if (val) { + elem.value = val; + } + return value; + } + }, + }, + }, + + propFix: { + tabindex: 'tabIndex', + readonly: 'readOnly', + for: 'htmlFor', + class: 'className', + maxlength: 'maxLength', + cellspacing: 'cellSpacing', + cellpadding: 'cellPadding', + rowspan: 'rowSpan', + colspan: 'colSpan', + usemap: 'useMap', + frameborder: 'frameBorder', + contenteditable: 'contentEditable', + }, + + prop: function(elem, name, value) { + var ret, + hooks, + notxml, + nType = elem.nodeType; + + // don't get/set properties on text, comment and attribute nodes + if (!elem || nType === 3 || nType === 8 || nType === 2) { + return; + } + + notxml = nType !== 1 || !jQuery.isXMLDoc(elem); + + if (notxml) { + // Fix name and attach hooks + name = jQuery.propFix[name] || name; + hooks = jQuery.propHooks[name]; + } + + if (value !== undefined) { + if ( + hooks && + 'set' in hooks && + (ret = hooks.set(elem, value, name)) !== undefined + ) { + return ret; + } else { + return (elem[name] = value); + } + } else { + if (hooks && 'get' in hooks && (ret = hooks.get(elem, name)) !== null) { + return ret; + } else { + return elem[name]; + } + } + }, + + propHooks: { + tabIndex: { + get: function(elem) { + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + var attributeNode = elem.getAttributeNode('tabindex'); + + return attributeNode && attributeNode.specified + ? parseInt(attributeNode.value, 10) + : rfocusable.test(elem.nodeName) || + (rclickable.test(elem.nodeName) && elem.href) + ? 0 + : undefined; + }, + }, + }, + }); + + // Hook for boolean attributes + boolHook = { + get: function(elem, name) { + return elem.getAttribute(name) !== null ? name.toLowerCase() : undefined; + }, + set: function(elem, value, name) { + if (value === false) { + // Remove boolean attributes when set to false + jQuery.removeAttr(elem, name); + } else { + elem.setAttribute(name, name); + } + return name; + }, + }; + + // Radios and checkboxes getter/setter + if (!jQuery.support.checkOn) { + jQuery.each(['radio', 'checkbox'], function() { + jQuery.valHooks[this] = { + get: function(elem) { + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + return elem.getAttribute('value') === null ? 'on' : elem.value; + }, + }; + }); + } + jQuery.each(['radio', 'checkbox'], function() { + jQuery.valHooks[this] = jQuery.extend(jQuery.valHooks[this], { + set: function(elem, value) { + if (jQuery.isArray(value)) { + return (elem.checked = + jQuery.inArray(jQuery(elem).val(), value) >= 0); + } + }, + }); + }); + + // IE9/10 do not see a selected option inside an optgroup unless you access it + // Support: IE9, IE10 + if (!jQuery.support.optSelected) { + jQuery.propHooks.selected = jQuery.extend(jQuery.propHooks.selected, { + get: function(elem) { + var parent = elem.parentNode; + if (parent && parent.parentNode) { + parent.parentNode.selectedIndex; + } + return null; + }, + }); + } + var rkeyEvent = /^key/, + rmouseEvent = /^(?:mouse|contextmenu)|click/, + rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, + rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; + + function returnTrue() { + return true; + } + + function returnFalse() { + return false; + } + + /* + * Helper functions for managing events -- not part of the public interface. + * Props to Dean Edwards' addEvent library for many of the ideas. + */ + jQuery.event = { + global: {}, + + add: function(elem, types, handler, data, selector) { + var handleObjIn, + eventHandle, + tmp, + events, + t, + handleObj, + special, + handlers, + type, + namespaces, + origType, + elemData = data_priv.get(elem); + + // Don't attach events to noData or text/comment nodes (but allow plain objects) + if (!elemData) { + return; + } + + // Caller can pass in an object of custom data in lieu of the handler + if (handler.handler) { + handleObjIn = handler; + handler = handleObjIn.handler; + selector = handleObjIn.selector; + } + + // Make sure that the handler has a unique ID, used to find/remove it later + if (!handler.guid) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure and main handler, if this is the first + if (!(events = elemData.events)) { + events = elemData.events = {}; + } + if (!(eventHandle = elemData.handle)) { + eventHandle = elemData.handle = function(e) { + // Discard the second event of a jQuery.event.trigger() and + // when an event is called after a page has unloaded + return typeof jQuery !== core_strundefined && + (!e || jQuery.event.triggered !== e.type) + ? jQuery.event.dispatch.apply(eventHandle.elem, arguments) + : undefined; + }; + // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events + eventHandle.elem = elem; + } + + // Handle multiple events separated by a space + types = (types || '').match(core_rnotwhite) || ['']; + t = types.length; + while (t--) { + tmp = rtypenamespace.exec(types[t]) || []; + type = origType = tmp[1]; + namespaces = (tmp[2] || '').split('.').sort(); + + // There *must* be a type, no attaching namespace-only handlers + if (!type) { + continue; + } + + // If event changes its type, use the special event handlers for the changed type + special = jQuery.event.special[type] || {}; + + // If selector defined, determine special event api type, otherwise given type + type = (selector ? special.delegateType : special.bindType) || type; + + // Update special based on newly reset type + special = jQuery.event.special[type] || {}; + + // handleObj is passed to all event handlers + handleObj = jQuery.extend( + { + type: type, + origType: origType, + data: data, + handler: handler, + guid: handler.guid, + selector: selector, + needsContext: + selector && jQuery.expr.match.needsContext.test(selector), + namespace: namespaces.join('.'), + }, + handleObjIn + ); + + // Init the event handler queue if we're the first + if (!(handlers = events[type])) { + handlers = events[type] = []; + handlers.delegateCount = 0; + + // Only use addEventListener if the special events handler returns false + if ( + !special.setup || + special.setup.call(elem, data, namespaces, eventHandle) === false + ) { + if (elem.addEventListener) { + elem.addEventListener(type, eventHandle, false); + } + } + } + + if (special.add) { + special.add.call(elem, handleObj); + + if (!handleObj.handler.guid) { + handleObj.handler.guid = handler.guid; + } + } + + // Add to the element's handler list, delegates in front + if (selector) { + handlers.splice(handlers.delegateCount++, 0, handleObj); + } else { + handlers.push(handleObj); + } + + // Keep track of which events have ever been used, for event optimization + jQuery.event.global[type] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + // Detach an event or set of events from an element + remove: function(elem, types, handler, selector, mappedTypes) { + var j, + origCount, + tmp, + events, + t, + handleObj, + special, + handlers, + type, + namespaces, + origType, + elemData = data_priv.hasData(elem) && data_priv.get(elem); + + if (!elemData || !(events = elemData.events)) { + return; + } + + // Once for each type.namespace in types; type may be omitted + types = (types || '').match(core_rnotwhite) || ['']; + t = types.length; + while (t--) { + tmp = rtypenamespace.exec(types[t]) || []; + type = origType = tmp[1]; + namespaces = (tmp[2] || '').split('.').sort(); + + // Unbind all events (on this namespace, if provided) for the element + if (!type) { + for (type in events) { + jQuery.event.remove(elem, type + types[t], handler, selector, true); + } + continue; + } + + special = jQuery.event.special[type] || {}; + type = (selector ? special.delegateType : special.bindType) || type; + handlers = events[type] || []; + tmp = + tmp[2] && + new RegExp('(^|\\.)' + namespaces.join('\\.(?:.*\\.|)') + '(\\.|$)'); + + // Remove matching events + origCount = j = handlers.length; + while (j--) { + handleObj = handlers[j]; + + if ( + (mappedTypes || origType === handleObj.origType) && + (!handler || handler.guid === handleObj.guid) && + (!tmp || tmp.test(handleObj.namespace)) && + (!selector || + selector === handleObj.selector || + (selector === '**' && handleObj.selector)) + ) { + handlers.splice(j, 1); + + if (handleObj.selector) { + handlers.delegateCount--; + } + if (special.remove) { + special.remove.call(elem, handleObj); + } + } + } + + // Remove generic event handler if we removed something and no more handlers exist + // (avoids potential for endless recursion during removal of special event handlers) + if (origCount && !handlers.length) { + if ( + !special.teardown || + special.teardown.call(elem, namespaces, elemData.handle) === false + ) { + jQuery.removeEvent(elem, type, elemData.handle); + } + + delete events[type]; + } + } + + // Remove the expando if it's no longer used + if (jQuery.isEmptyObject(events)) { + delete elemData.handle; + + // removeData also checks for emptiness and clears the expando if empty + // so use it instead of delete + jQuery._removeData(elem, 'events'); + } + }, + + trigger: function(event, data, elem, onlyHandlers) { + var i, + cur, + tmp, + bubbleType, + ontype, + handle, + special, + eventPath = [elem || document], + type = core_hasOwn.call(event, 'type') ? event.type : event, + namespaces = core_hasOwn.call(event, 'namespace') + ? event.namespace.split('.') + : []; + + cur = tmp = elem = elem || document; + + // Don't do events on text and comment nodes + if (elem.nodeType === 3 || elem.nodeType === 8) { + return; + } + + // focus/blur morphs to focusin/out; ensure we're not firing them right now + if (rfocusMorph.test(type + jQuery.event.triggered)) { + return; + } + + if (type.indexOf('.') >= 0) { + // Namespaced trigger; create a regexp to match event type in handle() + namespaces = type.split('.'); + type = namespaces.shift(); + namespaces.sort(); + } + ontype = type.indexOf(':') < 0 && 'on' + type; + + // Caller can pass in a jQuery.Event object, Object, or just an event type string + event = event[jQuery.expando] + ? event + : new jQuery.Event(type, typeof event === 'object' && event); + + // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) + event.isTrigger = onlyHandlers ? 2 : 3; + event.namespace = namespaces.join('.'); + event.namespace_re = event.namespace + ? new RegExp('(^|\\.)' + namespaces.join('\\.(?:.*\\.|)') + '(\\.|$)') + : null; + + // Clean up the event in case it is being reused + event.result = undefined; + if (!event.target) { + event.target = elem; + } + + // Clone any incoming data and prepend the event, creating the handler arg list + data = data == null ? [event] : jQuery.makeArray(data, [event]); + + // Allow special events to draw outside the lines + special = jQuery.event.special[type] || {}; + if ( + !onlyHandlers && + special.trigger && + special.trigger.apply(elem, data) === false + ) { + return; + } + + // Determine event propagation path in advance, per W3C events spec (#9951) + // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) + if (!onlyHandlers && !special.noBubble && !jQuery.isWindow(elem)) { + bubbleType = special.delegateType || type; + if (!rfocusMorph.test(bubbleType + type)) { + cur = cur.parentNode; + } + for (; cur; cur = cur.parentNode) { + eventPath.push(cur); + tmp = cur; + } + + // Only add window if we got to document (e.g., not plain obj or detached DOM) + if (tmp === (elem.ownerDocument || document)) { + eventPath.push(tmp.defaultView || tmp.parentWindow || window); + } + } + + // Fire handlers on the event path + i = 0; + while ((cur = eventPath[i++]) && !event.isPropagationStopped()) { + event.type = i > 1 ? bubbleType : special.bindType || type; + + // jQuery handler + handle = + (jQuery._data(cur, 'events') || {})[event.type] && + jQuery._data(cur, 'handle'); + if (handle) { + handle.apply(cur, data); + } + + // Native handler + handle = ontype && cur[ontype]; + if ( + handle && + jQuery.acceptData(cur) && + handle.apply && + handle.apply(cur, data) === false + ) { + event.preventDefault(); + } + } + event.type = type; + + // If nobody prevented the default action, do it now + if (!onlyHandlers && !event.isDefaultPrevented()) { + if ( + (!special._default || + special._default.apply(elem.ownerDocument, data) === false) && + !(type === 'click' && jQuery.nodeName(elem, 'a')) && + jQuery.acceptData(elem) + ) { + // Call a native DOM method on the target with the same name name as the event. + // Don't do default actions on window, that's where global variables be (#6170) + if ( + ontype && + jQuery.isFunction(elem[type]) && + !jQuery.isWindow(elem) + ) { + // Don't re-trigger an onFOO event when we call its FOO() method + tmp = elem[ontype]; + + if (tmp) { + elem[ontype] = null; + } + + // Prevent re-triggering of the same event, since we already bubbled it above + jQuery.event.triggered = type; + elem[type](); + jQuery.event.triggered = undefined; + + if (tmp) { + elem[ontype] = tmp; + } + } + } + } + + return event.result; + }, + + dispatch: function(event) { + // Make a writable jQuery.Event from the native event object + event = jQuery.event.fix(event); + + var i, + j, + ret, + matched, + handleObj, + handlerQueue = [], + args = core_slice.call(arguments), + handlers = (jQuery._data(this, 'events') || {})[event.type] || [], + special = jQuery.event.special[event.type] || {}; + + // Use the fix-ed jQuery.Event rather than the (read-only) native event + args[0] = event; + event.delegateTarget = this; + + // Call the preDispatch hook for the mapped type, and let it bail if desired + if ( + special.preDispatch && + special.preDispatch.call(this, event) === false + ) { + return; + } + + // Determine handlers + handlerQueue = jQuery.event.handlers.call(this, event, handlers); + + // Run delegates first; they may want to stop propagation beneath us + i = 0; + while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) { + event.currentTarget = matched.elem; + + j = 0; + while ( + (handleObj = matched.handlers[j++]) && + !event.isImmediatePropagationStopped() + ) { + // Triggered event must either 1) have no namespace, or + // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). + if ( + !event.namespace_re || + event.namespace_re.test(handleObj.namespace) + ) { + event.handleObj = handleObj; + event.data = handleObj.data; + + ret = ( + (jQuery.event.special[handleObj.origType] || {}).handle || + handleObj.handler + ).apply(matched.elem, args); + + if (ret !== undefined) { + if ((event.result = ret) === false) { + event.preventDefault(); + event.stopPropagation(); + } + } + } + } + } + + // Call the postDispatch hook for the mapped type + if (special.postDispatch) { + special.postDispatch.call(this, event); + } + + return event.result; + }, + + handlers: function(event, handlers) { + var i, + matches, + sel, + handleObj, + handlerQueue = [], + delegateCount = handlers.delegateCount, + cur = event.target; + + // Find delegate handlers + // Black-hole SVG instance trees (#13180) + // Avoid non-left-click bubbling in Firefox (#3861) + if ( + delegateCount && + cur.nodeType && + (!event.button || event.type !== 'click') + ) { + for (; cur != this; cur = cur.parentNode || this) { + // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) + if (cur.disabled !== true || event.type !== 'click') { + matches = []; + for (i = 0; i < delegateCount; i++) { + handleObj = handlers[i]; + + // Don't conflict with Object.prototype properties (#13203) + sel = handleObj.selector + ' '; + + if (matches[sel] === undefined) { + matches[sel] = handleObj.needsContext + ? jQuery(sel, this).index(cur) >= 0 + : jQuery.find(sel, this, null, [cur]).length; + } + if (matches[sel]) { + matches.push(handleObj); + } + } + if (matches.length) { + handlerQueue.push({ elem: cur, handlers: matches }); + } + } + } + } + + // Add the remaining (directly-bound) handlers + if (delegateCount < handlers.length) { + handlerQueue.push({ + elem: this, + handlers: handlers.slice(delegateCount), + }); + } + + return handlerQueue; + }, + + // Includes some event props shared by KeyEvent and MouseEvent + props: 'altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which'.split( + ' ' + ), + + fixHooks: {}, + + keyHooks: { + props: 'char charCode key keyCode'.split(' '), + filter: function(event, original) { + // Add which for key events + if (event.which == null) { + event.which = + original.charCode != null ? original.charCode : original.keyCode; + } + + return event; + }, + }, + + mouseHooks: { + props: 'button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement'.split( + ' ' + ), + filter: function(event, original) { + var eventDoc, + doc, + body, + button = original.button; + + // Calculate pageX/Y if missing and clientX/Y available + if (event.pageX == null && original.clientX != null) { + eventDoc = event.target.ownerDocument || document; + doc = eventDoc.documentElement; + body = eventDoc.body; + + event.pageX = + original.clientX + + ((doc && doc.scrollLeft) || (body && body.scrollLeft) || 0) - + ((doc && doc.clientLeft) || (body && body.clientLeft) || 0); + event.pageY = + original.clientY + + ((doc && doc.scrollTop) || (body && body.scrollTop) || 0) - + ((doc && doc.clientTop) || (body && body.clientTop) || 0); + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if (!event.which && button !== undefined) { + event.which = button & 1 ? 1 : button & 2 ? 3 : button & 4 ? 2 : 0; + } + + return event; + }, + }, + + fix: function(event) { + if (event[jQuery.expando]) { + return event; + } + + // Create a writable copy of the event object and normalize some properties + var i, + prop, + copy, + type = event.type, + originalEvent = event, + fixHook = this.fixHooks[type]; + + if (!fixHook) { + this.fixHooks[type] = fixHook = rmouseEvent.test(type) + ? this.mouseHooks + : rkeyEvent.test(type) ? this.keyHooks : {}; + } + copy = fixHook.props ? this.props.concat(fixHook.props) : this.props; + + event = new jQuery.Event(originalEvent); + + i = copy.length; + while (i--) { + prop = copy[i]; + event[prop] = originalEvent[prop]; + } + + // Support: Chrome 23+, Safari? + // Target should not be a text node (#504, #13143) + if (event.target.nodeType === 3) { + event.target = event.target.parentNode; + } + + return fixHook.filter ? fixHook.filter(event, originalEvent) : event; + }, + + special: { + load: { + // Prevent triggered image.load events from bubbling to window.load + noBubble: true, + }, + click: { + // For checkbox, fire native event so checked state will be right + trigger: function() { + if ( + this.type === 'checkbox' && + this.click && + jQuery.nodeName(this, 'input') + ) { + this.click(); + return false; + } + }, + }, + focus: { + // Fire native event if possible so blur/focus sequence is correct + trigger: function() { + if (this !== document.activeElement && this.focus) { + this.focus(); + return false; + } + }, + delegateType: 'focusin', + }, + blur: { + trigger: function() { + if (this === document.activeElement && this.blur) { + this.blur(); + return false; + } + }, + delegateType: 'focusout', + }, + + beforeunload: { + postDispatch: function(event) { + // Support: Firefox 10+ + if (event.result !== undefined) { + event.originalEvent.returnValue = event.result; + } + }, + }, + }, + + simulate: function(type, elem, event, bubble) { + // Piggyback on a donor event to simulate a different one. + // Fake originalEvent to avoid donor's stopPropagation, but if the + // simulated event prevents default then we do the same on the donor. + var e = jQuery.extend(new jQuery.Event(), event, { + type: type, + isSimulated: true, + originalEvent: {}, + }); + if (bubble) { + jQuery.event.trigger(e, null, elem); + } else { + jQuery.event.dispatch.call(elem, e); + } + if (e.isDefaultPrevented()) { + event.preventDefault(); + } + }, + }; + + jQuery.removeEvent = function(elem, type, handle) { + if (elem.removeEventListener) { + elem.removeEventListener(type, handle, false); + } + }; + + jQuery.Event = function(src, props) { + // Allow instantiation without the 'new' keyword + if (!(this instanceof jQuery.Event)) { + return new jQuery.Event(src, props); + } + + // Event object + if (src && src.type) { + this.originalEvent = src; + this.type = src.type; + + // Events bubbling up the document may have been marked as prevented + // by a handler lower down the tree; reflect the correct value. + this.isDefaultPrevented = + src.defaultPrevented || + (src.getPreventDefault && src.getPreventDefault()) + ? returnTrue + : returnFalse; + + // Event type + } else { + this.type = src; + } + + // Put explicitly provided properties onto the event object + if (props) { + jQuery.extend(this, props); + } + + // Create a timestamp if incoming event doesn't have one + this.timeStamp = (src && src.timeStamp) || jQuery.now(); + + // Mark it as fixed + this[jQuery.expando] = true; + }; + + // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding + // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html + jQuery.Event.prototype = { + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse, + + preventDefault: function() { + var e = this.originalEvent; + + this.isDefaultPrevented = returnTrue; + + if (e && e.preventDefault) { + e.preventDefault(); + } + }, + stopPropagation: function() { + var e = this.originalEvent; + + this.isPropagationStopped = returnTrue; + + if (e && e.stopPropagation) { + e.stopPropagation(); + } + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + }; + + // Create mouseenter/leave events using mouseover/out and event-time checks + // Support: Chrome 15+ + jQuery.each( + { + mouseenter: 'mouseover', + mouseleave: 'mouseout', + }, + function(orig, fix) { + jQuery.event.special[orig] = { + delegateType: fix, + bindType: fix, + + handle: function(event) { + var ret, + target = this, + related = event.relatedTarget, + handleObj = event.handleObj; + + // For mousenter/leave call the handler if related is outside the target. + // NB: No relatedTarget if the mouse left/entered the browser window + if ( + !related || + (related !== target && !jQuery.contains(target, related)) + ) { + event.type = handleObj.origType; + ret = handleObj.handler.apply(this, arguments); + event.type = fix; + } + return ret; + }, + }; + } + ); + + // Create "bubbling" focus and blur events + // Support: Firefox 10+ + if (!jQuery.support.focusinBubbles) { + jQuery.each({ focus: 'focusin', blur: 'focusout' }, function(orig, fix) { + // Attach a single capturing handler while someone wants focusin/focusout + var attaches = 0, + handler = function(event) { + jQuery.event.simulate( + fix, + event.target, + jQuery.event.fix(event), + true + ); + }; + + jQuery.event.special[fix] = { + setup: function() { + if (attaches++ === 0) { + document.addEventListener(orig, handler, true); + } + }, + teardown: function() { + if (--attaches === 0) { + document.removeEventListener(orig, handler, true); + } + }, + }; + }); + } + + jQuery.fn.extend({ + on: function(types, selector, data, fn, /*INTERNAL*/ one) { + var origFn, type; + + // Types can be a map of types/handlers + if (typeof types === 'object') { + // ( types-Object, selector, data ) + if (typeof selector !== 'string') { + // ( types-Object, data ) + data = data || selector; + selector = undefined; + } + for (type in types) { + this.on(type, selector, data, types[type], one); + } + return this; + } + + if (data == null && fn == null) { + // ( types, fn ) + fn = selector; + data = selector = undefined; + } else if (fn == null) { + if (typeof selector === 'string') { + // ( types, selector, fn ) + fn = data; + data = undefined; + } else { + // ( types, data, fn ) + fn = data; + data = selector; + selector = undefined; + } + } + if (fn === false) { + fn = returnFalse; + } else if (!fn) { + return this; + } + + if (one === 1) { + origFn = fn; + fn = function(event) { + // Can use an empty set, since event contains the info + jQuery().off(event); + return origFn.apply(this, arguments); + }; + // Use same guid so caller can remove using origFn + fn.guid = origFn.guid || (origFn.guid = jQuery.guid++); + } + return this.each(function() { + jQuery.event.add(this, types, fn, data, selector); + }); + }, + one: function(types, selector, data, fn) { + return this.on(types, selector, data, fn, 1); + }, + off: function(types, selector, fn) { + var handleObj, type; + if (types && types.preventDefault && types.handleObj) { + // ( event ) dispatched jQuery.Event + handleObj = types.handleObj; + jQuery(types.delegateTarget).off( + handleObj.namespace + ? handleObj.origType + '.' + handleObj.namespace + : handleObj.origType, + handleObj.selector, + handleObj.handler + ); + return this; + } + if (typeof types === 'object') { + // ( types-object [, selector] ) + for (type in types) { + this.off(type, selector, types[type]); + } + return this; + } + if (selector === false || typeof selector === 'function') { + // ( types [, fn] ) + fn = selector; + selector = undefined; + } + if (fn === false) { + fn = returnFalse; + } + return this.each(function() { + jQuery.event.remove(this, types, fn, selector); + }); + }, + + bind: function(types, data, fn) { + return this.on(types, null, data, fn); + }, + unbind: function(types, fn) { + return this.off(types, null, fn); + }, + + delegate: function(selector, types, data, fn) { + return this.on(types, selector, data, fn); + }, + undelegate: function(selector, types, fn) { + // ( namespace ) or ( selector, types [, fn] ) + return arguments.length === 1 + ? this.off(selector, '**') + : this.off(types, selector || '**', fn); + }, + + trigger: function(type, data) { + return this.each(function() { + jQuery.event.trigger(type, data, this); + }); + }, + triggerHandler: function(type, data) { + var elem = this[0]; + if (elem) { + return jQuery.event.trigger(type, data, elem, true); + } + }, + }); + /*! + * Sizzle CSS Selector Engine + * Copyright 2013 jQuery Foundation and other contributors + * Released under the MIT license + * http://sizzlejs.com/ + */ + (function(window, undefined) { + var i, + cachedruns, + Expr, + getText, + isXML, + compile, + outermostContext, + recompare, + sortInput, + // Local document vars + setDocument, + document, + docElem, + documentIsXML, + rbuggyQSA, + rbuggyMatches, + matches, + contains, + // Instance-specific data + expando = 'sizzle' + -new Date(), + preferredDoc = window.document, + support = {}, + dirruns = 0, + done = 0, + classCache = createCache(), + tokenCache = createCache(), + compilerCache = createCache(), + hasDuplicate = false, + sortOrder = function() { + return 0; + }, + // General-purpose constants + strundefined = typeof undefined, + MAX_NEGATIVE = 1 << 31, + // Array methods + arr = [], + pop = arr.pop, + push_native = arr.push, + push = arr.push, + slice = arr.slice, + // Use a stripped-down indexOf if we can't use a native one + indexOf = + arr.indexOf || + function(elem) { + var i = 0, + len = this.length; + for (; i < len; i++) { + if (this[i] === elem) { + return i; + } + } + return -1; + }, + // Regular expressions + + // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace + whitespace = '[\\x20\\t\\r\\n\\f]', + // http://www.w3.org/TR/css3-syntax/#characters + characterEncoding = '(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+', + // Loosely modeled on CSS identifier characters + // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors + // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier + identifier = characterEncoding.replace('w', 'w#'), + // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors + operators = '([*^$|!~]?=)', + attributes = + '\\[' + + whitespace + + '*(' + + characterEncoding + + ')' + + whitespace + + '*(?:' + + operators + + whitespace + + '*(?:([\'"])((?:\\\\.|[^\\\\])*?)\\3|(' + + identifier + + ')|)|)' + + whitespace + + '*\\]', + // Prefer arguments quoted, + // then not containing pseudos/brackets, + // then attribute selectors/non-parenthetical expressions, + // then anything else + // These preferences are here to reduce the number of selectors + // needing tokenize in the PSEUDO preFilter + pseudos = + ':(' + + characterEncoding + + ')(?:\\((([\'"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|' + + attributes.replace(3, 8) + + ')*)|.*)\\)|)', + // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter + rtrim = new RegExp( + '^' + whitespace + '+|((?:^|[^\\\\])(?:\\\\.)*)' + whitespace + '+$', + 'g' + ), + rcomma = new RegExp('^' + whitespace + '*,' + whitespace + '*'), + rcombinators = new RegExp( + '^' + whitespace + '*([\\x20\\t\\r\\n\\f>+~])' + whitespace + '*' + ), + rpseudo = new RegExp(pseudos), + ridentifier = new RegExp('^' + identifier + '$'), + matchExpr = { + ID: new RegExp('^#(' + characterEncoding + ')'), + CLASS: new RegExp('^\\.(' + characterEncoding + ')'), + NAME: new RegExp('^\\[name=[\'"]?(' + characterEncoding + ')[\'"]?\\]'), + TAG: new RegExp('^(' + characterEncoding.replace('w', 'w*') + ')'), + ATTR: new RegExp('^' + attributes), + PSEUDO: new RegExp('^' + pseudos), + CHILD: new RegExp( + '^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(' + + whitespace + + '*(even|odd|(([+-]|)(\\d*)n|)' + + whitespace + + '*(?:([+-]|)' + + whitespace + + '*(\\d+)|))' + + whitespace + + '*\\)|)', + 'i' + ), + // For use in libraries implementing .is() + // We use this for POS matching in `select` + needsContext: new RegExp( + '^' + + whitespace + + '*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(' + + whitespace + + '*((?:-\\d)?\\d*)' + + whitespace + + '*\\)|)(?=[^-]|$)', + 'i' + ), + }, + rsibling = /[\x20\t\r\n\f]*[+~]/, + rnative = /^[^{]+\{\s*\[native code/, + // Easily-parseable/retrievable ID or TAG or CLASS selectors + rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + rinputs = /^(?:input|select|textarea|button)$/i, + rheader = /^h\d$/i, + rescape = /'|\\/g, + rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g, + // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters + runescape = /\\([\da-fA-F]{1,6}[\x20\t\r\n\f]?|.)/g, + funescape = function(_, escaped) { + var high = '0x' + escaped - 0x10000; + // NaN means non-codepoint + return high !== high + ? escaped + : // BMP codepoint + high < 0 + ? String.fromCharCode(high + 0x10000) + : // Supplemental Plane codepoint (surrogate pair) + String.fromCharCode( + (high >> 10) | 0xd800, + (high & 0x3ff) | 0xdc00 + ); + }; + + // Optimize for push.apply( _, NodeList ) + try { + push.apply( + (arr = slice.call(preferredDoc.childNodes)), + preferredDoc.childNodes + ); + // Support: Android<4.0 + // Detect silently failing push.apply + arr[preferredDoc.childNodes.length].nodeType; + } catch (e) { + push = { + apply: arr.length + ? // Leverage slice if possible + function(target, els) { + push_native.apply(target, slice.call(els)); + } + : // Support: IE<9 + // Otherwise append directly + function(target, els) { + var j = target.length, + i = 0; + // Can't trust NodeList.length + while ((target[j++] = els[i++])) {} + target.length = j - 1; + }, + }; + } + + /** + * For feature detection + * @param {Function} fn The function to test for native support + */ + function isNative(fn) { + return rnative.test(fn + ''); + } + + /** + * Create key-value caches of limited size + * @returns {Function(string, Object)} Returns the Object data after storing it on itself with + * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) + * deleting the oldest entry + */ + function createCache() { + var cache, + keys = []; + + return (cache = function(key, value) { + // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) + if (keys.push((key += ' ')) > Expr.cacheLength) { + // Only keep the most recent entries + delete cache[keys.shift()]; + } + return (cache[key] = value); + }); + } + + /** + * Mark a function for special use by Sizzle + * @param {Function} fn The function to mark + */ + function markFunction(fn) { + fn[expando] = true; + return fn; + } + + /** + * Support testing using an element + * @param {Function} fn Passed the created div and expects a boolean result + */ + function assert(fn) { + var div = document.createElement('div'); + + try { + return !!fn(div); + } catch (e) { + return false; + } finally { + // release memory in IE + div = null; + } + } + + function Sizzle(selector, context, results, seed) { + var match, + elem, + m, + nodeType, + // QSA vars + i, + groups, + old, + nid, + newContext, + newSelector; + + if ( + (context ? context.ownerDocument || context : preferredDoc) !== document + ) { + setDocument(context); + } + + context = context || document; + results = results || []; + + if (!selector || typeof selector !== 'string') { + return results; + } + + if ((nodeType = context.nodeType) !== 1 && nodeType !== 9) { + return []; + } + + if (!documentIsXML && !seed) { + // Shortcuts + if ((match = rquickExpr.exec(selector))) { + // Speed-up: Sizzle("#ID") + if ((m = match[1])) { + if (nodeType === 9) { + elem = context.getElementById(m); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + if (elem && elem.parentNode) { + // Handle the case where IE, Opera, and Webkit return items + // by name instead of ID + if (elem.id === m) { + results.push(elem); + return results; + } + } else { + return results; + } + } else { + // Context is not a document + if ( + context.ownerDocument && + (elem = context.ownerDocument.getElementById(m)) && + contains(context, elem) && + elem.id === m + ) { + results.push(elem); + return results; + } + } + + // Speed-up: Sizzle("TAG") + } else if (match[2]) { + push.apply(results, context.getElementsByTagName(selector)); + return results; + + // Speed-up: Sizzle(".CLASS") + } else if ( + (m = match[3]) && + support.getByClassName && + context.getElementsByClassName + ) { + push.apply(results, context.getElementsByClassName(m)); + return results; + } + } + + // QSA path + if (support.qsa && !rbuggyQSA.test(selector)) { + old = true; + nid = expando; + newContext = context; + newSelector = nodeType === 9 && selector; + + // qSA works strangely on Element-rooted queries + // We can work around this by specifying an extra ID on the root + // and working up from there (Thanks to Andrew Dupont for the technique) + // IE 8 doesn't work on object elements + if (nodeType === 1 && context.nodeName.toLowerCase() !== 'object') { + groups = tokenize(selector); + + if ((old = context.getAttribute('id'))) { + nid = old.replace(rescape, '\\$&'); + } else { + context.setAttribute('id', nid); + } + nid = "[id='" + nid + "'] "; + + i = groups.length; + while (i--) { + groups[i] = nid + toSelector(groups[i]); + } + newContext = + (rsibling.test(selector) && context.parentNode) || context; + newSelector = groups.join(','); + } + + if (newSelector) { + try { + push.apply(results, newContext.querySelectorAll(newSelector)); + return results; + } catch (qsaError) { + } finally { + if (!old) { + context.removeAttribute('id'); + } + } + } + } + } + + // All others + return select(selector.replace(rtrim, '$1'), context, results, seed); + } + + /** + * Detect xml + * @param {Element|Object} elem An element or a document + */ + isXML = Sizzle.isXML = function(elem) { + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = + elem && (elem.ownerDocument || elem).documentElement; + return documentElement ? documentElement.nodeName !== 'HTML' : false; + }; + + /** + * Sets document-related variables once based on the current document + * @param {Element|Object} [doc] An element or document object to use to set the document + * @returns {Object} Returns the current document + */ + setDocument = Sizzle.setDocument = function(node) { + var doc = node ? node.ownerDocument || node : preferredDoc; + + // If no document and documentElement is available, return + if (doc === document || doc.nodeType !== 9 || !doc.documentElement) { + return document; + } + + // Set our document + document = doc; + docElem = doc.documentElement; + + // Support tests + documentIsXML = isXML(doc); + + // Check if getElementsByTagName("*") returns only elements + support.tagNameNoComments = assert(function(div) { + div.appendChild(doc.createComment('')); + return !div.getElementsByTagName('*').length; + }); + + // Check if attributes should be retrieved by attribute nodes + support.attributes = assert(function(div) { + div.innerHTML = ''; + var type = typeof div.lastChild.getAttribute('multiple'); + // IE8 returns a string for some attributes even when not present + return type !== 'boolean' && type !== 'string'; + }); + + // Check if getElementsByClassName can be trusted + support.getByClassName = assert(function(div) { + // Opera can't find a second classname (in 9.6) + div.innerHTML = + ""; + if ( + !div.getElementsByClassName || + !div.getElementsByClassName('e').length + ) { + return false; + } + + // Safari 3.2 caches class attributes and doesn't catch changes + div.lastChild.className = 'e'; + return div.getElementsByClassName('e').length === 2; + }); + + // Check if getElementsByName privileges form controls or returns elements by ID + // If so, assume (for broader support) that getElementById returns elements by name + support.getByName = assert(function(div) { + // Inject content + div.id = expando + 0; + // Support: Windows 8 Native Apps + // Assigning innerHTML with "name" attributes throws uncatchable exceptions + // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx + div + .appendChild(document.createElement('a')) + .setAttribute('name', expando); + div + .appendChild(document.createElement('i')) + .setAttribute('name', expando); + docElem.appendChild(div); + + // Test + var pass = + doc.getElementsByName && + // buggy browsers will return fewer than the correct 2 + doc.getElementsByName(expando).length === + 2 + + // buggy browsers will return more than the correct 0 + doc.getElementsByName(expando + 0).length; + + // Cleanup + docElem.removeChild(div); + + return pass; + }); + + // Support: Webkit<537.32 + // Detached nodes confoundingly follow *each other* + support.sortDetached = assert(function(div1) { + return ( + div1.compareDocumentPosition && + // Should return 1, but Webkit returns 4 (following) + div1.compareDocumentPosition(document.createElement('div')) & 1 + ); + }); + + // IE6/7 return modified attributes + Expr.attrHandle = assert(function(div) { + div.innerHTML = ""; + return ( + div.firstChild && + typeof div.firstChild.getAttribute !== strundefined && + div.firstChild.getAttribute('href') === '#' + ); + }) + ? {} + : { + href: function(elem) { + return elem.getAttribute('href', 2); + }, + type: function(elem) { + return elem.getAttribute('type'); + }, + }; + + // ID find and filter + if (support.getByName) { + Expr.find['ID'] = function(id, context) { + if ( + typeof context.getElementById !== strundefined && + !documentIsXML + ) { + var m = context.getElementById(id); + // Check parentNode to catch when Blackberry 4.6 returns + // nodes that are no longer in the document #6963 + return m && m.parentNode ? [m] : []; + } + }; + Expr.filter['ID'] = function(id) { + var attrId = id.replace(runescape, funescape); + return function(elem) { + return elem.getAttribute('id') === attrId; + }; + }; + } else { + Expr.find['ID'] = function(id, context) { + if ( + typeof context.getElementById !== strundefined && + !documentIsXML + ) { + var m = context.getElementById(id); + + return m + ? m.id === id || + (typeof m.getAttributeNode !== strundefined && + m.getAttributeNode('id').value === id) + ? [m] + : undefined + : []; + } + }; + Expr.filter['ID'] = function(id) { + var attrId = id.replace(runescape, funescape); + return function(elem) { + var node = + typeof elem.getAttributeNode !== strundefined && + elem.getAttributeNode('id'); + return node && node.value === attrId; + }; + }; + } + + // Tag + Expr.find['TAG'] = support.tagNameNoComments + ? function(tag, context) { + if (typeof context.getElementsByTagName !== strundefined) { + return context.getElementsByTagName(tag); + } + } + : function(tag, context) { + var elem, + tmp = [], + i = 0, + results = context.getElementsByTagName(tag); + + // Filter out possible comments + if (tag === '*') { + while ((elem = results[i++])) { + if (elem.nodeType === 1) { + tmp.push(elem); + } + } + + return tmp; + } + return results; + }; + + // Name + Expr.find['NAME'] = + support.getByName && + function(tag, context) { + if (typeof context.getElementsByName !== strundefined) { + return context.getElementsByName(name); + } + }; + + // Class + Expr.find['CLASS'] = + support.getByClassName && + function(className, context) { + if ( + typeof context.getElementsByClassName !== strundefined && + !documentIsXML + ) { + return context.getElementsByClassName(className); + } + }; + + // QSA and matchesSelector support + + // matchesSelector(:active) reports false when true (IE9/Opera 11.5) + rbuggyMatches = []; + + // qSa(:focus) reports false when true (Chrome 21), + // no need to also add to buggyMatches since matches checks buggyQSA + // A support test would require too much code (would include document ready) + rbuggyQSA = [':focus']; + + if ((support.qsa = isNative(doc.querySelectorAll))) { + // Build QSA regex + // Regex strategy adopted from Diego Perini + assert(function(div) { + // Select is set to empty string on purpose + // This is to test IE's treatment of not explictly + // setting a boolean content attribute, + // since its presence should be enough + // http://bugs.jquery.com/ticket/12359 + div.innerHTML = ""; + + // IE8 - Some boolean attributes are not treated correctly + if (!div.querySelectorAll('[selected]').length) { + rbuggyQSA.push( + '\\[' + + whitespace + + '*(?:checked|disabled|ismap|multiple|readonly|selected|value)' + ); + } + + // Webkit/Opera - :checked should return selected option elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + // IE8 throws error here and will not see later tests + if (!div.querySelectorAll(':checked').length) { + rbuggyQSA.push(':checked'); + } + }); + + assert(function(div) { + // Opera 10-12/IE8 - ^= $= *= and empty values + // Should not select anything + div.innerHTML = ""; + if (div.querySelectorAll("[i^='']").length) { + rbuggyQSA.push('[*^$]=' + whitespace + '*(?:""|\'\')'); + } + + // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) + // IE8 throws error here and will not see later tests + if (!div.querySelectorAll(':enabled').length) { + rbuggyQSA.push(':enabled', ':disabled'); + } + + // Opera 10-11 does not throw on post-comma invalid pseudos + div.querySelectorAll('*,:x'); + rbuggyQSA.push(',.*:'); + }); + } + + if ( + (support.matchesSelector = isNative( + (matches = + docElem.matchesSelector || + docElem.mozMatchesSelector || + docElem.webkitMatchesSelector || + docElem.oMatchesSelector || + docElem.msMatchesSelector) + )) + ) { + assert(function(div) { + // Check to see if it's possible to do matchesSelector + // on a disconnected node (IE 9) + support.disconnectedMatch = matches.call(div, 'div'); + + // This should fail with an exception + // Gecko does not error, returns false instead + matches.call(div, "[s!='']:x"); + rbuggyMatches.push('!=', pseudos); + }); + } + + rbuggyQSA = new RegExp(rbuggyQSA.join('|')); + rbuggyMatches = + rbuggyMatches.length && new RegExp(rbuggyMatches.join('|')); + + // Element contains another + // Purposefully does not implement inclusive descendent + // As in, an element does not contain itself + contains = + isNative(docElem.contains) || docElem.compareDocumentPosition + ? function(a, b) { + var adown = a.nodeType === 9 ? a.documentElement : a, + bup = b && b.parentNode; + return ( + a === bup || + !!( + bup && + bup.nodeType === 1 && + (adown.contains + ? adown.contains(bup) + : a.compareDocumentPosition && + a.compareDocumentPosition(bup) & 16) + ) + ); + } + : function(a, b) { + if (b) { + while ((b = b.parentNode)) { + if (b === a) { + return true; + } + } + } + return false; + }; + + // Document order sorting + sortOrder = docElem.compareDocumentPosition + ? function(a, b) { + // Flag for duplicate removal + if (a === b) { + hasDuplicate = true; + return 0; + } + + var compare = + b.compareDocumentPosition && + a.compareDocumentPosition && + a.compareDocumentPosition(b); + + if (compare) { + // Disconnected nodes + if ( + compare & 1 || + (recompare && b.compareDocumentPosition(a) === compare) + ) { + // Choose the first element that is related to our preferred document + if (a === doc || contains(preferredDoc, a)) { + return -1; + } + if (b === doc || contains(preferredDoc, b)) { + return 1; + } + + // Maintain original order + return sortInput + ? indexOf.call(sortInput, a) - indexOf.call(sortInput, b) + : 0; + } + + return compare & 4 ? -1 : 1; + } + + // Not directly comparable, sort on existence of method + return a.compareDocumentPosition ? -1 : 1; + } + : function(a, b) { + var cur, + i = 0, + aup = a.parentNode, + bup = b.parentNode, + ap = [a], + bp = [b]; + + // Exit early if the nodes are identical + if (a === b) { + hasDuplicate = true; + return 0; + + // Parentless nodes are either documents or disconnected + } else if (!aup || !bup) { + return a === doc ? -1 : b === doc ? 1 : aup ? -1 : bup ? 1 : 0; + + // If the nodes are siblings, we can do a quick check + } else if (aup === bup) { + return siblingCheck(a, b); + } + + // Otherwise we need full lists of their ancestors for comparison + cur = a; + while ((cur = cur.parentNode)) { + ap.unshift(cur); + } + cur = b; + while ((cur = cur.parentNode)) { + bp.unshift(cur); + } + + // Walk down the tree looking for a discrepancy + while (ap[i] === bp[i]) { + i++; + } + + return i + ? // Do a sibling check if the nodes have a common ancestor + siblingCheck(ap[i], bp[i]) + : // Otherwise nodes in our document sort first + ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0; + }; + + return document; + }; + + Sizzle.matches = function(expr, elements) { + return Sizzle(expr, null, null, elements); + }; + + Sizzle.matchesSelector = function(elem, expr) { + // Set document vars if needed + if ((elem.ownerDocument || elem) !== document) { + setDocument(elem); + } + + // Make sure that attribute selectors are quoted + expr = expr.replace(rattributeQuotes, "='$1']"); + + // rbuggyQSA always contains :focus, so no need for an existence check + if ( + support.matchesSelector && + !documentIsXML && + (!rbuggyMatches || !rbuggyMatches.test(expr)) && + !rbuggyQSA.test(expr) + ) { + try { + var ret = matches.call(elem, expr); + + // IE 9's matchesSelector returns false on disconnected nodes + if ( + ret || + support.disconnectedMatch || + // As well, disconnected nodes are said to be in a document + // fragment in IE 9 + (elem.document && elem.document.nodeType !== 11) + ) { + return ret; + } + } catch (e) {} + } + + return Sizzle(expr, document, null, [elem]).length > 0; + }; + + Sizzle.contains = function(context, elem) { + // Set document vars if needed + if ((context.ownerDocument || context) !== document) { + setDocument(context); + } + return contains(context, elem); + }; + + Sizzle.attr = function(elem, name) { + var val; + + // Set document vars if needed + if ((elem.ownerDocument || elem) !== document) { + setDocument(elem); + } + + if (!documentIsXML) { + name = name.toLowerCase(); + } + if ((val = Expr.attrHandle[name])) { + return val(elem); + } + if (documentIsXML || support.attributes) { + return elem.getAttribute(name); + } + return ((val = elem.getAttributeNode(name)) || elem.getAttribute(name)) && + elem[name] === true + ? name + : val && val.specified ? val.value : null; + }; + + Sizzle.error = function(msg) { + throw new Error('Syntax error, unrecognized expression: ' + msg); + }; + + // Document sorting and removing duplicates + Sizzle.uniqueSort = function(results) { + var elem, + duplicates = [], + j = 0, + i = 0; + + // Unless we *know* we can detect duplicates, assume their presence + hasDuplicate = !support.detectDuplicates; + // Compensate for sort limitations + recompare = !support.sortDetached; + sortInput = !support.sortStable && results.slice(0); + results.sort(sortOrder); + + if (hasDuplicate) { + while ((elem = results[i++])) { + if (elem === results[i]) { + j = duplicates.push(i); + } + } + while (j--) { + results.splice(duplicates[j], 1); + } + } + + return results; + }; + + /** + * Checks document order of two siblings + * @param {Element} a + * @param {Element} b + * @returns Returns -1 if a precedes b, 1 if a follows b + */ + function siblingCheck(a, b) { + var cur = b && a, + diff = + cur && + (~b.sourceIndex || MAX_NEGATIVE) - (~a.sourceIndex || MAX_NEGATIVE); + + // Use IE sourceIndex if available on both nodes + if (diff) { + return diff; + } + + // Check if b follows a + if (cur) { + while ((cur = cur.nextSibling)) { + if (cur === b) { + return -1; + } + } + } + + return a ? 1 : -1; + } + + // Returns a function to use in pseudos for input types + function createInputPseudo(type) { + return function(elem) { + var name = elem.nodeName.toLowerCase(); + return name === 'input' && elem.type === type; + }; + } + + // Returns a function to use in pseudos for buttons + function createButtonPseudo(type) { + return function(elem) { + var name = elem.nodeName.toLowerCase(); + return (name === 'input' || name === 'button') && elem.type === type; + }; + } + + // Returns a function to use in pseudos for positionals + function createPositionalPseudo(fn) { + return markFunction(function(argument) { + argument = +argument; + return markFunction(function(seed, matches) { + var j, + matchIndexes = fn([], seed.length, argument), + i = matchIndexes.length; + + // Match elements found at the specified indexes + while (i--) { + if (seed[(j = matchIndexes[i])]) { + seed[j] = !(matches[j] = seed[j]); + } + } + }); + }); + } + + /** + * Utility function for retrieving the text value of an array of DOM nodes + * @param {Array|Element} elem + */ + getText = Sizzle.getText = function(elem) { + var node, + ret = '', + i = 0, + nodeType = elem.nodeType; + + if (!nodeType) { + // If no nodeType, this is expected to be an array + for (; (node = elem[i]); i++) { + // Do not traverse comment nodes + ret += getText(node); + } + } else if (nodeType === 1 || nodeType === 9 || nodeType === 11) { + // Use textContent for elements + // innerText usage removed for consistency of new lines (see #11153) + if (typeof elem.textContent === 'string') { + return elem.textContent; + } else { + // Traverse its children + for (elem = elem.firstChild; elem; elem = elem.nextSibling) { + ret += getText(elem); + } + } + } else if (nodeType === 3 || nodeType === 4) { + return elem.nodeValue; + } + // Do not include comment or processing instruction nodes + + return ret; + }; + + Expr = Sizzle.selectors = { + // Can be adjusted by the user + cacheLength: 50, + + createPseudo: markFunction, + + match: matchExpr, + + find: {}, + + relative: { + '>': { dir: 'parentNode', first: true }, + ' ': { dir: 'parentNode' }, + '+': { dir: 'previousSibling', first: true }, + '~': { dir: 'previousSibling' }, + }, + + preFilter: { + ATTR: function(match) { + match[1] = match[1].replace(runescape, funescape); + + // Move the given value to match[3] whether quoted or unquoted + match[3] = (match[4] || match[5] || '').replace(runescape, funescape); + + if (match[2] === '~=') { + match[3] = ' ' + match[3] + ' '; + } + + return match.slice(0, 4); + }, + + CHILD: function(match) { + /* matches from matchExpr["CHILD"] + 1 type (only|nth|...) + 2 what (child|of-type) + 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) + 4 xn-component of xn+y argument ([+-]?\d*n|) + 5 sign of xn-component + 6 x of xn-component + 7 sign of y-component + 8 y of y-component + */ + match[1] = match[1].toLowerCase(); + + if (match[1].slice(0, 3) === 'nth') { + // nth-* requires argument + if (!match[3]) { + Sizzle.error(match[0]); + } + + // numeric x and y parameters for Expr.filter.CHILD + // remember that false/true cast respectively to 0/1 + match[4] = +(match[4] + ? match[5] + (match[6] || 1) + : 2 * (match[3] === 'even' || match[3] === 'odd')); + match[5] = +(match[7] + match[8] || match[3] === 'odd'); + + // other types prohibit arguments + } else if (match[3]) { + Sizzle.error(match[0]); + } + + return match; + }, + + PSEUDO: function(match) { + var excess, + unquoted = !match[5] && match[2]; + + if (matchExpr['CHILD'].test(match[0])) { + return null; + } + + // Accept quoted arguments as-is + if (match[4]) { + match[2] = match[4]; + + // Strip excess characters from unquoted arguments + } else if ( + unquoted && + rpseudo.test(unquoted) && + // Get excess from tokenize (recursively) + (excess = tokenize(unquoted, true)) && + // advance to the next closing parenthesis + (excess = + unquoted.indexOf(')', unquoted.length - excess) - unquoted.length) + ) { + // excess is a negative index + match[0] = match[0].slice(0, excess); + match[2] = unquoted.slice(0, excess); + } + + // Return only captures needed by the pseudo filter method (type and argument) + return match.slice(0, 3); + }, + }, + + filter: { + TAG: function(nodeName) { + if (nodeName === '*') { + return function() { + return true; + }; + } + + nodeName = nodeName.replace(runescape, funescape).toLowerCase(); + return function(elem) { + return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; + }; + }, + + CLASS: function(className) { + var pattern = classCache[className + ' ']; + + return ( + pattern || + ((pattern = new RegExp( + '(^|' + whitespace + ')' + className + '(' + whitespace + '|$)' + )) && + classCache(className, function(elem) { + return pattern.test( + elem.className || + (typeof elem.getAttribute !== strundefined && + elem.getAttribute('class')) || + '' + ); + })) + ); + }, + + ATTR: function(name, operator, check) { + return function(elem) { + var result = Sizzle.attr(elem, name); + + if (result == null) { + return operator === '!='; + } + if (!operator) { + return true; + } + + result += ''; + + return operator === '=' + ? result === check + : operator === '!=' + ? result !== check + : operator === '^=' + ? check && result.indexOf(check) === 0 + : operator === '*=' + ? check && result.indexOf(check) > -1 + : operator === '$=' + ? check && result.slice(-check.length) === check + : operator === '~=' + ? (' ' + result + ' ').indexOf(check) > -1 + : operator === '|=' + ? result === check || + result.slice(0, check.length + 1) === check + '-' + : false; + }; + }, + + CHILD: function(type, what, argument, first, last) { + var simple = type.slice(0, 3) !== 'nth', + forward = type.slice(-4) !== 'last', + ofType = what === 'of-type'; + + return first === 1 && last === 0 + ? // Shortcut for :nth-*(n) + function(elem) { + return !!elem.parentNode; + } + : function(elem, context, xml) { + var cache, + outerCache, + node, + diff, + nodeIndex, + start, + dir = simple !== forward ? 'nextSibling' : 'previousSibling', + parent = elem.parentNode, + name = ofType && elem.nodeName.toLowerCase(), + useCache = !xml && !ofType; + + if (parent) { + // :(first|last|only)-(child|of-type) + if (simple) { + while (dir) { + node = elem; + while ((node = node[dir])) { + if ( + ofType + ? node.nodeName.toLowerCase() === name + : node.nodeType === 1 + ) { + return false; + } + } + // Reverse direction for :only-* (if we haven't yet done so) + start = dir = type === 'only' && !start && 'nextSibling'; + } + return true; + } + + start = [forward ? parent.firstChild : parent.lastChild]; + + // non-xml :nth-child(...) stores cache data on `parent` + if (forward && useCache) { + // Seek `elem` from a previously-cached index + outerCache = parent[expando] || (parent[expando] = {}); + cache = outerCache[type] || []; + nodeIndex = cache[0] === dirruns && cache[1]; + diff = cache[0] === dirruns && cache[2]; + node = nodeIndex && parent.childNodes[nodeIndex]; + + while ( + (node = + (++nodeIndex && node && node[dir]) || + // Fallback to seeking `elem` from the start + (diff = nodeIndex = 0) || + start.pop()) + ) { + // When found, cache indexes on `parent` and break + if (node.nodeType === 1 && ++diff && node === elem) { + outerCache[type] = [dirruns, nodeIndex, diff]; + break; + } + } + + // Use previously-cached element index if available + } else if ( + useCache && + (cache = (elem[expando] || (elem[expando] = {}))[type]) && + cache[0] === dirruns + ) { + diff = cache[1]; + + // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) + } else { + // Use the same loop as above to seek `elem` from the start + while ( + (node = + (++nodeIndex && node && node[dir]) || + (diff = nodeIndex = 0) || + start.pop()) + ) { + if ( + (ofType + ? node.nodeName.toLowerCase() === name + : node.nodeType === 1) && + ++diff + ) { + // Cache the index of each encountered element + if (useCache) { + (node[expando] || (node[expando] = {}))[type] = [ + dirruns, + diff, + ]; + } + + if (node === elem) { + break; + } + } + } + } + + // Incorporate the offset, then check against cycle size + diff -= last; + return ( + diff === first || (diff % first === 0 && diff / first >= 0) + ); + } + }; + }, + + PSEUDO: function(pseudo, argument) { + // pseudo-class names are case-insensitive + // http://www.w3.org/TR/selectors/#pseudo-classes + // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters + // Remember that setFilters inherits from pseudos + var args, + fn = + Expr.pseudos[pseudo] || + Expr.setFilters[pseudo.toLowerCase()] || + Sizzle.error('unsupported pseudo: ' + pseudo); + + // The user may use createPseudo to indicate that + // arguments are needed to create the filter function + // just as Sizzle does + if (fn[expando]) { + return fn(argument); + } + + // But maintain support for old signatures + if (fn.length > 1) { + args = [pseudo, pseudo, '', argument]; + return Expr.setFilters.hasOwnProperty(pseudo.toLowerCase()) + ? markFunction(function(seed, matches) { + var idx, + matched = fn(seed, argument), + i = matched.length; + while (i--) { + idx = indexOf.call(seed, matched[i]); + seed[idx] = !(matches[idx] = matched[i]); + } + }) + : function(elem) { + return fn(elem, 0, args); + }; + } + + return fn; + }, + }, + + pseudos: { + // Potentially complex pseudos + not: markFunction(function(selector) { + // Trim the selector passed to compile + // to avoid treating leading and trailing + // spaces as combinators + var input = [], + results = [], + matcher = compile(selector.replace(rtrim, '$1')); + + return matcher[expando] + ? markFunction(function(seed, matches, context, xml) { + var elem, + unmatched = matcher(seed, null, xml, []), + i = seed.length; + + // Match elements unmatched by `matcher` + while (i--) { + if ((elem = unmatched[i])) { + seed[i] = !(matches[i] = elem); + } + } + }) + : function(elem, context, xml) { + input[0] = elem; + matcher(input, null, xml, results); + return !results.pop(); + }; + }), + + has: markFunction(function(selector) { + return function(elem) { + return Sizzle(selector, elem).length > 0; + }; + }), + + contains: markFunction(function(text) { + return function(elem) { + return ( + (elem.textContent || elem.innerText || getText(elem)).indexOf( + text + ) > -1 + ); + }; + }), + + // "Whether an element is represented by a :lang() selector + // is based solely on the element's language value + // being equal to the identifier C, + // or beginning with the identifier C immediately followed by "-". + // The matching of C against the element's language value is performed case-insensitively. + // The identifier C does not have to be a valid language name." + // http://www.w3.org/TR/selectors/#lang-pseudo + lang: markFunction(function(lang) { + // lang value must be a valid identifider + if (!ridentifier.test(lang || '')) { + Sizzle.error('unsupported lang: ' + lang); + } + lang = lang.replace(runescape, funescape).toLowerCase(); + return function(elem) { + var elemLang; + do { + if ( + (elemLang = documentIsXML + ? elem.getAttribute('xml:lang') || elem.getAttribute('lang') + : elem.lang) + ) { + elemLang = elemLang.toLowerCase(); + return elemLang === lang || elemLang.indexOf(lang + '-') === 0; + } + } while ((elem = elem.parentNode) && elem.nodeType === 1); + return false; + }; + }), + + // Miscellaneous + target: function(elem) { + var hash = window.location && window.location.hash; + return hash && hash.slice(1) === elem.id; + }, + + root: function(elem) { + return elem === docElem; + }, + + focus: function(elem) { + return ( + elem === document.activeElement && + (!document.hasFocus || document.hasFocus()) && + !!(elem.type || elem.href || ~elem.tabIndex) + ); + }, + + // Boolean properties + enabled: function(elem) { + return elem.disabled === false; + }, + + disabled: function(elem) { + return elem.disabled === true; + }, + + checked: function(elem) { + // In CSS3, :checked should return both checked and selected elements + // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked + var nodeName = elem.nodeName.toLowerCase(); + return ( + (nodeName === 'input' && !!elem.checked) || + (nodeName === 'option' && !!elem.selected) + ); + }, + + selected: function(elem) { + // Accessing this property makes selected-by-default + // options in Safari work properly + if (elem.parentNode) { + elem.parentNode.selectedIndex; + } + + return elem.selected === true; + }, + + // Contents + empty: function(elem) { + // http://www.w3.org/TR/selectors/#empty-pseudo + // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)), + // not comment, processing instructions, or others + // Thanks to Diego Perini for the nodeName shortcut + // Greater than "@" means alpha characters (specifically not starting with "#" or "?") + for (elem = elem.firstChild; elem; elem = elem.nextSibling) { + if ( + elem.nodeName > '@' || + elem.nodeType === 3 || + elem.nodeType === 4 + ) { + return false; + } + } + return true; + }, + + parent: function(elem) { + return !Expr.pseudos['empty'](elem); + }, + + // Element/input types + header: function(elem) { + return rheader.test(elem.nodeName); + }, + + input: function(elem) { + return rinputs.test(elem.nodeName); + }, + + button: function(elem) { + var name = elem.nodeName.toLowerCase(); + return ( + (name === 'input' && elem.type === 'button') || name === 'button' + ); + }, + + text: function(elem) { + var attr; + // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc) + // use getAttribute instead to test this case + return ( + elem.nodeName.toLowerCase() === 'input' && + elem.type === 'text' && + ((attr = elem.getAttribute('type')) == null || + attr.toLowerCase() === elem.type) + ); + }, + + // Position-in-collection + first: createPositionalPseudo(function() { + return [0]; + }), + + last: createPositionalPseudo(function(matchIndexes, length) { + return [length - 1]; + }), + + eq: createPositionalPseudo(function(matchIndexes, length, argument) { + return [argument < 0 ? argument + length : argument]; + }), + + even: createPositionalPseudo(function(matchIndexes, length) { + var i = 0; + for (; i < length; i += 2) { + matchIndexes.push(i); + } + return matchIndexes; + }), + + odd: createPositionalPseudo(function(matchIndexes, length) { + var i = 1; + for (; i < length; i += 2) { + matchIndexes.push(i); + } + return matchIndexes; + }), + + lt: createPositionalPseudo(function(matchIndexes, length, argument) { + var i = argument < 0 ? argument + length : argument; + for (; --i >= 0; ) { + matchIndexes.push(i); + } + return matchIndexes; + }), + + gt: createPositionalPseudo(function(matchIndexes, length, argument) { + var i = argument < 0 ? argument + length : argument; + for (; ++i < length; ) { + matchIndexes.push(i); + } + return matchIndexes; + }), + }, + }; + + // Add button/input type pseudos + for (i in { + radio: true, + checkbox: true, + file: true, + password: true, + image: true, + }) { + Expr.pseudos[i] = createInputPseudo(i); + } + for (i in { submit: true, reset: true }) { + Expr.pseudos[i] = createButtonPseudo(i); + } + + function tokenize(selector, parseOnly) { + var matched, + match, + tokens, + type, + soFar, + groups, + preFilters, + cached = tokenCache[selector + ' ']; + + if (cached) { + return parseOnly ? 0 : cached.slice(0); + } + + soFar = selector; + groups = []; + preFilters = Expr.preFilter; + + while (soFar) { + // Comma and first run + if (!matched || (match = rcomma.exec(soFar))) { + if (match) { + // Don't consume trailing commas as valid + soFar = soFar.slice(match[0].length) || soFar; + } + groups.push((tokens = [])); + } + + matched = false; + + // Combinators + if ((match = rcombinators.exec(soFar))) { + matched = match.shift(); + tokens.push({ + value: matched, + // Cast descendant combinators to space + type: match[0].replace(rtrim, ' '), + }); + soFar = soFar.slice(matched.length); + } + + // Filters + for (type in Expr.filter) { + if ( + (match = matchExpr[type].exec(soFar)) && + (!preFilters[type] || (match = preFilters[type](match))) + ) { + matched = match.shift(); + tokens.push({ + value: matched, + type: type, + matches: match, + }); + soFar = soFar.slice(matched.length); + } + } + + if (!matched) { + break; + } + } + + // Return the length of the invalid excess + // if we're just parsing + // Otherwise, throw an error or return tokens + return parseOnly + ? soFar.length + : soFar + ? Sizzle.error(selector) + : // Cache the tokens + tokenCache(selector, groups).slice(0); + } + + function toSelector(tokens) { + var i = 0, + len = tokens.length, + selector = ''; + for (; i < len; i++) { + selector += tokens[i].value; + } + return selector; + } + + function addCombinator(matcher, combinator, base) { + var dir = combinator.dir, + checkNonElements = base && dir === 'parentNode', + doneName = done++; + + return combinator.first + ? // Check against closest ancestor/preceding element + function(elem, context, xml) { + while ((elem = elem[dir])) { + if (elem.nodeType === 1 || checkNonElements) { + return matcher(elem, context, xml); + } + } + } + : // Check against all ancestor/preceding elements + function(elem, context, xml) { + var data, + cache, + outerCache, + dirkey = dirruns + ' ' + doneName; + + // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching + if (xml) { + while ((elem = elem[dir])) { + if (elem.nodeType === 1 || checkNonElements) { + if (matcher(elem, context, xml)) { + return true; + } + } + } + } else { + while ((elem = elem[dir])) { + if (elem.nodeType === 1 || checkNonElements) { + outerCache = elem[expando] || (elem[expando] = {}); + if ((cache = outerCache[dir]) && cache[0] === dirkey) { + if ((data = cache[1]) === true || data === cachedruns) { + return data === true; + } + } else { + cache = outerCache[dir] = [dirkey]; + cache[1] = matcher(elem, context, xml) || cachedruns; + if (cache[1] === true) { + return true; + } + } + } + } + } + }; + } + + function elementMatcher(matchers) { + return matchers.length > 1 + ? function(elem, context, xml) { + var i = matchers.length; + while (i--) { + if (!matchers[i](elem, context, xml)) { + return false; + } + } + return true; + } + : matchers[0]; + } + + function condense(unmatched, map, filter, context, xml) { + var elem, + newUnmatched = [], + i = 0, + len = unmatched.length, + mapped = map != null; + + for (; i < len; i++) { + if ((elem = unmatched[i])) { + if (!filter || filter(elem, context, xml)) { + newUnmatched.push(elem); + if (mapped) { + map.push(i); + } + } + } + } + + return newUnmatched; + } + + function setMatcher( + preFilter, + selector, + matcher, + postFilter, + postFinder, + postSelector + ) { + if (postFilter && !postFilter[expando]) { + postFilter = setMatcher(postFilter); + } + if (postFinder && !postFinder[expando]) { + postFinder = setMatcher(postFinder, postSelector); + } + return markFunction(function(seed, results, context, xml) { + var temp, + i, + elem, + preMap = [], + postMap = [], + preexisting = results.length, + // Get initial elements from seed or context + elems = + seed || + multipleContexts( + selector || '*', + context.nodeType ? [context] : context, + [] + ), + // Prefilter to get matcher input, preserving a map for seed-results synchronization + matcherIn = + preFilter && (seed || !selector) + ? condense(elems, preMap, preFilter, context, xml) + : elems, + matcherOut = matcher + ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, + postFinder || (seed ? preFilter : preexisting || postFilter) + ? // ...intermediate processing is necessary + [] + : // ...otherwise use results directly + results + : matcherIn; + + // Find primary matches + if (matcher) { + matcher(matcherIn, matcherOut, context, xml); + } + + // Apply postFilter + if (postFilter) { + temp = condense(matcherOut, postMap); + postFilter(temp, [], context, xml); + + // Un-match failing elements by moving them back to matcherIn + i = temp.length; + while (i--) { + if ((elem = temp[i])) { + matcherOut[postMap[i]] = !(matcherIn[postMap[i]] = elem); + } + } + } + + if (seed) { + if (postFinder || preFilter) { + if (postFinder) { + // Get the final matcherOut by condensing this intermediate into postFinder contexts + temp = []; + i = matcherOut.length; + while (i--) { + if ((elem = matcherOut[i])) { + // Restore matcherIn since elem is not yet a final match + temp.push((matcherIn[i] = elem)); + } + } + postFinder(null, (matcherOut = []), temp, xml); + } + + // Move matched elements from seed to results to keep them synchronized + i = matcherOut.length; + while (i--) { + if ( + (elem = matcherOut[i]) && + (temp = postFinder ? indexOf.call(seed, elem) : preMap[i]) > -1 + ) { + seed[temp] = !(results[temp] = elem); + } + } + } + + // Add elements to results, through postFinder if defined + } else { + matcherOut = condense( + matcherOut === results + ? matcherOut.splice(preexisting, matcherOut.length) + : matcherOut + ); + if (postFinder) { + postFinder(null, results, matcherOut, xml); + } else { + push.apply(results, matcherOut); + } + } + }); + } + + function matcherFromTokens(tokens) { + var checkContext, + matcher, + j, + len = tokens.length, + leadingRelative = Expr.relative[tokens[0].type], + implicitRelative = leadingRelative || Expr.relative[' '], + i = leadingRelative ? 1 : 0, + // The foundational matcher ensures that elements are reachable from top-level context(s) + matchContext = addCombinator( + function(elem) { + return elem === checkContext; + }, + implicitRelative, + true + ), + matchAnyContext = addCombinator( + function(elem) { + return indexOf.call(checkContext, elem) > -1; + }, + implicitRelative, + true + ), + matchers = [ + function(elem, context, xml) { + return ( + (!leadingRelative && (xml || context !== outermostContext)) || + ((checkContext = context).nodeType + ? matchContext(elem, context, xml) + : matchAnyContext(elem, context, xml)) + ); + }, + ]; + + for (; i < len; i++) { + if ((matcher = Expr.relative[tokens[i].type])) { + matchers = [addCombinator(elementMatcher(matchers), matcher)]; + } else { + matcher = Expr.filter[tokens[i].type].apply(null, tokens[i].matches); + + // Return special upon seeing a positional matcher + if (matcher[expando]) { + // Find the next relative operator (if any) for proper handling + j = ++i; + for (; j < len; j++) { + if (Expr.relative[tokens[j].type]) { + break; + } + } + return setMatcher( + i > 1 && elementMatcher(matchers), + i > 1 && toSelector(tokens.slice(0, i - 1)).replace(rtrim, '$1'), + matcher, + i < j && matcherFromTokens(tokens.slice(i, j)), + j < len && matcherFromTokens((tokens = tokens.slice(j))), + j < len && toSelector(tokens) + ); + } + matchers.push(matcher); + } + } + + return elementMatcher(matchers); + } + + function matcherFromGroupMatchers(elementMatchers, setMatchers) { + // A counter to specify which element is currently being matched + var matcherCachedRuns = 0, + bySet = setMatchers.length > 0, + byElement = elementMatchers.length > 0, + superMatcher = function(seed, context, xml, results, expandContext) { + var elem, + j, + matcher, + setMatched = [], + matchedCount = 0, + i = '0', + unmatched = seed && [], + outermost = expandContext != null, + contextBackup = outermostContext, + // We must always have either seed elements or context + elems = + seed || + (byElement && + Expr.find['TAG']( + '*', + (expandContext && context.parentNode) || context + )), + // Use integer dirruns iff this is the outermost matcher + dirrunsUnique = (dirruns += + contextBackup == null ? 1 : Math.random() || 0.1); + + if (outermost) { + outermostContext = context !== document && context; + cachedruns = matcherCachedRuns; + } + + // Add elements passing elementMatchers directly to results + // Keep `i` a string if there are no elements so `matchedCount` will be "00" below + for (; (elem = elems[i]) != null; i++) { + if (byElement && elem) { + j = 0; + while ((matcher = elementMatchers[j++])) { + if (matcher(elem, context, xml)) { + results.push(elem); + break; + } + } + if (outermost) { + dirruns = dirrunsUnique; + cachedruns = ++matcherCachedRuns; + } + } + + // Track unmatched elements for set filters + if (bySet) { + // They will have gone through all possible matchers + if ((elem = !matcher && elem)) { + matchedCount--; + } + + // Lengthen the array for every element, matched or not + if (seed) { + unmatched.push(elem); + } + } + } + + // Apply set filters to unmatched elements + matchedCount += i; + if (bySet && i !== matchedCount) { + j = 0; + while ((matcher = setMatchers[j++])) { + matcher(unmatched, setMatched, context, xml); + } + + if (seed) { + // Reintegrate element matches to eliminate the need for sorting + if (matchedCount > 0) { + while (i--) { + if (!(unmatched[i] || setMatched[i])) { + setMatched[i] = pop.call(results); + } + } + } + + // Discard index placeholder values to get only actual matches + setMatched = condense(setMatched); + } + + // Add matches to results + push.apply(results, setMatched); + + // Seedless set matches succeeding multiple successful matchers stipulate sorting + if ( + outermost && + !seed && + setMatched.length > 0 && + matchedCount + setMatchers.length > 1 + ) { + Sizzle.uniqueSort(results); + } + } + + // Override manipulation of globals by nested matchers + if (outermost) { + dirruns = dirrunsUnique; + outermostContext = contextBackup; + } + + return unmatched; + }; + + return bySet ? markFunction(superMatcher) : superMatcher; + } + + compile = Sizzle.compile = function( + selector, + group /* Internal Use Only */ + ) { + var i, + setMatchers = [], + elementMatchers = [], + cached = compilerCache[selector + ' ']; + + if (!cached) { + // Generate a function of recursive functions that can be used to check each element + if (!group) { + group = tokenize(selector); + } + i = group.length; + while (i--) { + cached = matcherFromTokens(group[i]); + if (cached[expando]) { + setMatchers.push(cached); + } else { + elementMatchers.push(cached); + } + } + + // Cache the compiled function + cached = compilerCache( + selector, + matcherFromGroupMatchers(elementMatchers, setMatchers) + ); + } + return cached; + }; + + function multipleContexts(selector, contexts, results) { + var i = 0, + len = contexts.length; + for (; i < len; i++) { + Sizzle(selector, contexts[i], results); + } + return results; + } + + function select(selector, context, results, seed) { + var i, + tokens, + token, + type, + find, + match = tokenize(selector); + + if (!seed) { + // Try to minimize operations if there is only one group + if (match.length === 1) { + // Take a shortcut and set the context if the root selector is an ID + tokens = match[0] = match[0].slice(0); + if ( + tokens.length > 2 && + (token = tokens[0]).type === 'ID' && + context.nodeType === 9 && + !documentIsXML && + Expr.relative[tokens[1].type] + ) { + context = (Expr.find['ID']( + token.matches[0].replace(runescape, funescape), + context + ) || [])[0]; + if (!context) { + return results; + } + + selector = selector.slice(tokens.shift().value.length); + } + + // Fetch a seed set for right-to-left matching + i = matchExpr['needsContext'].test(selector) ? 0 : tokens.length; + while (i--) { + token = tokens[i]; + + // Abort if we hit a combinator + if (Expr.relative[(type = token.type)]) { + break; + } + if ((find = Expr.find[type])) { + // Search, expanding context for leading sibling combinators + if ( + (seed = find( + token.matches[0].replace(runescape, funescape), + (rsibling.test(tokens[0].type) && context.parentNode) || + context + )) + ) { + // If seed is empty or no tokens remain, we can return early + tokens.splice(i, 1); + selector = seed.length && toSelector(tokens); + if (!selector) { + push.apply(results, seed); + return results; + } + + break; + } + } + } + } + } + + // Compile and execute a filtering function + // Provide `match` to avoid retokenization if we modified the selector above + compile(selector, match)( + seed, + context, + documentIsXML, + results, + rsibling.test(selector) + ); + return results; + } + + // Deprecated + Expr.pseudos['nth'] = Expr.pseudos['eq']; + + // Easy API for creating new setFilters + function setFilters() {} + setFilters.prototype = Expr.filters = Expr.pseudos; + Expr.setFilters = new setFilters(); + + // Check sort stability + support.sortStable = + expando + .split('') + .sort(sortOrder) + .join('') === expando; + + // Initialize with the default document + setDocument(); + + // Always assume the presence of duplicates if sort doesn't + // pass them to our comparison function (as in Google Chrome). + [0, 0].sort(sortOrder); + support.detectDuplicates = hasDuplicate; + + // Override sizzle attribute retrieval + Sizzle.attr = jQuery.attr; + jQuery.find = Sizzle; + jQuery.expr = Sizzle.selectors; + jQuery.expr[':'] = jQuery.expr.pseudos; + jQuery.unique = Sizzle.uniqueSort; + jQuery.text = Sizzle.getText; + jQuery.isXMLDoc = Sizzle.isXML; + jQuery.contains = Sizzle.contains; + })(window); + var runtil = /Until$/, + rparentsprev = /^(?:parents|prev(?:Until|All))/, + isSimple = /^.[^:#\[\.,]*$/, + rneedsContext = jQuery.expr.match.needsContext, + // methods guaranteed to produce a unique set when starting from a unique set + guaranteedUnique = { + children: true, + contents: true, + next: true, + prev: true, + }; + + jQuery.fn.extend({ + find: function(selector) { + var self, + matched, + i, + l = this.length; + + if (typeof selector !== 'string') { + self = this; + return this.pushStack( + jQuery(selector).filter(function() { + for (i = 0; i < l; i++) { + if (jQuery.contains(self[i], this)) { + return true; + } + } + }) + ); + } + + matched = []; + for (i = 0; i < l; i++) { + jQuery.find(selector, this[i], matched); + } + + // Needed because $( selector, context ) becomes $( context ).find( selector ) + matched = this.pushStack(l > 1 ? jQuery.unique(matched) : matched); + matched.selector = (this.selector ? this.selector + ' ' : '') + selector; + return matched; + }, + + has: function(target) { + var targets = jQuery(target, this), + l = targets.length; + + return this.filter(function() { + var i = 0; + for (; i < l; i++) { + if (jQuery.contains(this, targets[i])) { + return true; + } + } + }); + }, + + not: function(selector) { + return this.pushStack(winnow(this, selector, false)); + }, + + filter: function(selector) { + return this.pushStack(winnow(this, selector, true)); + }, + + is: function(selector) { + return ( + !!selector && + (typeof selector === 'string' + ? // If this is a positional/relative selector, check membership in the returned set + // so $("p:first").is("p:last") won't return true for a doc with two "p". + rneedsContext.test(selector) + ? jQuery(selector, this.context).index(this[0]) >= 0 + : jQuery.filter(selector, this).length > 0 + : this.filter(selector).length > 0) + ); + }, + + closest: function(selectors, context) { + var cur, + i = 0, + l = this.length, + matched = [], + pos = + rneedsContext.test(selectors) || typeof selectors !== 'string' + ? jQuery(selectors, context || this.context) + : 0; + + for (; i < l; i++) { + for (cur = this[i]; cur && cur !== context; cur = cur.parentNode) { + // Always skip document fragments + if ( + cur.nodeType < 11 && + (pos + ? pos.index(cur) > -1 + : // Don't pass non-elements to Sizzle + cur.nodeType === 1 && + jQuery.find.matchesSelector(cur, selectors)) + ) { + cur = matched.push(cur); + break; + } + } + } + + return this.pushStack( + matched.length > 1 ? jQuery.unique(matched) : matched + ); + }, + + // Determine the position of an element within + // the matched set of elements + index: function(elem) { + // No argument, return index in parent + if (!elem) { + return this[0] && this[0].parentNode + ? this.first().prevAll().length + : -1; + } + + // index in selector + if (typeof elem === 'string') { + return core_indexOf.call(jQuery(elem), this[0]); + } + + // Locate the position of the desired element + return core_indexOf.call( + this, + + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem + ); + }, + + add: function(selector, context) { + var set = + typeof selector === 'string' + ? jQuery(selector, context) + : jQuery.makeArray( + selector && selector.nodeType ? [selector] : selector + ), + all = jQuery.merge(this.get(), set); + + return this.pushStack(jQuery.unique(all)); + }, + + addBack: function(selector) { + return this.add( + selector == null ? this.prevObject : this.prevObject.filter(selector) + ); + }, + }); + + function sibling(cur, dir) { + while ((cur = cur[dir]) && cur.nodeType !== 1) {} + + return cur; + } + + jQuery.each( + { + parent: function(elem) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function(elem) { + return jQuery.dir(elem, 'parentNode'); + }, + parentsUntil: function(elem, i, until) { + return jQuery.dir(elem, 'parentNode', until); + }, + next: function(elem) { + return sibling(elem, 'nextSibling'); + }, + prev: function(elem) { + return sibling(elem, 'previousSibling'); + }, + nextAll: function(elem) { + return jQuery.dir(elem, 'nextSibling'); + }, + prevAll: function(elem) { + return jQuery.dir(elem, 'previousSibling'); + }, + nextUntil: function(elem, i, until) { + return jQuery.dir(elem, 'nextSibling', until); + }, + prevUntil: function(elem, i, until) { + return jQuery.dir(elem, 'previousSibling', until); + }, + siblings: function(elem) { + return jQuery.sibling((elem.parentNode || {}).firstChild, elem); + }, + children: function(elem) { + return jQuery.sibling(elem.firstChild); + }, + contents: function(elem) { + return jQuery.nodeName(elem, 'iframe') + ? elem.contentDocument || elem.contentWindow.document + : jQuery.merge([], elem.childNodes); + }, + }, + function(name, fn) { + jQuery.fn[name] = function(until, selector) { + var matched = jQuery.map(this, fn, until); + + if (!runtil.test(name)) { + selector = until; + } + + if (selector && typeof selector === 'string') { + matched = jQuery.filter(selector, matched); + } + + if (this.length > 1) { + if (!guaranteedUnique[name]) { + jQuery.unique(matched); + } + + if (rparentsprev.test(name)) { + matched.reverse(); + } + } + + return this.pushStack(matched); + }; + } + ); + + jQuery.extend({ + filter: function(expr, elems, not) { + if (not) { + expr = ':not(' + expr + ')'; + } + + return elems.length === 1 + ? jQuery.find.matchesSelector(elems[0], expr) ? [elems[0]] : [] + : jQuery.find.matches(expr, elems); + }, + + dir: function(elem, dir, until) { + var matched = [], + truncate = until !== undefined; + + while ((elem = elem[dir]) && elem.nodeType !== 9) { + if (elem.nodeType === 1) { + if (truncate && jQuery(elem).is(until)) { + break; + } + matched.push(elem); + } + } + return matched; + }, + + sibling: function(n, elem) { + var matched = []; + + for (; n; n = n.nextSibling) { + if (n.nodeType === 1 && n !== elem) { + matched.push(n); + } + } + + return matched; + }, + }); + + // Implement the identical functionality for filter and not + function winnow(elements, qualifier, keep) { + // Can't pass null or undefined to indexOf in Firefox 4 + // Set to 0 to skip string check + qualifier = qualifier || 0; + + var filtered; + + if (jQuery.isFunction(qualifier)) { + return jQuery.grep(elements, function(elem, i) { + var retVal = !!qualifier.call(elem, i, elem); + return retVal === keep; + }); + } + + if (qualifier.nodeType) { + return jQuery.grep(elements, function(elem) { + return (elem === qualifier) === keep; + }); + } + + if (typeof qualifier === 'string') { + filtered = jQuery.grep(elements, function(elem) { + return elem.nodeType === 1; + }); + + if (isSimple.test(qualifier)) { + return jQuery.filter(qualifier, filtered, !keep); + } + + qualifier = jQuery.filter(qualifier, filtered); + } + + return jQuery.grep(elements, function(elem) { + return core_indexOf.call(qualifier, elem) >= 0 === keep; + }); + } + var rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + rtagName = /<([\w:]+)/, + rhtml = /<|&#?\w+;/, + rnoInnerhtml = /<(?:script|style|link)/i, + manipulation_rcheckableType = /^(?:checkbox|radio)$/i, + // checked="checked" or checked + rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, + rscriptType = /^$|\/(?:java|ecma)script/i, + rscriptTypeMasked = /^true\/(.*)/, + rcleanScript = /^\s*\s*$/g, + // We have to close these tags to support XHTML (#13200) + wrapMap = { + // Support: IE 9 + option: [1, "'], + + thead: [1, '', '
    '], + tr: [2, '', '
    '], + td: [3, '', '
    '], + + _default: [0, '', ''], + }; + + // Support: IE 9 + wrapMap.optgroup = wrapMap.option; + + wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.col = + wrapMap.thead; + wrapMap.th = wrapMap.td; + + jQuery.fn.extend({ + text: function(value) { + return jQuery.access( + this, + function(value) { + return value === undefined + ? jQuery.text(this) + : this.empty().append( + ((this[0] && this[0].ownerDocument) || document).createTextNode( + value + ) + ); + }, + null, + value, + arguments.length + ); + }, + + wrapAll: function(html) { + var wrap; + + if (jQuery.isFunction(html)) { + return this.each(function(i) { + jQuery(this).wrapAll(html.call(this, i)); + }); + } + + if (this[0]) { + // The elements to wrap the target around + wrap = jQuery(html, this[0].ownerDocument) + .eq(0) + .clone(true); + + if (this[0].parentNode) { + wrap.insertBefore(this[0]); + } + + wrap + .map(function() { + var elem = this; + + while (elem.firstElementChild) { + elem = elem.firstElementChild; + } + + return elem; + }) + .append(this); + } + + return this; + }, + + wrapInner: function(html) { + if (jQuery.isFunction(html)) { + return this.each(function(i) { + jQuery(this).wrapInner(html.call(this, i)); + }); + } + + return this.each(function() { + var self = jQuery(this), + contents = self.contents(); + + if (contents.length) { + contents.wrapAll(html); + } else { + self.append(html); + } + }); + }, + + wrap: function(html) { + var isFunction = jQuery.isFunction(html); + + return this.each(function(i) { + jQuery(this).wrapAll(isFunction ? html.call(this, i) : html); + }); + }, + + unwrap: function() { + return this.parent() + .each(function() { + if (!jQuery.nodeName(this, 'body')) { + jQuery(this).replaceWith(this.childNodes); + } + }) + .end(); + }, + + append: function() { + return this.domManip(arguments, true, function(elem) { + if ( + this.nodeType === 1 || + this.nodeType === 11 || + this.nodeType === 9 + ) { + this.appendChild(elem); + } + }); + }, + + prepend: function() { + return this.domManip(arguments, true, function(elem) { + if ( + this.nodeType === 1 || + this.nodeType === 11 || + this.nodeType === 9 + ) { + this.insertBefore(elem, this.firstChild); + } + }); + }, + + before: function() { + return this.domManip(arguments, false, function(elem) { + if (this.parentNode) { + this.parentNode.insertBefore(elem, this); + } + }); + }, + + after: function() { + return this.domManip(arguments, false, function(elem) { + if (this.parentNode) { + this.parentNode.insertBefore(elem, this.nextSibling); + } + }); + }, + + // keepData is for internal use only--do not document + remove: function(selector, keepData) { + var elem, + i = 0, + l = this.length; + + for (; i < l; i++) { + elem = this[i]; + + if (!selector || jQuery.filter(selector, [elem]).length > 0) { + if (!keepData && elem.nodeType === 1) { + jQuery.cleanData(getAll(elem)); + } + + if (elem.parentNode) { + if (keepData && jQuery.contains(elem.ownerDocument, elem)) { + setGlobalEval(getAll(elem, 'script')); + } + elem.parentNode.removeChild(elem); + } + } + } + + return this; + }, + + empty: function() { + var elem, + i = 0, + l = this.length; + + for (; i < l; i++) { + elem = this[i]; + + if (elem.nodeType === 1) { + // Prevent memory leaks + jQuery.cleanData(getAll(elem, false)); + + // Remove any remaining nodes + elem.textContent = ''; + } + } + + return this; + }, + + clone: function(dataAndEvents, deepDataAndEvents) { + dataAndEvents = dataAndEvents == null ? false : dataAndEvents; + deepDataAndEvents = + deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; + + return this.map(function() { + return jQuery.clone(this, dataAndEvents, deepDataAndEvents); + }); + }, + + html: function(value) { + return jQuery.access( + this, + function(value) { + var elem = this[0] || {}, + i = 0, + l = this.length; + + if (value === undefined && elem.nodeType === 1) { + return elem.innerHTML; + } + + // See if we can take a shortcut and just use innerHTML + if ( + typeof value === 'string' && + !rnoInnerhtml.test(value) && + !wrapMap[(rtagName.exec(value) || ['', ''])[1].toLowerCase()] + ) { + value = value.replace(rxhtmlTag, '<$1>'); + + try { + for (; i < l; i++) { + elem = this[i] || {}; + + // Remove element nodes and prevent memory leaks + if (elem.nodeType === 1) { + jQuery.cleanData(getAll(elem, false)); + elem.innerHTML = value; + } + } + + elem = 0; + + // If using innerHTML throws an exception, use the fallback method + } catch (e) {} + } + + if (elem) { + this.empty().append(value); + } + }, + null, + value, + arguments.length + ); + }, + + replaceWith: function(value) { + var isFunction = jQuery.isFunction(value); + + // Make sure that the elements are removed from the DOM before they are inserted + // this can help fix replacing a parent with child elements + if (!isFunction && typeof value !== 'string') { + value = jQuery(value) + .not(this) + .detach(); + } + + return value !== '' + ? this.domManip([value], true, function(elem) { + var next = this.nextSibling, + parent = this.parentNode; + + if (parent) { + jQuery(this).remove(); + parent.insertBefore(elem, next); + } + }) + : this.remove(); + }, + + detach: function(selector) { + return this.remove(selector, true); + }, + + domManip: function(args, table, callback) { + // Flatten any nested arrays + args = core_concat.apply([], args); + + var fragment, + first, + scripts, + hasScripts, + node, + doc, + i = 0, + l = this.length, + set = this, + iNoClone = l - 1, + value = args[0], + isFunction = jQuery.isFunction(value); + + // We can't cloneNode fragments that contain checked, in WebKit + if ( + isFunction || + !( + l <= 1 || + typeof value !== 'string' || + jQuery.support.checkClone || + !rchecked.test(value) + ) + ) { + return this.each(function(index) { + var self = set.eq(index); + if (isFunction) { + args[0] = value.call(this, index, table ? self.html() : undefined); + } + self.domManip(args, table, callback); + }); + } + + if (l) { + fragment = jQuery.buildFragment( + args, + this[0].ownerDocument, + false, + this + ); + first = fragment.firstChild; + + if (fragment.childNodes.length === 1) { + fragment = first; + } + + if (first) { + table = table && jQuery.nodeName(first, 'tr'); + scripts = jQuery.map(getAll(fragment, 'script'), disableScript); + hasScripts = scripts.length; + + // Use the original fragment for the last item instead of the first because it can end up + // being emptied incorrectly in certain situations (#8070). + for (; i < l; i++) { + node = fragment; + + if (i !== iNoClone) { + node = jQuery.clone(node, true, true); + + // Keep references to cloned scripts for later restoration + if (hasScripts) { + // Support: QtWebKit + // jQuery.merge because core_push.apply(_, arraylike) throws + jQuery.merge(scripts, getAll(node, 'script')); + } + } + + callback.call( + table && jQuery.nodeName(this[i], 'table') + ? findOrAppend(this[i], 'tbody') + : this[i], + node, + i + ); + } + + if (hasScripts) { + doc = scripts[scripts.length - 1].ownerDocument; + + // Reenable scripts + jQuery.map(scripts, restoreScript); + + // Evaluate executable scripts on first document insertion + for (i = 0; i < hasScripts; i++) { + node = scripts[i]; + if ( + rscriptType.test(node.type || '') && + !data_priv.access(node, 'globalEval') && + jQuery.contains(doc, node) + ) { + if (node.src) { + // Hope ajax is available... + jQuery.ajax({ + url: node.src, + type: 'GET', + dataType: 'script', + async: false, + global: false, + throws: true, + }); + } else { + jQuery.globalEval(node.textContent.replace(rcleanScript, '')); + } + } + } + } + } + } + + return this; + }, + }); + + jQuery.each( + { + appendTo: 'append', + prependTo: 'prepend', + insertBefore: 'before', + insertAfter: 'after', + replaceAll: 'replaceWith', + }, + function(name, original) { + jQuery.fn[name] = function(selector) { + var elems, + ret = [], + insert = jQuery(selector), + last = insert.length - 1, + i = 0; + + for (; i <= last; i++) { + elems = i === last ? this : this.clone(true); + jQuery(insert[i])[original](elems); + + // Support: QtWebKit + // .get() because core_push.apply(_, arraylike) throws + core_push.apply(ret, elems.get()); + } + + return this.pushStack(ret); + }; + } + ); + + jQuery.extend({ + clone: function(elem, dataAndEvents, deepDataAndEvents) { + var i, + l, + srcElements, + destElements, + clone = elem.cloneNode(true), + inPage = jQuery.contains(elem.ownerDocument, elem); + + // Support: IE >=9 + // Fix Cloning issues + if ( + !jQuery.support.noCloneChecked && + (elem.nodeType === 1 || elem.nodeType === 11) && + !jQuery.isXMLDoc(elem) + ) { + // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 + destElements = getAll(clone); + srcElements = getAll(elem); + + for (i = 0, l = srcElements.length; i < l; i++) { + fixInput(srcElements[i], destElements[i]); + } + } + + // Copy the events from the original to the clone + if (dataAndEvents) { + if (deepDataAndEvents) { + srcElements = srcElements || getAll(elem); + destElements = destElements || getAll(clone); + + for (i = 0, l = srcElements.length; i < l; i++) { + cloneCopyEvent(srcElements[i], destElements[i]); + } + } else { + cloneCopyEvent(elem, clone); + } + } + + // Preserve script evaluation history + destElements = getAll(clone, 'script'); + if (destElements.length > 0) { + setGlobalEval(destElements, !inPage && getAll(elem, 'script')); + } + + // Return the cloned set + return clone; + }, + + buildFragment: function(elems, context, scripts, selection) { + var elem, + tmp, + tag, + wrap, + contains, + j, + i = 0, + l = elems.length, + fragment = context.createDocumentFragment(), + nodes = []; + + for (; i < l; i++) { + elem = elems[i]; + + if (elem || elem === 0) { + // Add nodes directly + if (jQuery.type(elem) === 'object') { + // Support: QtWebKit + // jQuery.merge because core_push.apply(_, arraylike) throws + jQuery.merge(nodes, elem.nodeType ? [elem] : elem); + + // Convert non-html into a text node + } else if (!rhtml.test(elem)) { + nodes.push(context.createTextNode(elem)); + + // Convert html into DOM nodes + } else { + tmp = tmp || fragment.appendChild(context.createElement('div')); + + // Deserialize a standard representation + tag = (rtagName.exec(elem) || ['', ''])[1].toLowerCase(); + wrap = wrapMap[tag] || wrapMap._default; + tmp.innerHTML = + wrap[1] + elem.replace(rxhtmlTag, '<$1>') + wrap[2]; + + // Descend through wrappers to the right content + j = wrap[0]; + while (j--) { + tmp = tmp.firstChild; + } + + // Support: QtWebKit + // jQuery.merge because core_push.apply(_, arraylike) throws + jQuery.merge(nodes, tmp.childNodes); + + // Remember the top-level container + tmp = fragment.firstChild; + + // Fixes #12346 + // Support: Webkit, IE + tmp.textContent = ''; + } + } + } + + // Remove wrapper from fragment + fragment.textContent = ''; + + i = 0; + while ((elem = nodes[i++])) { + // #4087 - If origin and destination elements are the same, and this is + // that element, do not do anything + if (selection && jQuery.inArray(elem, selection) !== -1) { + continue; + } + + contains = jQuery.contains(elem.ownerDocument, elem); + + // Append to fragment + tmp = getAll(fragment.appendChild(elem), 'script'); + + // Preserve script evaluation history + if (contains) { + setGlobalEval(tmp); + } + + // Capture executables + if (scripts) { + j = 0; + while ((elem = tmp[j++])) { + if (rscriptType.test(elem.type || '')) { + scripts.push(elem); + } + } + } + } + + return fragment; + }, + + cleanData: function(elems, /* internal */ acceptData) { + var data, + elem, + type, + l = elems.length, + i = 0, + special = jQuery.event.special; + + for (; i < l; i++) { + elem = elems[i]; + + if (acceptData || jQuery.acceptData(elem)) { + data = data_priv.access(elem); + + if (data) { + for (type in data.events) { + if (special[type]) { + jQuery.event.remove(elem, type); + + // This is a shortcut to avoid jQuery.event.remove's overhead + } else { + jQuery.removeEvent(elem, type, data.handle); + } + } + } + } + // Discard any remaining `private` and `user` data + data_discard(elem); + } + }, + }); + + function findOrAppend(elem, tag) { + return ( + elem.getElementsByTagName(tag)[0] || + elem.appendChild(elem.ownerDocument.createElement(tag)) + ); + } + + // Replace/restore the type attribute of script elements for safe DOM manipulation + function disableScript(elem) { + var attr = elem.getAttributeNode('type'); + elem.type = (attr && attr.specified) + '/' + elem.type; + return elem; + } + function restoreScript(elem) { + var match = rscriptTypeMasked.exec(elem.type); + + if (match) { + elem.type = match[1]; + } else { + elem.removeAttribute('type'); + } + + return elem; + } + + // Mark scripts as having already been evaluated + function setGlobalEval(elems, refElements) { + var l = elems.length, + i = 0; + + for (; i < l; i++) { + data_priv.set( + elems[i], + 'globalEval', + !refElements || data_priv.get(refElements[i], 'globalEval') + ); + } + } + + function cloneCopyEvent(src, dest) { + var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; + + if (dest.nodeType !== 1) { + return; + } + + // 1. Copy private data: events, handlers, etc. + if (data_priv.hasData(src)) { + pdataOld = data_priv.access(src); + pdataCur = jQuery.extend({}, pdataOld); + events = pdataOld.events; + + data_priv.set(dest, pdataCur); + + if (events) { + delete pdataCur.handle; + pdataCur.events = {}; + + for (type in events) { + for (i = 0, l = events[type].length; i < l; i++) { + jQuery.event.add(dest, type, events[type][i]); + } + } + } + } + + // 2. Copy user data + if (data_user.hasData(src)) { + udataOld = data_user.access(src); + udataCur = jQuery.extend({}, udataOld); + + data_user.set(dest, udataCur); + } + } + + function getAll(context, tag) { + var ret = context.getElementsByTagName + ? context.getElementsByTagName(tag || '*') + : context.querySelectorAll ? context.querySelectorAll(tag || '*') : []; + + return tag === undefined || (tag && jQuery.nodeName(context, tag)) + ? jQuery.merge([context], ret) + : ret; + } + + // Support: IE >= 9 + function fixInput(src, dest) { + var nodeName = dest.nodeName.toLowerCase(); + + // Fails to persist the checked state of a cloned checkbox or radio button. + if (nodeName === 'input' && manipulation_rcheckableType.test(src.type)) { + dest.checked = src.checked; + + // Fails to return the selected option to the default selected state when cloning options + } else if (nodeName === 'input' || nodeName === 'textarea') { + dest.defaultValue = src.defaultValue; + } + } + var curCSS, + iframe, + // swappable if display is none or starts with table except "table", "table-cell", or "table-caption" + // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display + rdisplayswap = /^(none|table(?!-c[ea]).+)/, + rmargin = /^margin/, + rnumsplit = new RegExp('^(' + core_pnum + ')(.*)$', 'i'), + rnumnonpx = new RegExp('^(' + core_pnum + ')(?!px)[a-z%]+$', 'i'), + rrelNum = new RegExp('^([+-])=(' + core_pnum + ')', 'i'), + elemdisplay = { BODY: 'block' }, + cssShow = { position: 'absolute', visibility: 'hidden', display: 'block' }, + cssNormalTransform = { + letterSpacing: 0, + fontWeight: 400, + }, + cssExpand = ['Top', 'Right', 'Bottom', 'Left'], + cssPrefixes = ['Webkit', 'O', 'Moz', 'ms']; + + // return a css property mapped to a potentially vendor prefixed property + function vendorPropName(style, name) { + // shortcut for names that are not vendor prefixed + if (name in style) { + return name; + } + + // check for vendor prefixed names + var capName = name.charAt(0).toUpperCase() + name.slice(1), + origName = name, + i = cssPrefixes.length; + + while (i--) { + name = cssPrefixes[i] + capName; + if (name in style) { + return name; + } + } + + return origName; + } + + function isHidden(elem, el) { + // isHidden might be called from jQuery#filter function; + // in that case, element will be second argument + elem = el || elem; + return ( + jQuery.css(elem, 'display') === 'none' || + !jQuery.contains(elem.ownerDocument, elem) + ); + } + + // NOTE: we've included the "window" in window.getComputedStyle + // because jsdom on node.js will break without it. + function getStyles(elem) { + return window.getComputedStyle(elem, null); + } + + function showHide(elements, show) { + var display, + elem, + hidden, + values = [], + index = 0, + length = elements.length; + + for (; index < length; index++) { + elem = elements[index]; + if (!elem.style) { + continue; + } + + values[index] = jQuery._data(elem, 'olddisplay'); + display = elem.style.display; + if (show) { + // Reset the inline display of this element to learn if it is + // being hidden by cascaded rules or not + if (!values[index] && display === 'none') { + elem.style.display = ''; + } + + // Set elements which have been overridden with display: none + // in a stylesheet to whatever the default browser style is + // for such an element + if (elem.style.display === '' && isHidden(elem)) { + values[index] = jQuery._data( + elem, + 'olddisplay', + css_defaultDisplay(elem.nodeName) + ); + } + } else { + if (!values[index]) { + hidden = isHidden(elem); + + if ((display && display !== 'none') || !hidden) { + jQuery._data( + elem, + 'olddisplay', + hidden ? display : jQuery.css(elem, 'display') + ); + } + } + } + } + + // Set the display of most of the elements in a second loop + // to avoid the constant reflow + for (index = 0; index < length; index++) { + elem = elements[index]; + if (!elem.style) { + continue; + } + if (!show || elem.style.display === 'none' || elem.style.display === '') { + elem.style.display = show ? values[index] || '' : 'none'; + } + } + + return elements; + } + + jQuery.fn.extend({ + css: function(name, value) { + return jQuery.access( + this, + function(elem, name, value) { + var styles, + len, + map = {}, + i = 0; + + if (jQuery.isArray(name)) { + styles = getStyles(elem); + len = name.length; + + for (; i < len; i++) { + map[name[i]] = jQuery.css(elem, name[i], false, styles); + } + + return map; + } + + return value !== undefined + ? jQuery.style(elem, name, value) + : jQuery.css(elem, name); + }, + name, + value, + arguments.length > 1 + ); + }, + show: function() { + return showHide(this, true); + }, + hide: function() { + return showHide(this); + }, + toggle: function(state) { + var bool = typeof state === 'boolean'; + + return this.each(function() { + if (bool ? state : isHidden(this)) { + jQuery(this).show(); + } else { + jQuery(this).hide(); + } + }); + }, + }); + + jQuery.extend({ + // Add in style property hooks for overriding the default + // behavior of getting and setting a style property + cssHooks: { + opacity: { + get: function(elem, computed) { + if (computed) { + // We should always get a number back from opacity + var ret = curCSS(elem, 'opacity'); + return ret === '' ? '1' : ret; + } + }, + }, + }, + + // Exclude the following css properties to add px + cssNumber: { + columnCount: true, + fillOpacity: true, + fontWeight: true, + lineHeight: true, + opacity: true, + orphans: true, + widows: true, + zIndex: true, + zoom: true, + }, + + // Add in properties whose names you wish to fix before + // setting or getting the value + cssProps: { + // normalize float css property + float: 'cssFloat', + }, + + // Get and set the style property on a DOM Node + style: function(elem, name, value, extra) { + // Don't set styles on text and comment nodes + if (!elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style) { + return; + } + + // Make sure that we're working with the right name + var ret, + type, + hooks, + origName = jQuery.camelCase(name), + style = elem.style; + + name = + jQuery.cssProps[origName] || + (jQuery.cssProps[origName] = vendorPropName(style, origName)); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[name] || jQuery.cssHooks[origName]; + + // Check if we're setting a value + if (value !== undefined) { + type = typeof value; + + // convert relative number strings (+= or -=) to relative numbers. #7345 + if (type === 'string' && (ret = rrelNum.exec(value))) { + value = (ret[1] + 1) * ret[2] + parseFloat(jQuery.css(elem, name)); + // Fixes bug #9237 + type = 'number'; + } + + // Make sure that NaN and null values aren't set. See: #7116 + if (value == null || (type === 'number' && isNaN(value))) { + return; + } + + // If a number was passed in, add 'px' to the (except for certain CSS properties) + if (type === 'number' && !jQuery.cssNumber[origName]) { + value += 'px'; + } + + // Fixes #8908, it can be done more correctly by specifying setters in cssHooks, + // but it would mean to define eight (for every problematic property) identical functions + if ( + !jQuery.support.clearCloneStyle && + value === '' && + name.indexOf('background') === 0 + ) { + style[name] = 'inherit'; + } + + // If a hook was provided, use that value, otherwise just set the specified value + if ( + !hooks || + !('set' in hooks) || + (value = hooks.set(elem, value, extra)) !== undefined + ) { + style[name] = value; + } + } else { + // If a hook was provided get the non-computed value from there + if ( + hooks && + 'get' in hooks && + (ret = hooks.get(elem, false, extra)) !== undefined + ) { + return ret; + } + + // Otherwise just get the value from the style object + return style[name]; + } + }, + + css: function(elem, name, extra, styles) { + var val, + num, + hooks, + origName = jQuery.camelCase(name); + + // Make sure that we're working with the right name + name = + jQuery.cssProps[origName] || + (jQuery.cssProps[origName] = vendorPropName(elem.style, origName)); + + // gets hook for the prefixed version + // followed by the unprefixed version + hooks = jQuery.cssHooks[name] || jQuery.cssHooks[origName]; + + // If a hook was provided get the computed value from there + if (hooks && 'get' in hooks) { + val = hooks.get(elem, true, extra); + } + + // Otherwise, if a way to get the computed value exists, use that + if (val === undefined) { + val = curCSS(elem, name, styles); + } + + //convert "normal" to computed value + if (val === 'normal' && name in cssNormalTransform) { + val = cssNormalTransform[name]; + } + + // Return, converting to number if forced or a qualifier was provided and val looks numeric + if (extra === '' || extra) { + num = parseFloat(val); + return extra === true || jQuery.isNumeric(num) ? num || 0 : val; + } + return val; + }, + + // A method for quickly swapping in/out CSS properties to get correct calculations + swap: function(elem, options, callback, args) { + var ret, + name, + old = {}; + + // Remember the old values, and insert the new ones + for (name in options) { + old[name] = elem.style[name]; + elem.style[name] = options[name]; + } + + ret = callback.apply(elem, args || []); + + // Revert the old values + for (name in options) { + elem.style[name] = old[name]; + } + + return ret; + }, + }); + + curCSS = function(elem, name, _computed) { + var width, + minWidth, + maxWidth, + computed = _computed || getStyles(elem), + // Support: IE9 + // getPropertyValue is only needed for .css('filter') in IE9, see #12537 + ret = computed + ? computed.getPropertyValue(name) || computed[name] + : undefined, + style = elem.style; + + if (computed) { + if (ret === '' && !jQuery.contains(elem.ownerDocument, elem)) { + ret = jQuery.style(elem, name); + } + + // Support: Chrome <17, Safari 5.1 + // A tribute to the "awesome hack by Dean Edwards" + // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right + // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels + // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values + if (rnumnonpx.test(ret) && rmargin.test(name)) { + // Remember the original values + width = style.width; + minWidth = style.minWidth; + maxWidth = style.maxWidth; + + // Put in the new values to get a computed value out + style.minWidth = style.maxWidth = style.width = ret; + ret = computed.width; + + // Revert the changed values + style.width = width; + style.minWidth = minWidth; + style.maxWidth = maxWidth; + } + } + + return ret; + }; + + function setPositiveNumber(elem, value, subtract) { + var matches = rnumsplit.exec(value); + return matches + ? // Guard against undefined "subtract", e.g., when used as in cssHooks + Math.max(0, matches[1] - (subtract || 0)) + (matches[2] || 'px') + : value; + } + + function augmentWidthOrHeight(elem, name, extra, isBorderBox, styles) { + var i = + extra === (isBorderBox ? 'border' : 'content') + ? // If we already have the right measurement, avoid augmentation + 4 + : // Otherwise initialize for horizontal or vertical properties + name === 'width' ? 1 : 0, + val = 0; + + for (; i < 4; i += 2) { + // both box models exclude margin, so add it if we want it + if (extra === 'margin') { + val += jQuery.css(elem, extra + cssExpand[i], true, styles); + } + + if (isBorderBox) { + // border-box includes padding, so remove it if we want content + if (extra === 'content') { + val -= jQuery.css(elem, 'padding' + cssExpand[i], true, styles); + } + + // at this point, extra isn't border nor margin, so remove border + if (extra !== 'margin') { + val -= jQuery.css( + elem, + 'border' + cssExpand[i] + 'Width', + true, + styles + ); + } + } else { + // at this point, extra isn't content, so add padding + val += jQuery.css(elem, 'padding' + cssExpand[i], true, styles); + + // at this point, extra isn't content nor padding, so add border + if (extra !== 'padding') { + val += jQuery.css( + elem, + 'border' + cssExpand[i] + 'Width', + true, + styles + ); + } + } + } + + return val; + } + + function getWidthOrHeight(elem, name, extra) { + // Start with offset property, which is equivalent to the border-box value + var valueIsBorderBox = true, + val = name === 'width' ? elem.offsetWidth : elem.offsetHeight, + styles = getStyles(elem), + isBorderBox = + jQuery.support.boxSizing && + jQuery.css(elem, 'boxSizing', false, styles) === 'border-box'; + + // some non-html elements return undefined for offsetWidth, so check for null/undefined + // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285 + // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668 + if (val <= 0 || val == null) { + // Fall back to computed then uncomputed css if necessary + val = curCSS(elem, name, styles); + if (val < 0 || val == null) { + val = elem.style[name]; + } + + // Computed unit is not pixels. Stop here and return. + if (rnumnonpx.test(val)) { + return val; + } + + // we need the check for style in case a browser which returns unreliable values + // for getComputedStyle silently falls back to the reliable elem.style + valueIsBorderBox = + isBorderBox && + (jQuery.support.boxSizingReliable || val === elem.style[name]); + + // Normalize "", auto, and prepare for extra + val = parseFloat(val) || 0; + } + + // use the active box-sizing model to add/subtract irrelevant styles + return ( + val + + augmentWidthOrHeight( + elem, + name, + extra || (isBorderBox ? 'border' : 'content'), + valueIsBorderBox, + styles + ) + + 'px' + ); + } + + // Try to determine the default display value of an element + function css_defaultDisplay(nodeName) { + var doc = document, + display = elemdisplay[nodeName]; + + if (!display) { + display = actualDisplay(nodeName, doc); + + // If the simple way fails, read from inside an iframe + if (display === 'none' || !display) { + // Use the already-created iframe if possible + iframe = ( + iframe || + jQuery(" + +
    +
    + + Drag to resize Pattern Lab + + +
    +
    +
    + + `; + } + + handleMouseDown(event) { + // capture default data + const self = this; + self.querySelector('.pl-js-resize-handle').classList.add('is-resizing'); + const origClientX = event.clientX; + const origViewportWidth = this.iframeContainer.clientWidth; + + this.fullMode = false; + + // show the cover + this.iframeCover.style.display = 'block'; + + function handleIframeCoverResize(e) { + const viewportWidth = origViewportWidth + 2 * (e.clientX - origClientX); + if ( + viewportWidth > minViewportWidth && + viewportWidth < maxViewportWidth + ) { + self.sizeiframe(viewportWidth, false); + } else if (viewportWidth > maxViewportWidth) { + self.sizeiframe(maxViewportWidth, false); + } else { + self.sizeiframe(minViewportWidth, false); + } + } + + // add the mouse move event and capture data. also update the viewport width + this.iframeCover.addEventListener('mousemove', handleIframeCoverResize); + + document.body.addEventListener('mouseup', function() { + self.iframeCover.removeEventListener( + 'mousemove', + handleIframeCoverResize + ); + self.iframeCover.style.display = 'none'; + self + .querySelector('.pl-js-resize-handle') + .classList.remove('is-resizing'); + }); + + return false; + } + + // updates the nav after the iframed page tells the iframe it's done loading + receiveIframeMessage(event) { + // does the origin sending the message match the current host? if not dev/null the request + if ( + window.location.protocol !== 'file:' && + event.origin !== window.location.protocol + '//' + window.location.host + ) { + return; + } + + let data = {}; + try { + data = + typeof event.data !== 'string' ? event.data : JSON.parse(event.data); + } catch (e) { + // @todo: how do we want to handle exceptions here? + } + + // try to auto-correct for currentPattern data that doesn't always match with url + // workaround for certain pages (especially view all pages) not always matching up internally with the expected current pattern key + if (data.event !== undefined && data.event === 'patternLab.pageLoad') { + try { + const currentPattern = + this.sanitizePatternName(event.data.patternpartial) || + this.getPatternParam(); + + document.title = 'Pattern Lab - ' + currentPattern; + + const addressReplacement = + window.location.protocol === 'file:' + ? null + : window.location.protocol + + '//' + + window.location.host + + window.location.pathname.replace('index.html', '') + + '?p=' + + currentPattern; + + window.history.replaceState( + { + currentPattern: currentPattern, + }, + null, + addressReplacement + ); + + store.dispatch(updateCurrentPattern(currentPattern)); + } catch (error) { + console.log(error); + } + } + } +} + +export { IFrame }; diff --git a/packages/uikit-workshop/src/scripts/lit-components/pl-viewport/pl-viewport.scss b/packages/uikit-workshop/src/scripts/lit-components/pl-viewport/pl-viewport.scss new file mode 100644 index 000000000..c6ea632e2 --- /dev/null +++ b/packages/uikit-workshop/src/scripts/lit-components/pl-viewport/pl-viewport.scss @@ -0,0 +1,248 @@ +/*------------------------------------*\ + #VIEWPORT +\*------------------------------------*/ + +@import '../../../sass/scss/core.scss'; + +/** +* To keep user code and PL code separate, and to make +* resizing the viewport possible, PL contains an iframe +* that houses all user code. +*/ + +$pl-resizer-width: 20px; +$pl-viewport-bg: #F4F4F4; + +.pl-c-viewport-modal-wrapper { + background-color: $pl-viewport-bg; +} + +pl-iframe { + display: flex; + flex-grow: 1; + background-color: $pl-viewport-bg; +} + +/** +* Viewport +* 1) This wrapper div occupies all remaining viewport space after PL's header +*/ +.pl-c-viewport { + display: flex; + flex-direction: column; + width: 100%; + position: relative; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 0; + flex-grow: 1; + transition: height 0.3s ease; + // @supports (position: sticky) { + // top: 0; + // } + + // .pl-c-body--theme-sidebar & { + // top: 0; + // } +} + +/** +* Cover +* 1) This is an invisible div that sits above the iframe and is +* used in JS for manual viewport resizing purposes. +*/ +.pl-c-viewport__cover { + width: 100%; + height: 100%; + display: none; + position: fixed; + top: 0; + left: 0; + z-index: 200; + cursor: move; + pointer-events: auto; // Firefox bug fix +} + +/** +* Viewport iframe wrapper +* 1) This is the container that houses the +* iframe and the manual resize handle +*/ +.pl-c-viewport__iframe-wrapper { + display: flex; + flex-direction: column; + width: 100vw; + max-width: 100vw; + position: relative; + margin: 0 auto; + flex-grow: 1; + -webkit-overflow-scrolling: touch; + min-width: 240px; + max-width: 100vw; + background: $pl-viewport-bg; + padding-right: 0; + padding-left: 0; + // box-shadow: 0 3px 6px rgba(21,22,25,.16), 0 3px 6px rgba(21,22,25,.23); + + &.hay-mode { + transition: all 40s linear; + } + + .pl-c-body--theme-sidebar & { + @media all and (min-width: $pl-bp-med) { + max-width: calc(100vw - #{$pl-sidebar-width}); + width: calc(100vw - #{$pl-sidebar-width}); + } + } +} + +.is-animating { + transition: all 0.2s linear; +} + +/** +* Viewport iframe +* 1) this is the actual