├── .babelrc ├── .gitignore ├── LICENSE ├── README.md ├── TODO.md ├── _config.yml ├── client-bundle └── index.js ├── package-lock.json ├── package.json ├── scaffolds ├── draft.md ├── page.md └── post.md ├── source ├── index.md ├── v1 │ ├── authentication-management │ │ ├── common-patterns.md │ │ ├── guide.md │ │ ├── index.md │ │ └── whats-new.md │ ├── batch-loader │ │ ├── common-patterns.md │ │ ├── guide.md │ │ └── index.md │ ├── feathers-hooks-common │ │ ├── guide.md │ │ └── index.md │ └── feathers-vuex │ │ ├── auth-module.md │ │ ├── common-patterns.md │ │ ├── components.md │ │ ├── guide.md │ │ ├── index.md │ │ ├── mixins.md │ │ ├── model-classes.md │ │ ├── service-module.md │ │ └── vue-plugin.md └── whats-new │ └── index.md ├── themes └── tech-docs │ ├── _config.yml │ ├── layout │ ├── index.ejs │ ├── layout.ejs │ ├── page.ejs │ ├── partials │ │ ├── dropdown_adapters.ejs │ │ ├── dropdown_extensions.ejs │ │ ├── dropdown_frameworks.ejs │ │ ├── header.ejs │ │ ├── main_menu.ejs │ │ ├── sidebar.ejs │ │ ├── toc.ejs │ │ ├── z-ad-text.ejs │ │ ├── z-ad.ejs │ │ ├── z-conf.ejs │ │ ├── z-ecosystem_dropdown.ejs │ │ ├── z-language_dropdown.ejs │ │ ├── z-learn_dropdown.ejs │ │ ├── z-sponsors.ejs │ │ └── z-support_vue_dropdown.ejs │ └── post.ejs │ ├── scripts │ ├── api.js │ ├── get-pages-in-folder.js │ ├── hooks-api.js │ └── inject-feathers-plus.js │ ├── source │ ├── css │ │ ├── _ad.styl │ │ ├── _animations.styl │ │ ├── _common.styl │ │ ├── _demo.styl │ │ ├── _header.styl │ │ ├── _migration.styl │ │ ├── _offline-menu.styl │ │ ├── _settings.styl │ │ ├── _sidebar.styl │ │ ├── _sponsor.styl │ │ ├── _style-guide.styl │ │ ├── _syntax.styl │ │ ├── _team.styl │ │ ├── benchmark.styl │ │ ├── index.styl │ │ ├── page.styl │ │ └── search.styl │ ├── images │ │ ├── icons │ │ │ ├── android-icon-144x144.png │ │ │ ├── android-icon-192x192.png │ │ │ ├── android-icon-36x36.png │ │ │ ├── android-icon-48x48.png │ │ │ ├── android-icon-72x72.png │ │ │ ├── android-icon-96x96.png │ │ │ ├── apple-icon-114x114.png │ │ │ ├── apple-icon-120x120.png │ │ │ ├── apple-icon-144x144.png │ │ │ ├── apple-icon-152x152.png │ │ │ ├── apple-icon-180x180.png │ │ │ ├── apple-icon-57x57.png │ │ │ ├── apple-icon-60x60.png │ │ │ ├── apple-icon-72x72.png │ │ │ ├── apple-icon-76x76.png │ │ │ ├── apple-icon-precomposed.png │ │ │ ├── apple-icon.png │ │ │ ├── browserconfig.xml │ │ │ ├── favicon-16x16.png │ │ │ ├── favicon-32x32.png │ │ │ ├── favicon-96x96.png │ │ │ ├── favicon.ico │ │ │ ├── manifest.json │ │ │ ├── ms-icon-144x144.png │ │ │ ├── ms-icon-150x150.png │ │ │ ├── ms-icon-310x310.png │ │ │ └── ms-icon-70x70.png │ │ └── logo.png │ └── js │ │ ├── common.js │ │ ├── css.escape.js │ │ ├── smooth-scroll.min.js │ │ └── vue.min.js │ └── standards │ ├── hook-api-args-dot-notation.jpg │ ├── hook-api-args-dot-notation.js │ ├── hook-api-args-value-return.jpg │ ├── hook-api-args-value-return.js │ ├── hook-api-args-with-object.jpg │ └── hook-api-args-with-object.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ "add-module-exports" ], 3 | "presets": [ "es2015" ] 4 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | db.json 4 | *.log 5 | node_modules/ 6 | public/ 7 | .deploy*/ 8 | .idea/ 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2017 John J. Szwaronek 3 | All Rights Reserved 4 | 5 | This repository is protected by copyright and distributed under 6 | licenses restricting copying, distribution and decompilation. 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docs.feathers-plus.org 2 | 3 | > Web site for Feathers-Plus. 4 | 5 | **WORK IN PROGRESS.** 6 | 7 | ## Installation 8 | 9 | ``` 10 | npm install docs.feathers-plus.org --save 11 | ``` 12 | 13 | ## Documentation 14 | 15 | Run `hexo server` and point your browser to `localhost:4000`. 16 | 17 | ## License 18 | 19 | Copyright (c) 2017 John J. Szwaronek 20 | 21 | Licensed under the [license](LICENSE). 22 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - hexo-multiple-codeblock 2 | 3 | ==================================== 4 | 5 | console.log("**Deprecated** The ??? will be removed next FeathersJS version. Use ??? instead."); 6 | 7 | ==================================== 8 | 9 | `providers`: 10 | 11 | Type | Value | Description 12 | ---|---|--- 13 | | `socketio` | allow calls by Socket.IO transport 14 | | `primus` | allow calls by Primus transport 15 | | `rest` | allow calls by REST transport 16 | | `external` | allow calls other than from server 17 | | `server` | allow calls from server 18 | 19 | ==================================== 20 | 21 | - **Returns** 22 | 23 | - `{Object} newContext` 24 | 25 | Variable | Type | Description 26 | ---|:---:|---|--- 27 | `newContext` | `Object` | The new context created. 28 | 29 | ==================================== 30 | 31 | - predicates 32 | every.js 33 | is-provider.js 34 | is-not.js 35 | some.js 36 | 37 | 38 | - conditionals 39 | else.iff 40 | iff.js 41 | iff-else.js 42 | unless.js 43 | when 44 | 45 | - data hooks 46 | discard.js 47 | keep.js 48 | lower-case.js 49 | set-now.js 50 | traverse.js 51 | 52 | - data utilities 53 | get-items.js 54 | replace-items.js 55 | 56 | - query object 57 | discard-query.js (remove-query.js) 58 | keep-query.js (pluck-query.js) 59 | 60 | - client/server 61 | params-for-server.js 62 | params-from-client.js 63 | 64 | - dot notation 65 | delete-by-dot.js 66 | exists-by-dot.js 67 | get-by-dot.js 68 | set-by-dot.js 69 | 70 | - service methods 71 | disable-multi-item-change.js 72 | disallow.js 73 | prevent-changes.js 74 | sifter.js 75 | 76 | - misc 77 | stash-before.js 78 | 79 | - relations 80 | populate.js 81 | de-populate.js 82 | serialize.js 83 | 84 | - validate 85 | validate.js 86 | validate-schema.js 87 | 88 | - rest 89 | - set-slug 90 | - thenify-hook.js 91 | 92 | ==================================== 93 | 94 | soft-delete.js 95 | 96 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Hexo Configuration 2 | ## Docs: https://hexo.io/docs/configuration.html 3 | ## Source: https://github.com/hexojs/hexo/ 4 | 5 | # Site 6 | title: Feathers-Plus 7 | subtitle: 8 | description: 9 | author: John J. Szwaronek 10 | language: 11 | timezone: 12 | 13 | # URL 14 | ## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/' 15 | url: http://feathers-plus.github.com 16 | root: / 17 | permalink: :year/:month/:day/:title/ 18 | permalink_defaults: 19 | 20 | # Directory 21 | source_dir: source 22 | public_dir: public 23 | tag_dir: tags 24 | archive_dir: archives 25 | category_dir: categories 26 | code_dir: downloads/code 27 | i18n_dir: :lang 28 | skip_render: 29 | 30 | # Writing 31 | new_post_name: :title.md # File name of new posts 32 | default_layout: post 33 | titlecase: false # Transform title into titlecase 34 | external_link: true # Open external links in new tab 35 | filename_case: 0 36 | render_drafts: false 37 | post_asset_folder: false 38 | relative_link: false 39 | future: true 40 | highlight: 41 | enable: true 42 | line_number: false # was true 43 | auto_detect: false 44 | tab_replace: 45 | 46 | # Home page setting 47 | # path: Root path for your blogs index page. (default = '') 48 | # per_page: Posts displayed per page. (0 = disable pagination) 49 | # order_by: Posts order. (Order by date descending by default) 50 | index_generator: 51 | path: '' 52 | per_page: 10 53 | order_by: -date 54 | 55 | # Category & Tag 56 | default_category: uncategorized 57 | category_map: 58 | tag_map: 59 | 60 | # Date / Time format 61 | ## Hexo uses Moment.js to parse and display date 62 | ## You can customize the date format as defined in 63 | ## http://momentjs.com/docs/#/displaying/format/ 64 | date_format: YYYY-MM-DD 65 | time_format: HH:mm:ss 66 | 67 | # Pagination 68 | ## Set per_page to 0 to disable pagination 69 | per_page: 10 70 | pagination_dir: page 71 | 72 | # Extensions 73 | ## Plugins: https://hexo.io/plugins/ 74 | ## Themes: https://hexo.io/themes/ 75 | theme: tech-docs # was landscape 76 | 77 | # Deployment 78 | ## Docs: https://hexo.io/docs/deployment.html 79 | deploy: 80 | type: git 81 | repository: https://github.com/feathers-plus/feathers-plus.github.com.git 82 | branch: master 83 | -------------------------------------------------------------------------------- /client-bundle/index.js: -------------------------------------------------------------------------------- 1 | ;(function(global) { 2 | global.feathersjs = { 3 | errors: require('@feathersjs/errors'), 4 | }; 5 | }(this)); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@feathers-plus/docs", 3 | "description": "Web site for Feathers-Plus.", 4 | "version": "0.1.0", 5 | "keywords": [ 6 | "feathers", 7 | "feathers-plus", 8 | "hexo", 9 | "technical", 10 | "documentation" 11 | ], 12 | "license": "Copyright (c)", 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/feathers-plus/docs.git" 16 | }, 17 | "author": { 18 | "name": "John J. Szwaronek", 19 | "email": "johnsz9999@gmail.com" 20 | }, 21 | "contributors": [], 22 | "bugs": { 23 | "url": "https://github.com/feathers-plus/docs/issues" 24 | }, 25 | "engines": { 26 | "node": ">= 6.0.0" 27 | }, 28 | "hexo": { 29 | "version": "3.9.0" 30 | }, 31 | "scripts": { 32 | "webpack": "webpack", 33 | "deploy": "hexo deploy", 34 | "generate": "hexo generate", 35 | "server": "hexo server" 36 | }, 37 | "dependencies": { 38 | "hexo": "^3.2.0", 39 | "hexo-deployer-git": "0.3.1", 40 | "hexo-generator-alias": "git+https://github.com/chrisvfritz/vuejs.org-hexo-generator-alias.git", 41 | "hexo-generator-archive": "^0.1.4", 42 | "hexo-generator-category": "^0.1.3", 43 | "hexo-generator-index": "^0.2.0", 44 | "hexo-generator-tag": "^0.2.0", 45 | "hexo-renderer-ejs": "^0.3.0", 46 | "hexo-renderer-marked": "^0.3.0", 47 | "hexo-renderer-stylus": "^0.3.1", 48 | "hexo-server": "^0.2.0" 49 | }, 50 | "devDependencies": { 51 | "@feathersjs/errors": "3.0.0", 52 | "babel-core": "6.26.0", 53 | "babel-loader": "7.1.2", 54 | "babel-plugin-add-module-exports": "0.2.1", 55 | "babel-preset-env": "1.6.1", 56 | "babel-preset-es2015": "6.24.1", 57 | "babili-webpack-plugin": "0.1.2", 58 | "feathers-batchloader": "0.1.1", 59 | "webpack": "3.8.1" 60 | } 61 | } -------------------------------------------------------------------------------- /scaffolds/draft.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | tags: 4 | --- 5 | -------------------------------------------------------------------------------- /scaffolds/page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | date: {{ date }} 4 | --- 5 | -------------------------------------------------------------------------------- /scaffolds/post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | date: {{ date }} 4 | tags: 5 | --- 6 | -------------------------------------------------------------------------------- /source/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Feathers+ 3 | type: frontpage 4 | order: 1 5 | dropdown: 6 | repo: frontpage 7 | index: true 8 | --- 9 | 10 | # 🚨 feathers-hooks-common has moved! 🚨 11 | 12 | As of version `5.0.0`, feathers-hooks-common has a new home at [https://common-hooks.feathersjs.com](https://common-hooks.feathersjs.com). 13 | 14 | [View the new docs](https://common-hooks.feathersjs.com) 15 | -------------------------------------------------------------------------------- /source/v1/authentication-management/common-patterns.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Common Patterns 3 | type: guide 4 | order: 3 5 | dropdown: extensions 6 | repo: authentication-management 7 | --- 8 | 9 | ## ??? 10 | -------------------------------------------------------------------------------- /source/v1/authentication-management/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Guide 3 | type: guide 4 | order: 1 5 | dropdown: extensions 6 | repo: authentication-management 7 | --- 8 | 9 | ## ??? 10 | -------------------------------------------------------------------------------- /source/v1/authentication-management/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API 3 | type: guide 4 | order: 2 5 | dropdown: extensions 6 | repo: authentication-management 7 | --- 8 | 9 | 10 |

Usage

11 | Initializing the module server side: 12 | ``` js 13 | npm install @feathers-plus/authentication-management 14 | 15 | // JS 16 | const auth = require('@feathers/authentication'); 17 | const authManagement = require('@feathers-plus/authentication-management'); 18 | 19 | // authentication and user service should be initialized before 20 | // initializing authManagement 21 | app.configure(authManagement()); 22 | 23 | // allow only signed in users to use passwordChange and identityChange 24 | const isAction = (...args) => hook => args.includes(hook.data.action); 25 | 26 | // TODO: Security for passwordChange and identityChange (and more?) 27 | 28 | app.service('authManagement').before({ 29 | create: [ 30 | hooks.iff(isAction('passwordChange', 'identityChange'), auth.hooks.authenticate('jwt')), 31 | ], 32 | }); 33 | ``` 34 | 35 | This will add the service `authManagement` to your app. To use the service client side, we provide a module which lets you access methods on the service directly. 36 | 37 | Initialize the module client side: 38 | 39 | ``` js 40 | import AuthManagement from '@feathers-plus/authentication-management/lib/client' 41 | 42 | const authManagement = new AuthManagement(app); 43 | ``` 44 | 45 | Now you can call methods like so: 46 | 47 | ```js 48 | authManagement.checkUnique('user@mail.com') 49 | .then(res => { 50 | // user is unique 51 | }).catch(err => { 52 | // user is not unique 53 | }) 54 | ``` 55 | 56 | 57 | 58 |

server side module authManagement ( [options] )

59 | 60 | - **Arguments:** 61 | - `{Object} options` 62 | 63 | - **Usage:** 64 | 65 | Adds authManagement service when initialized with `app.configure(authManagement(options))`. 66 | 67 | ``` js 68 | const authManagement = require('@feathers-plus/authentication-management'); 69 | 70 | app.configure(authManagement(options)); 71 | ``` 72 | 73 | - **Details:** 74 | - `options` An optional object of options: 75 | 76 | Property | Type | Default | Description 77 | ---|---|---|--- 78 | service | String | '/users' | Path to user service. 79 | path | String | 'authManagement' | Path for auth management service, see multiple services below. 80 | notifier | Function | `() => Promise.resolve()` | Function which sends notifications to user through email, SMS, etc. Call signature is `(type, user, notifierOptions) => Promise`. 81 | longTokenLen | Integer | 15 | URL-token's length will be twice this. 82 | shortTokenLen | Integer | 6 | Length of SMS tokens. 83 | shortTokenDigits | Boolean | true | If SMS token should be digits only. 84 | resetDelay | Integer | 1000 * 60 * 60 * 2 | Expiration for sign up email verification token in ms. 85 | delay | Integer | 1000 * 60 * 60 * 24 * 5 | Expiration for password reset token in ms. 86 | skipIsVerifiedCheck | Boolean | false | Allow sendResetPwd and resetPwd for unverified users. 87 | identifyUserProps | Array | ['email'] | Property in user-item which uniquely identifies the user, at least one of these props must be provided whenever a short token is used. 88 | sanitizeUserForClient | Function | sanitizeUserForClient | TODO 89 | 90 | -------------------------------------------------------------------------------- /source/v1/authentication-management/whats-new.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: What's New 3 | type: guide 4 | order: 4 5 | dropdown: extensions 6 | repo: authentication-management 7 | --- 8 | 9 | 10 | ## Dec. 2017 11 | 12 | - Upgraded to FeathersJS v3 (Buzzard). 13 | -------------------------------------------------------------------------------- /source/v1/batch-loader/common-patterns.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Common Patterns 3 | type: guide 4 | order: 3 5 | dropdown: extensions 6 | repo: batch-loader 7 | --- 8 | 9 | ## Creating a new batch-loader per Request. 10 | 11 | In many applications, a server using batch-loader serves requests to many different users with different access permissions. It may be dangerous to use one cache across many users, and it is encouraged to create a new batch-loader per request: 12 | 13 | ``` js 14 | function createLoaders(authToken) { 15 | return { 16 | users: new BatchLoader(ids => genUsers(authToken, ids)), 17 | cdnUrls: new BatchLoader(rawUrls => genCdnUrls(authToken, rawUrls)), 18 | stories: new BatchLoader(keys => genStories(authToken, keys)), 19 | }; 20 | } 21 | 22 | // When handling an incoming request: 23 | var loaders = createLoaders(request.query.authToken); 24 | 25 | // Then, within application logic: 26 | var user = await loaders.users.load(4); 27 | var pic = await loaders.cdnUrls.load(user.rawPicUrl); 28 | ``` 29 | 30 | ## Loading by alternative keys. 31 | 32 | Occasionally, some kind of value can be accessed in multiple ways. For example, perhaps a "User" type can be loaded not only by an "id" but also by a "username" value. If the same user is loaded by both keys, then it may be useful to fill both caches when a user is loaded from either source: 33 | 34 | ``` js 35 | let userByIDLoader = new BatchLoader(ids => genUsersByID(ids).then(users => { 36 | for (let user of users) { 37 | usernameLoader.prime(user.username, user); 38 | } 39 | return users; 40 | })); 41 | 42 | let usernameLoader = new BatchLoader(names => genUsernames(names).then(users => { 43 | for (let user of users) { 44 | userByIDLoader.prime(user.id, user); 45 | } 46 | return users; 47 | })); 48 | ``` 49 | 50 | ## Persistent caches 51 | 52 | By default, batch-loader uses the standard Map which simply grows until the batch-loader is released. A custom cache is provided as a convenience if you want to persist caches for longer periods of time. It implements a **least-recently-used** algorithm and allows you to limit the number of records cached. 53 | 54 | ``` js 55 | const BatchLoader = require('@feathers-plus/batch-loader'); 56 | const cache = require('@feathers-plus/cache'); 57 | 58 | const usersLoader = new BatchLoader( 59 | keys => { ... }, 60 | { cacheMap: cache({ max: 100 }) 61 | ); 62 | ``` 63 | 64 |

The default cache is appropriate when requests to your application are short-lived.

65 | 66 | ## Using non-Feathers services 67 | 68 | batch-loader provides a simplified and consistent API over various data sources, when its used as part of your application's data fetching layer. Custom Feathers services can use batch-loaders to natively accesses local and remote resources. 69 | 70 | ### Redis 71 | 72 | Redis is a very simple key-value store which provides the batch load method MGET which makes it very well suited for use with batch-loader. 73 | ``` js 74 | const BatchLoader = require('@feathers-plus/batch-loader'); 75 | const redis = require('redis'); 76 | 77 | const client = redis.createClient(); 78 | 79 | const redisLoader = new BatchLoader(keys => new Promise((resolve, reject) => { 80 | client.mget(keys, (error, results) => { 81 | if (error) return reject(error); 82 | 83 | resolve(results.map((result, index) => 84 | result !== null ? result : new Error(`No key: ${keys[index]}`) 85 | )); 86 | }); 87 | })); 88 | ``` 89 | 90 | ### SQLite 91 | 92 | While not a key-value store, SQL offers a natural batch mechanism with SELECT * WHERE IN statements. While batch-loader is best suited for key-value stores, it is still suited for SQL when queries remain simple. This example requests the entire row at a given id, however your usage may differ. 93 | 94 | This example uses the sqlite3 client which offers a parallelize method to further batch queries together. Another non-caching batch-loader utilizes this method to provide a similar API. batch-loaders can access other batch-loaders. 95 | 96 | ``` js 97 | const BatchLoader = require('@feathers-plus/batch-loader'); 98 | const sqlite3 = require('sqlite3'); 99 | 100 | const db = new sqlite3.Database('./to/your/db.sql'); 101 | 102 | // Dispatch a WHERE-IN query, ensuring response has rows in correct order. 103 | const userLoader = new BatchLoader(ids => { 104 | const params = ids.map(id => '?' ).join(); 105 | const query = `SELECT * FROM users WHERE id IN (${params})`; 106 | return queryLoader.load([query, ids]).then( 107 | rows => ids.map( 108 | id => rows.find(row => row.id === id) || new Error(`Row not found: ${id}`) 109 | ) 110 | ); 111 | }); 112 | 113 | // Parallelize all queries, but do not cache. 114 | const queryLoader = new BatchLoader(queries => new Promise(resolve => { 115 | const waitingOn = queries.length; 116 | const results = []; 117 | db.parallelize(() => { 118 | queries.forEach((query, index) => { 119 | db.all.apply(db, query.concat((error, result) => { 120 | results[index] = error || result; 121 | if (--waitingOn === 0) { 122 | resolve(results); 123 | } 124 | })); 125 | }); 126 | }); 127 | }), { cache: false }); 128 | 129 | // Usage 130 | 131 | const promise1 = userLoader.load('1234'); 132 | const promise2 = userLoader.load('5678'); 133 | 134 | Promise.all([ promise1, promise2 ]).then(([ user1, user2]) => { 135 | console.log(user1, user2); 136 | }); 137 | ``` 138 | 139 | ### Knex.js 140 | 141 | This example demonstrates how to use batch-loader with SQL databases via Knex.js, which is a SQL query builder and a client for popular databases such as PostgreSQL, MySQL, MariaDB etc. 142 | 143 | ``` js 144 | const BatchLoader = require('@feathers-plus/batch-loader'); 145 | const db = require('./db'); // an instance of Knex client 146 | 147 | // The list of batch loaders 148 | 149 | const batchLoader = { 150 | user: new BatchLoader(ids => db.table('users') 151 | .whereIn('id', ids).select() 152 | .then(rows => ids.map(id => rows.find(x => x.id === id)))), 153 | 154 | story: new BatchLoader(ids => db.table('stories') 155 | .whereIn('id', ids).select() 156 | .then(rows => ids.map(id => rows.find(x => x.id === id)))), 157 | 158 | storiesByUserId: new BatchLoader(ids => db.table('stories') 159 | .whereIn('author_id', ids).select() 160 | .then(rows => ids.map(id => rows.filter(x => x.author_id === id)))), 161 | }; 162 | 163 | // Usage 164 | 165 | Promise.all([ 166 | batchLoader.user.load('1234'), 167 | batchLoader.storiesByUserId.load('1234'), 168 | ]).then(([user, stories]) => {/* ... */}); 169 | ``` 170 | 171 | ### RethinkDB 172 | 173 | Full implementation: 174 | 175 | ``` js 176 | const BatchLoader = require('@feathers-plus/batch-loader'); 177 | const r = require('rethinkdb'); 178 | const db = await r.connect(); 179 | 180 | const batchLoadFunc = keys => db.table('example_table') 181 | .getAll(...keys) 182 | .then(res => res.toArray()) 183 | .then(normalizeRethinkDbResults(keys, 'id')); 184 | 185 | const exampleLoader = new BatchLoader(batchLoadFunc); 186 | 187 | await exampleLoader.loadMany([1, 2, 3]); // [{"id": 1, "name": "Document 1"}, {"id": 2, "name": "Document 2"}, Error]; 188 | 189 | await exampleLoader.load(1); // {"id": 1, "name": "Document 1"} 190 | 191 | function indexResults(results, indexField, cacheKeyFn = key => key) { 192 | const indexedResults = new Map(); 193 | results.forEach(res => { 194 | indexedResults.set(cacheKeyFn(res[indexField]), res); 195 | }); 196 | return indexedResults; 197 | } 198 | 199 | function normalizeRethinkDbResults(keys, indexField, cacheKeyFn = key => key) { 200 | return results => { 201 | const indexedResults = indexResults(results, indexField, cacheKeyFn); 202 | return keys.map(val => indexedResults.get(cacheKeyFn(val)) || new Error(`Key not found : ${val}`)); 203 | } 204 | } 205 | ``` -------------------------------------------------------------------------------- /source/v1/batch-loader/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Guide 3 | type: guide 4 | order: 1 5 | dropdown: extensions 6 | repo: batch-loader 7 | --- 8 | 9 | Loading data from database is one of the major tasks for most web applications. The goal of batch-loader is to improve the performance of database queries with two techniques: batching and caching. 10 | 11 | ## Batching 12 | 13 | Batching is batch-loader's primary feature. The reason for batching is to merge multiple similar database queries into one single query when possible. For example: 14 | 15 | ```js 16 | Promise.all([ 17 | posts.find({ query: { id: 1 } } ), 18 | posts.find({ query: { id: 2 } } ), 19 | posts.find({ query: { id: { $in: [3, 4] } } } ), 20 | posts.find({ query: { id: 5 } } ) 21 | ]) 22 | ``` 23 | 24 | is slower than 25 | 26 | ```js 27 | posts.find({ query: { id: { $in: [1, 2, 3, 4, 5] } } } ) 28 | ``` 29 | 30 | The latter sends only one query to database and retrieves the same 5 records as the former does, and therefore is much more efficient. 31 | 32 | Batch-loader is a tool to help you batch database calls in such a way. First, create a batch-loader by providing a batch loading function which accepts an array of keys and an optional context. It returns a Promise which resolves to an array of values. 33 | 34 | ``` js 35 | const BatchLoader = require('@feathers-plus/batch-loader'); 36 | const usersLoader = new BatchLoader((keys, context) => { 37 | return app.service('users').find({ query: { id: { $in: keys } } }) 38 | .then(records => { 39 | recordsByKey = /* recordsByKey[i] is the value for key[i] */; 40 | return recordsByKey; 41 | }); 42 | }, 43 | { context: {} } 44 | ); 45 | ``` 46 | 47 | You can then call the batch-loader with individual keys. It will coalesce all requests made within the current event loop into a single call to the batch-loader function, and return the results to each call. 48 | 49 | ``` js 50 | usersLoader.load(1).then(user => console.log('key 1', user)); 51 | usersLoader.load(2).then(user => console.log('key 2', user)); 52 | usersLoader.loadMany([1, 2, 3, 4]).then(users => console.log(users.length, users)); 53 | ``` 54 | 55 | The above will result in one database service call, i.e. `users.find({ query: { id: { $in: [1, 2, 3, 4] } } })`, instead of 6. 56 | 57 |

*"[W]ill coalesce all requests made within the current event loop into a single call"* sounds ominous. Just don't worry about it. Make `usersLoader.load` and `usersLoader.loadMany` calls the same way you would `users.get` and `users.find`. Everything will work as expected while, behind the scenes, batch-loader is making the fewest database calls logically possible.

58 | 59 | ### Batch Function 60 | 61 | The batch loading function accepts an array of keys and an optional context. It returns a Promise which resolves to an array of values. Each index in the returned array of values must correspond to the same index in the array of keys. 62 | 63 | For example, if the `usersLoader` from above is called with `[1, 2, 3, 4, 99]`, we would execute `users.find({ query: { id: { $in: [1, 2, 3, 4, 99] } } })`. The Feathers service could return the results: 64 | 65 | ``` js 66 | [ { id: 4, name: 'Aubree' } 67 | { id: 2, name: 'Marshall' }, 68 | { id: 1, name: 'John' }, 69 | { id: 3, name: 'Barbara' } ] 70 | ``` 71 | 72 | Please not that the order of the results will usually differ from the order of the keys and here, in addition, there is no `users` with an `id` of `99`. 73 | 74 | The batch function has to to reorganize the above results and return: 75 | 76 | ``` js 77 | [ { id: 1, name: 'John' }, 78 | { id: 2, name: 'Marshall' }, 79 | { id: 3, name: 'Barbara' }, 80 | { id: 4, name: 'Aubree' }, 81 | null ] 82 | ``` 83 | 84 | The `null` indicating there is no record for `user.id === 99`. 85 | 86 | ### Convenience Methods 87 | 88 | Batch-loader provides two convenience functions that will perform this reorganization for you. 89 | 90 | ``` js 91 | const BatchLoader = require('@feathers-plus/batch-loader'); 92 | const { getResultsByKey, getUniqueKeys } = BatchLoader; 93 | 94 | const usersLoader = new BatchLoader(keys => 95 | app.service('users').find({ query: { id: { $in: getUniqueKeys(keys) } } }) 96 | .then(records => getResultsByKey(keys, records, user => user.id, '')); 97 | ); 98 | ``` 99 | 100 | **getUniqueKeys** eliminates any duplicate elements in the keys. 101 | 102 | > The array of keys may contain duplicates when the batch-loader's memoization cache is disabled. 103 | 104 | **getResultsByKey** reorganizes the records from the service call into the result expected from the batch function. The `''` parameter indicates each key expects a single record or `null`. Other options are `'!'` when each key requires a single record, and `'[]'` when each key requires an array of 0, 1 or more records. 105 | 106 | ## Caching 107 | 108 | Each batch-loader instance contains a unique memoized cache. Once `load` or `loadMany` is called, the resulting value is cached. This eliminates redundant database requests, relieving pressure on your database. It also creates fewer objects which may relieve memory pressure on your application. 109 | 110 | ``` js 111 | Promise.all([ 112 | userLoader.load(1), 113 | userLoader.load(1) 114 | ]) 115 | .then(users => assert(users[0] === users[1])); 116 | ``` 117 | 118 |

The same object is returned for each of multiple hits on the cache. You should not mutate that object directly as the mutation would be reflected in every reference to the object. Rather you should deep-copy before mutating the copy.

119 | 120 | ### Caching Per Request 121 | 122 | It may be dangerous to use one cache across many users, and it is encouraged to create a new batch-loader per request. Typically batch-loader instances are created when a request begins and are released once the request ends. 123 | 124 | Since the cache exists for a limited time only, the cache contents should not normally grow large enough to cause memory pressure on the application. 125 | 126 | ### Persistent Caches 127 | 128 | A batch-loader can be shared between requests and between users if care is taken. Use caution when used in long-lived applications or those which serve many users with different access permissions. 129 | 130 | The main advantage is having the cache already primed at the start of each request, which could result in fewer initial database requests. 131 | 132 | #### Memory pressure 133 | 134 | There are two concerns though. First the cache could keep filling up with records causing memory pressure. This can be handled with a custom cache. 135 | 136 | **@feathers-plus/cache** is a least-recently-used (LRU) cache which you can inject when initializing the batch-loader. You can specify the maximum number of records to be kept in the cache, and it will retain the least recently used records. 137 | 138 | ``` js 139 | const BatchLoader = require('@feathers-plus/batch-loader'); 140 | const cache = require('@feathers-plus/cache'); 141 | 142 | const usersLoader = new BatchLoader( 143 | keys => { ... }, 144 | { cacheMap: cache({ max: 100 }) 145 | ); 146 | ``` 147 | 148 | #### Mutation 149 | 150 | The other concern is a record mutating. You can create a hook which clears a record from its BatchLoaders' caches when it mutates. 151 | 152 | ``` js 153 | usersLoader.clear(1); 154 | ``` 155 | 156 | > `@feathers-plus/cache/lib/hooks` contains hooks which clear the keys of mutated records. 157 | 158 | ## Explore Performance Gains 159 | 160 | ### Our Sample Data 161 | 162 | We will be using Feathers database services containing the following data: 163 | 164 | ``` js 165 | // app.service('posts') 166 | const postsStore = [ 167 | { id: 1, body: 'John post', userId: 101, starIds: [102, 103, 104] }, 168 | { id: 2, body: 'Marshall post', userId: 102, starIds: [101, 103, 104] }, 169 | { id: 3, body: 'Barbara post', userId: 103 }, 170 | { id: 4, body: 'Aubree post', userId: 104 } 171 | ]; 172 | 173 | // app.service('comments') 174 | const commentsStore = [ 175 | { id: 11, text: 'John post Marshall comment 11', postId: 1, userId: 102 }, 176 | { id: 12, text: 'John post Marshall comment 12', postId: 1, userId: 102 }, 177 | { id: 13, text: 'John post Marshall comment 13', postId: 1, userId: 102 }, 178 | { id: 14, text: 'Marshall post John comment 14', postId: 2, userId: 101 }, 179 | { id: 15, text: 'Marshall post John comment 15', postId: 2, userId: 101 }, 180 | { id: 16, text: 'Barbara post John comment 16', postId: 3, userId: 101 }, 181 | { id: 17, text: 'Aubree post Marshall comment 17', postId: 4, userId: 102 } 182 | ]; 183 | 184 | // app.service('users') 185 | const usersStore = [ 186 | { id: 101, name: 'John' }, 187 | { id: 102, name: 'Marshall' }, 188 | { id: 103, name: 'Barbara' }, 189 | { id: 104, name: 'Aubree' } 190 | ]; 191 | ``` 192 | 193 | We want to see how using batch-loader affects the number of database calls, and we will do that by populating the `posts` records with related information. 194 | 195 | ### Using Plain JavaScript 196 | 197 | First, let's add the related `comments` records to each `posts` record using regular JavaScript, and let's do this using both Promises and async/await. 198 | 199 | ``` js 200 | // Populate using Promises. 201 | Promise.resolve(posts.find() 202 | .then(posts => Promise.all(posts.map(post => comments.find({ query: { postId: post.id } }) 203 | .then(comments => { 204 | post.commentRecords = comments; 205 | return post; 206 | }) 207 | ))) 208 | ) 209 | .then(data => ... ); 210 | 211 | // Populate using async/await. 212 | const postRecords = await posts.find(); 213 | const data = await Promise.all(postRecords.map(async post => { 214 | post.commentRecords = await comments.find({ query: { postId: post.id } }); 215 | return post; 216 | })); 217 | ``` 218 | 219 | Both of these make the following database service calls, and both get the following result. 220 | 221 | ``` js 222 | ... posts find 223 | ... comments find { postId: 1 } 224 | ... comments find { postId: 2 } 225 | ... comments find { postId: 3 } 226 | ... comments find { postId: 4 } 227 | 228 | [ { id: 1, 229 | body: 'John post', 230 | userId: 101, 231 | starIds: [ 102, 103, 104 ], 232 | commentRecords: [ 233 | { id: 11, text: 'John post Marshall comment 11', postId: 1, userId: 102 }, 234 | { id: 12, text: 'John post Marshall comment 12', postId: 1, userId: 102 }, 235 | { id: 13, text: 'John post Marshall comment 13', postId: 1, userId: 102 } ] }, 236 | { ... } 237 | ] 238 | ``` 239 | 240 | ### Using Neither Batching nor Caching 241 | 242 | The batch-loader function will be called for every `load` and `loadMany` when batching and caching are disabled in the batch-loader. This means it acts just like individual `get` and `find` method calls. Let's rewrite the above example using such a rudimentary batch-loader: 243 | 244 | ``` js 245 | const BatchLoader = require('@feathers-plus/batch-loader'); 246 | const { getResultsByKey, getUniqueKeys } = BatchLoader; 247 | 248 | // Populate using Promises. 249 | const commentsLoaderPromises = new BatchLoader( 250 | keys => comments.find({ query: { postId: { $in: getUniqueKeys(keys) } } }) 251 | .then(result => getResultsByKey(keys, result, comment => comment.postId, '[]')), 252 | { batch: false, cache: false } 253 | ); 254 | 255 | Promise.resolve(posts.find() 256 | .then(postRecords => Promise.all(postRecords.map(post => commentsLoaderPromises.load(post.id) 257 | .then(comments => { 258 | post.commentRecords = comments; 259 | return post; 260 | }) 261 | ))) 262 | ) 263 | .then(data => { ... }); 264 | 265 | // Populate using async/await. 266 | const commentsLoaderAwait = new BatchLoader(async keys => { 267 | const postRecords = await comments.find({ query: { postId: { $in: getUniqueKeys(keys) } } }); 268 | return getResultsByKey(keys, postRecords, comment => comment.postId, '[]'); 269 | }, 270 | { batch: false, cache: false } 271 | ); 272 | 273 | const postRecords = await posts.find(); 274 | const data = await Promise.all(postRecords.map(async post => { 275 | post.commentRecords = await commentsLoaderAwait.load(post.id); 276 | return post; 277 | })); 278 | ``` 279 | 280 | Both of these make the same database service calls as did the [plain JavaScript example](#Using-Plain-JavaScript), because batching and caching were both disabled. 281 | 282 | ``` text 283 | ... posts find 284 | ... comments find { postId: { '$in': [ 1 ] } } 285 | ... comments find { postId: { '$in': [ 2 ] } } 286 | ... comments find { postId: { '$in': [ 3 ] } } 287 | ... comments find { postId: { '$in': [ 4 ] } } 288 | ``` 289 | 290 | > A batch-loader with neither batching nor caching makes the same database calls as does a plain Javascript implementation. This is a convenient way to debug issues you might have with batch-loader. The *"magic"* disappears when you disable batching and caching, which makes it simpler to understand what is happening. 291 | 292 | ### Using Batching and Caching 293 | 294 | Batching and caching are enabled when we remove the 2 `{ batch: false, cache: false }` in the above example. A very different performance profile is now produced: 295 | 296 | ``` text 297 | ... posts find 298 | ... comments find { postId: { '$in': [ 1, 2, 3, 4 ] } } 299 | ``` 300 | 301 | Only 1 service call was made for the `comments` records, instead of the previous 4. 302 | 303 | ### A Realistic Example 304 | 305 | The more service calls made, the better batch-loader performs. The above example populated the `posts` records with just the `comments` records. Let's see the effect batch-loader has when we fully populate the `posts` records. 306 | 307 | ``` js 308 | const { map, parallel } = require('asyncro'); 309 | const BatchLoader = require('@feathers-plus/batch-loader'); 310 | 311 | const { getResultsByKey, getUniqueKeys } = BatchLoader; 312 | 313 | tester({ batch: false, cache: false }) 314 | .then(data => { ... ) 315 | 316 | async function tester (options) { 317 | const commentsLoader = new BatchLoader(async keys => { 318 | const result = await comments.find({ query: { postId: { $in: getUniqueKeys(keys) } } }); 319 | return getResultsByKey(keys, result, comment => comment.postId, '[]'); 320 | }, 321 | options 322 | ); 323 | 324 | const usersLoader = new BatchLoader(async keys => { 325 | const result = await users.find({ query: { id: { $in: getUniqueKeys(keys) } } }); 326 | return getResultsByKey(keys, result, user => user.id, ''); 327 | }, 328 | options 329 | ); 330 | 331 | const postRecords = await posts.find(); 332 | 333 | await map(postRecords, async post => { 334 | await parallel([ 335 | // Join one users record to posts, for post.userId === users.id 336 | async () => { 337 | post.userRecord = await usersLoader.load(post.userId); 338 | }, 339 | // Join 0, 1 or many comments records to posts, where comments.postId === posts.id 340 | async () => { 341 | const commentRecords = await commentsLoader.load(post.id); 342 | post.commentRecords = commentRecords; 343 | 344 | // Join one users record to comments, for comments.userId === users.id 345 | await map(commentRecords, async comment => { 346 | comment.userRecord = await usersLoader.load(comment.userId); 347 | }); 348 | }, 349 | // Join 0, 1 or many users record to posts, where posts.starIds === users.id 350 | async () => { 351 | if (!post.starIds) return null; 352 | 353 | post.starUserRecords = await usersLoader.loadMany(post.starIds); 354 | } 355 | ]); 356 | }); 357 | 358 | return postRecords; 359 | } 360 | ``` 361 | 362 | > Notice `usersLoader` is being called within 3 quite different joins. These joins will share their batching and cache, noticeably improving overall performance. 363 | 364 | This example has batching and caching disabled. These 22 service calls are made when it is run. They are the same calls which a plain JavaScript implementation would have made: 365 | 366 | ``` text 367 | ... posts find 368 | ... users find { id: { '$in': [ 101 ] } } 369 | ... comments find { postId: { '$in': [ 1 ] } } 370 | ... users find { id: { '$in': [ 102 ] } } 371 | ... users find { id: { '$in': [ 103 ] } } 372 | ... users find { id: { '$in': [ 104 ] } } 373 | ... users find { id: { '$in': [ 102 ] } } 374 | ... comments find { postId: { '$in': [ 2 ] } } 375 | ... users find { id: { '$in': [ 101 ] } } 376 | ... users find { id: { '$in': [ 103 ] } } 377 | ... users find { id: { '$in': [ 104 ] } } 378 | ... users find { id: { '$in': [ 103 ] } } 379 | ... comments find { postId: { '$in': [ 3 ] } } 380 | ... users find { id: { '$in': [ 104 ] } } 381 | ... comments find { postId: { '$in': [ 4 ] } } 382 | ... users find { id: { '$in': [ 102 ] } } 383 | ... users find { id: { '$in': [ 102 ] } } 384 | ... users find { id: { '$in': [ 102 ] } } 385 | ... users find { id: { '$in': [ 101 ] } } 386 | ... users find { id: { '$in': [ 101 ] } } 387 | ... users find { id: { '$in': [ 101 ] } } 388 | ... users find { id: { '$in': [ 102 ] } } 389 | ``` 390 | 391 | Now let's enable batching and caching by changing `tester({ batch: false, cache: false })` to `tester()`. Only these **three** service calls are now made to obtain the same results: 392 | 393 | ``` text 394 | ... posts find 395 | ... users find { id: { '$in': [ 101, 102, 103, 104 ] } } 396 | ... comments find { postId: { '$in': [ 1, 2, 3, 4 ] } } 397 | ``` 398 | 399 | > The 2 BatchLoaders reduced the number of services calls from 22 for a plain implementation, to just 3! 400 | 401 | The final populated result is: 402 | 403 | ``` js 404 | [ { id: 1, 405 | body: 'John post', 406 | userId: 101, 407 | starIds: [ 102, 103, 104 ], 408 | userRecord: { id: 101, name: 'John' }, 409 | starUserRecords: 410 | [ { id: 102, name: 'Marshall' }, { id: 103, name: 'Barbara' }, { id: 104, name: 'Aubree' } ], 411 | commentRecords: 412 | [ { id: 11, 413 | text: 'John post Marshall comment 11', 414 | postId: 1, 415 | userId: 102, 416 | userRecord: { id: 102, name: 'Marshall' } }, 417 | { id: 12, 418 | text: 'John post Marshall comment 12', 419 | postId: 1, 420 | userId: 102, 421 | userRecord: { id: 102, name: 'Marshall' } }, 422 | { id: 13, 423 | text: 'John post Marshall comment 13', 424 | postId: 1, 425 | userId: 102, 426 | userRecord: { id: 102, name: 'Marshall' } } ] }, 427 | { id: 2, 428 | body: 'Marshall post', 429 | userId: 102, 430 | starIds: [ 101, 103, 104 ], 431 | userRecord: { id: 102, name: 'Marshall' }, 432 | starUserRecords: 433 | [ { id: 101, name: 'John' }, { id: 103, name: 'Barbara' }, { id: 104, name: 'Aubree' } ], 434 | commentRecords: 435 | [ { id: 14, 436 | text: 'Marshall post John comment 14', 437 | postId: 2, 438 | userId: 101, 439 | userRecord: { id: 101, name: 'John' } }, 440 | { id: 15, 441 | text: 'Marshall post John comment 15', 442 | postId: 2, 443 | userId: 101, 444 | userRecord: { id: 101, name: 'John' } } ] }, 445 | { id: 3, 446 | body: 'Barbara post', 447 | userId: 103, 448 | userRecord: { id: 103, name: 'Barbara' }, 449 | commentRecords: 450 | [ { id: 16, 451 | text: 'Barbara post John comment 16', 452 | postId: 3, 453 | userId: 101, 454 | userRecord: { id: 101, name: 'John' } } ] }, 455 | { id: 4, 456 | body: 'Aubree post', 457 | userId: 104, 458 | userRecord: { id: 104, name: 'Aubree' }, 459 | commentRecords: 460 | [ { id: 17, 461 | text: 'Aubree post Marshall comment 17', 462 | postId: 4, 463 | userId: 102, 464 | userRecord: { id: 102, name: 'Marshall' } } ] } ] 465 | ``` 466 | 467 | ## See also 468 | 469 | - [facebook/dataloader](https://github.com/facebook/dataloader) from which batch-loader is derived. 470 | -------------------------------------------------------------------------------- /source/v1/batch-loader/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API 3 | type: guide 4 | order: 2 5 | dropdown: extensions 6 | repo: batch-loader 7 | --- 8 | 9 | 10 |

Usage

11 | 12 | ``` js 13 | npm install --save @feathers-plus/batch-loader 14 | 15 | // JS 16 | const BatchLoader = require('@feathers-plus/batch-loader'); 17 | const { getResultsByKey, getUniqueKeys } = BatchLoader; 18 | 19 | const usersLoader = new BatchLoader(async (keys, context) => { 20 | const usersRecords = await users.find({ query: { id: { $in: getUniqueKeys(keys) } } }); 21 | return getResultsByKey(keys, usersRecords, user => user.id, '') 22 | }, 23 | { context: {} } 24 | ); 25 | 26 | const user = await usersLoader.load(key); 27 | ``` 28 | 29 | > May be used on the client. 30 | 31 | 32 |

class BatchLoader( batchLoadFunc [, options] )

33 | 34 | Create a new batch-loader given a batch loading function and options. 35 | 36 | - **Arguments:** 37 | - `{Function} batchLoadFunc` 38 | - `{Object} [ options ]` 39 | - `{Boolean} batch` 40 | - `{Boolean} cache` 41 | - `{Function} cacheKeyFn` 42 | - `{Object} cacheMap` 43 | - `{Object} context` 44 | - `{Number} maxBatchSize` 45 | 46 | Argument | Type | Default | Description 47 | ---|:---:|---|--- 48 | `batchLoadFunc` | `Function` | | See [Batch Function](guide.html#batch-function). 49 | `options` | `Object` | | Options. 50 | 51 | `options` | Argument | Type | Default | Description 52 | ---|---|---|---|--- 53 | | `batch` | Boolean | `true` | Set to false to disable batching, invoking `batchLoadFunc` with a single load key. 54 | | `cache` | Boolean | `true` | Set to false to disable memoization caching, creating a new Promise and new key in the `batchLoadFunc` for every load of the same key. 55 | | `cacheKeyFn` | Function | `key => key` | Produces cache key for a given load key. Useful when keys are objects and two objects should be considered equivalent. 56 | | `cacheMap` | Object | `new Map()` | Instance of Map (or an object with a similar API) to be used as cache. See below. 57 | | `context` | Object | `null` | A context object to pass into `batchLoadFunc` as its second argument. 58 | | `maxBatchSize` | Number | `Infinity` | Limits the number of keys when calling `batchLoadFunc`. 59 | 60 | {% apiReturns class-batchloader "BatchLoader instance." batchLoader Object %} 61 | 62 | - **Example** 63 | 64 | ``` js 65 | const BatchLoader = require('@feathers-plus/batch-loader'); 66 | const { getResultsByKey, getUniqueKeys } = BatchLoader; 67 | 68 | const usersLoader = new BatchLoader(async (keys, context) => { 69 | const data = await users.find({ query: { id: { $in: getUniqueKeys(keys) } }, paginate: false }); 70 | return getResultsByKey(keys, data, user => user.id, '') 71 | }, 72 | { context: {}, batch: true, cache: true } 73 | ); 74 | ``` 75 | 76 | - **Pagination** 77 | 78 | The number of results returned by a query using `$in` is controlled by the pagination `max` set for that Feathers service. You need to specify a `paginate: false` option to ensure that records for all the keys are returned. 79 | 80 | The maximum number of keys the `batchLoadFunc` is called with can be controlled by the BatchLoader itself with the `maxBatchSize` option. 81 | 82 | - **option.cacheMap** 83 | 84 | The default cache will grow without limit, which is reasonable for short lived batch-loaders which are rebuilt on every request. The number of records cached can be limited with a *least-recently-used* cache: 85 | 86 | ``` js 87 | const BatchLoader = require('@feathers-plus/batch-loader'); 88 | const cache = require('@feathers-plus/cache'); 89 | 90 | const usersLoader = new BatchLoader( 91 | keys => { ... }, 92 | { cacheMap: cache({ max: 100 }) 93 | ); 94 | ``` 95 | 96 | > You can consider wrapping npm's `lru` on the browser. 97 | 98 | - **See also:** [Guide](./guide.html) 99 | 100 | {% apiFootnote classBatchLoader BatchLoader %} 101 | 102 | 103 |

static BatchLoader.getUniqueKeys( keys )

104 | 105 | Returns the unique elements in an array. 106 | 107 | - **Arguments:** 108 | - `{Array} keys` 109 | 110 | Argument | Type | Default | Description 111 | ---|---|---|--- 112 | `keys` | `Array<` `String /` `Number >` | | The keys. May contain duplicates. 113 | 114 | {% apiReturns get-unique-keys "The keys with no duplicates." keys "Array< String / Number >" %} 115 | 116 | - **Example:** 117 | 118 | ``` js 119 | const usersLoader = new BatchLoader(async keys => 120 | const data = users.find({ query: { id: { $in: getUniqueKeys(keys) } } }) 121 | ... 122 | ); 123 | ``` 124 | 125 | - **Details** 126 | 127 | The array of keys may contain duplicates when the batch-loader's memoization cache is disabled. 128 | 129 |

Function does not handle keys of type Object nor Array.

130 | 131 | {% apiFootnote getUniqueKeys BatchLoader %} 132 | 133 | 134 |

static BatchLoader.getResultsByKey( keys, records, getRecordKeyFunc, type [, options] )

135 | 136 | Reorganizes the records from the service call into the result expected from the batch function. 137 | 138 | - **Arguments:** 139 | - `{Array} keys` 140 | - `{Array} records` 141 | - `{Function} getRecordKeyFunc` 142 | - `{String} type` 143 | - `{Object} [ options ]` 144 | - `{null | []} defaultElem` 145 | - `{Function} onError` 146 | 147 | Argument | Type | Default | Description 148 | ---|:---:|---|--- 149 | `keys` | `Array<` `String /` `Number>` | | An array of `key` elements, which the value the batch loader function will use to find the records requested. 150 | `records` | `Array< ` `Object >` | | An array of records which, in total, resolve all the `keys`. 151 | `getRecordKeyFunc` | `Function` | | See below. 152 | `type` | `String` | | The type of value the batch loader must return for each key. 153 | `options` | `Object` | | Options. 154 | 155 | `type` | Value | Description 156 | ---|:---:|--- 157 | | `''` | An optional single record. 158 | | `'!'` | A required single record. 159 | | `'[]'` | A required array including 0, 1 or more records. 160 | 161 | `options` | Argument | Type | Default | Description 162 | ---|---|:---:|---|--- 163 | | `defaultElem` | `{null / []}` | `null` | The value to return for a `key` having no record(s). 164 | | `onError` | `Function` | `(i, msg) => {}` | Handler for detected errors, e.g. `(i, msg) =>` `{ throw new Error(msg,` `'on element', i); }` 165 | 166 | {% apiReturns get-results-by-key "The result for each key. results[i] is the result for keys[i]." results "Array< Object >"%} 167 | 168 | - **Example** 169 | 170 | ``` js 171 | const usersLoader = new BatchLoader(async keys => { 172 | const data = users.find({ query: { id: { $in: getUniqueKeys(keys) } } }) 173 | return getResultsByKey(keys, data, user => user.id, '', { defaultElem: [] }) 174 | }); 175 | ``` 176 | 177 | - **Details** 178 | 179 |

Function does not handle keys of type Object nor Array.

180 | 181 | - **getRecordKeyFunc** 182 | 183 | A function which, given a record, returns the key it satisfies, e.g. 184 | ``` js 185 | user => user.id 186 | ``` 187 | 188 | - **See also:** [Batch-Function](./guide.html#Batch-Function) 189 | 190 | {% apiFootnote getResultsByKey BatchLoader %} 191 | 192 | 193 |

batchLoader.load( key )

194 | 195 | Loads a key, returning a Promise for the value represented by that key. 196 | 197 | - **Arguments:** 198 | - `{String | Number | Object | Array} key` 199 | 200 | Argument | Type | Default | Description 201 | ---|:---:|---|--- 202 | `key` | `String` `Number` `Object` `Array` | | The key the batch-loader uses to find the result(s). 203 | 204 | {% apiReturns load "Resolves to the result(s)." promise "Promise< Object >"%} 205 | 206 | - **Example:** 207 | 208 | ``` js 209 | const batchLoader = new BatchLoader( ... ); 210 | const user = await batchLoader.load(key); 211 | ``` 212 | 213 | {% apiFootnote load BatchLoader %} 214 | 215 | 216 |

batchLoader.loadMany( keys )

217 | 218 | Loads multiple keys, promising a arrays of values. 219 | 220 | - **Arguments** 221 | - `{Array} keys` 222 | 223 | Argument | Type | Default | Description 224 | ---|:---:|---|--- 225 | `keys` | `Array<` `String /` ` Number /` ` Object /` ` Array>` | | The keys the batch-loader will return result(s) for. 226 | 227 | {% apiReturns load "Resolves to an array of result(s). promise[i] is the result for keys[i]." promise "Promise[ Array< Object > ]"%} 228 | 229 | - **Example** 230 | 231 | ``` js 232 | const usersLoader = new BatchLoader( ... ); 233 | const users = await usersLoader.loadMany([ key1, key2 ]); 234 | ``` 235 | 236 | - **Details** 237 | 238 | This is a convenience method. `usersLoader.loadMany([ key1, key2 ])` is equivalent to the more verbose: 239 | ``` js 240 | Promise.all([ 241 | usersLoader.load(key1), 242 | usersLoader.load(key2) 243 | ]); 244 | ``` 245 | 246 | {% apiFootnote loadMany BatchLoader %} 247 | 248 | 249 |

batchLoader.clear( key )

250 | 251 | Clears the value at key from the cache, if it exists. 252 | 253 | - **Arguments:** 254 | - `{String | Number | Object | Array} key` 255 | 256 | Argument | Type | Default | Description 257 | ---|:---:|---|--- 258 | `key` | `String` `Number` `Object` `Array` | | The key to remove from the cache. 259 | 260 | - **Details** 261 | 262 | The key is matches using strict equality. This is particularly important for `Object` and `Array` keys. 263 | 264 | {% apiFootnote clear BatchLoader %} 265 | 266 | 267 |

batchLoader.clearAll()

268 | 269 | Clears the entire cache. 270 | 271 | - **Details** 272 | 273 | To be used when some event results in unknown invalidations across this particular batch-loader. 274 | 275 | {% apiFootnote clearAll BatchLoader %} 276 | 277 | 278 |

batchLoader.prime( key, value )

279 | 280 | Primes the cache with the provided key and value. 281 | 282 | - **Arguments:** 283 | - `{String | Number | Object | Array} key` 284 | - `{Object} record` 285 | 286 | Argument | Type | Default | Description 287 | ---|:---:|---|--- 288 | `key` | `String` `Number` `Object` `Array` | | The key in the cache for the record. 289 | `record` | `Object` | | The value for the `key`. 290 | 291 | - **Details** 292 | 293 | **If the key already exists, no change is made.** To forcefully prime the cache, clear the key first with `batchloader.clear(key)`. 294 | 295 | {% apiFootnote prime BatchLoader %} 296 | 297 | 298 |

What's New

299 | 300 | The details are at Changelog. 301 | 302 | #### Feb. 2018 303 | 304 | - Added information about pagination within the `batchLoadFunc`. 305 | 306 | -------------------------------------------------------------------------------- /source/v1/feathers-vuex/auth-module.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Auth Module API 3 | type: guide 4 | order: 4 5 | dropdown: frameworks 6 | repo: feathers-vuex 7 | --- 8 | 9 | This page has moved to https://vuex.feathersjs.com/auth-module.html -------------------------------------------------------------------------------- /source/v1/feathers-vuex/common-patterns.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Common Patterns 3 | type: guide 4 | order: 1 5 | dropdown: frameworks 6 | repo: feathers-vuex 7 | --- 8 | 9 | This page has moved to https://vuex.feathersjs.com/common-patterns.html. -------------------------------------------------------------------------------- /source/v1/feathers-vuex/components.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Renderless Data Components 3 | type: guide 4 | order: 7 5 | dropdown: frameworks 6 | repo: feathers-vuex 7 | --- 8 | 9 | This page has moved to https://vuex.feathersjs.com/components.html. -------------------------------------------------------------------------------- /source/v1/feathers-vuex/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Guide 3 | type: guide 4 | order: 1 5 | dropdown: frameworks 6 | repo: feathers-vuex 7 | --- 8 | 9 | This page has moved to https://vuex.feathersjs.com/nuxt.html. -------------------------------------------------------------------------------- /source/v1/feathers-vuex/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API Overview 3 | type: guide 4 | order: 2 5 | dropdown: frameworks 6 | repo: feathers-vuex 7 | --- 8 | 9 | This page has moved to https://vuex.feathersjs.com/api-overview.html. -------------------------------------------------------------------------------- /source/v1/feathers-vuex/mixins.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Mixins 3 | type: guide 4 | order: 8 5 | dropdown: frameworks 6 | repo: feathers-vuex 7 | --- 8 | 9 | This page has moved to https://vuex.feathersjs.com/mixins.html. -------------------------------------------------------------------------------- /source/v1/feathers-vuex/model-classes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Models & Instances API 3 | type: guide 4 | order: 5 5 | dropdown: frameworks 6 | repo: feathers-vuex 7 | --- 8 | 9 | This page has moved to https://vuex.feathersjs.com/model-classes.html. -------------------------------------------------------------------------------- /source/v1/feathers-vuex/service-module.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Service Module API 3 | type: guide 4 | order: 3 5 | dropdown: frameworks 6 | repo: feathers-vuex 7 | --- 8 | 9 | This page has moved to https://vuex.feathersjs.com/service-module.html. -------------------------------------------------------------------------------- /source/v1/feathers-vuex/vue-plugin.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vue Plugin API 3 | type: guide 4 | order: 6 5 | dropdown: frameworks 6 | repo: feathers-vuex 7 | --- 8 | 9 | This page has moved to https://vuex.feathersjs.com/vue-plugin.html. -------------------------------------------------------------------------------- /source/whats-new/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: What's New 3 | type: guide 4 | order: 1 5 | dropdown: 6 | repo: What's New 7 | --- 8 | 9 | 10 | ## Nov. 2017 11 | 12 | ### batch-loader 13 | 14 | - Released. 15 | 16 | ### feathers-hooks-common 17 | 18 | # 🚨 feathers-hooks-common has moved! 🚨 19 | 20 | As of version `5.0.0`, feathers-hooks-common has a new home at [https://common-hooks.feathersjs.com](https://common-hooks.feathersjs.com). 21 | 22 | [View the new docs](https://common-hooks.feathersjs.com) 23 | 24 | 25 |

Use feathers-hooks-common v3.10.0 with FeathersJS v3 (Auk). 26 | Use feathers-hooks-common v4.x.x with FeathersJS v4 (Buzzard).

27 | 28 | - Docs moved to [Feathers-Plus web site.](https://feathers-plus.github.io/v1/feathers-hooks-common/guide.html) 29 | - v4.x.x now supports FeathersJS v3 (Buzzard). Continue using v3.10.0 for FeathersJS v2 (Auk). 30 | - Removed: 31 | - Removed support for the deprecated legacy syntax in `populate`. 32 | - Removed the deprecated `remove` hook. 33 | 34 | - Deprecated. These will be removed in FeathersJS v3 (Crow). 35 | - Deprecated `pluck` in favor of `iff(isProvider('external'),` `keep(...fieldNames))`. **Be careful!** 36 | - Deprecated the `client` in favor of the `paramsFromClient`. 37 | 38 | - Added modules. They work with both FeathersJS v2 and v3. 39 | - `fastJoin` hook - Very fast alternative to `populate`. 40 | - `makeCallingParams` utility - Help construct `context.params` when calling services. 41 | -------------------------------------------------------------------------------- /themes/tech-docs/_config.yml: -------------------------------------------------------------------------------- 1 | site_description: "Feathers-Plus - Bridging FeathersJS and real world apps." 2 | google_analytics: UA-xxxxxxxx-1 3 | root_domain: feathers-plus.com 4 | vue_version: 0.0.0 5 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/index.ejs: -------------------------------------------------------------------------------- 1 | <% console.log('......index.ejs running') %> 2 | 3 | 10 | 11 |
12 |
13 |
14 | "> 15 |
16 | 17 |

18 | Bridging FeathersJS and
real world apps 19 |

20 |

21 | ">What's New 22 | GitHub 23 |

24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |

Extend FeathersJS

32 |

33 | feathers-hooks-common contains the FeathersJS common hooks.

34 | ">batch-loader transparently reduces the number of database calls.

35 | ">authentication-management sign up verification, password reset, etc.

36 | offline-* offline-first, realtime synchronization. 37 |

38 |
39 | 40 |
41 |

Service Adapters

42 |

43 | graphql, the GraphQL service for SQL and non-SQL databases.

44 |

45 |
46 | 47 |
48 |

Framework Integration

49 |

50 | feathers-vuex for Vue/Vuex.

51 |

52 |
53 | 54 |
55 |
56 | 57 | 60 | 61 | 94 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/layout.ejs: -------------------------------------------------------------------------------- 1 | <% var isFrontPage = page.type === 'frontpage' %> 2 | <% console.log('...layout.ejs running', page.repo, page.title, isFrontPage) %> 3 | 4 | 5 | 6 | 7 | <%- page.title ? page.title + ' — ' : '' %>Feathers-Plus 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | "> 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | <%- css(isFrontPage ? 'css/index' : 'css/page') %> 55 | 56 | 57 | 58 | 59 | 60 | 61 | 72 | 73 | 74 |
> 75 | 76 | 77 |
78 | 79 | 80 | <%- partial('partials/header') %> 81 | 82 | 83 | <% if (!isFrontPage) { %> 84 |
85 | <% if (page.blog_index) { %> 86 | <%- partial('partials/blog') %> 87 | <% } else { %> 88 | 89 | <%- body %> 90 | <% } %> 91 |
92 | 93 | <% } else { %> 94 | 95 | <%- partial('index') %> 96 | <% } %> 97 | 98 | 99 | 100 | 101 | 102 | 103 | 127 | 128 | 129 | 130 | 135 | 136 | 137 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/page.ejs: -------------------------------------------------------------------------------- 1 | <% if (page.type) { %> 2 | <%- partial('partials/sidebar', { type: page.type === 'menu' ? 'guide' : page.type, index: page.index }) %> 3 | <% } else { %> 4 | 9 | <% } %> 10 |
11 | <% if (page.type) { %> 12 | <% if (page.type === 'menu') { %> 13 |
14 | 15 |
16 | <%- partial('partials/toc', { type: 'guide' }) %> 17 | <% } else { %> 18 | 19 | <% } %> 20 | <% } %> 21 | <% if (page.title.trim()) { %> 22 |

<%- page.title %><%- page.type === 'examples' ? ' Example' : '' %>

23 | <% } %> 24 | <%- page.content %> 25 | <% if (page.type === 'guide') { %> 26 | 36 | <% } %> 37 | 44 |
45 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/dropdown_adapters.ejs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/layout/partials/dropdown_adapters.ejs -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/dropdown_extensions.ejs: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/dropdown_frameworks.ejs: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/header.ejs: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/main_menu.ejs: -------------------------------------------------------------------------------- 1 |
  • 2 |
    3 | 4 |
    5 |
  • 6 | <%- partial('partials/dropdown_extensions', { dropdown: 'extensions' }) %> 7 | <%- partial('partials/dropdown_adapters', { dropdown: 'adapters' }) %> 8 | <%- partial('partials/dropdown_frameworks', { dropdown: 'frameworks' }) %> 9 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/sidebar.ejs: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/toc.ejs: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/z-ad-text.ejs: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 14 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/z-ad.ejs: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/z-conf.ejs: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/z-ecosystem_dropdown.ejs: -------------------------------------------------------------------------------- 1 | 38 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/z-language_dropdown.ejs: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/z-learn_dropdown.ejs: -------------------------------------------------------------------------------- 1 | 12 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/z-sponsors.ejs: -------------------------------------------------------------------------------- 1 |

    Patreon Sponsors

    2 | 3 | 4 | " style="width: 180px;"> 5 | 6 |
    7 | 8 | " style="width: 180px;"> 9 | 10 |
    11 |
    12 | 13 | " style="width: 130px;"> 14 | 15 | 16 | "> 17 | 18 | 19 | "> 20 | 21 | 22 | "> 23 | 24 | 25 | "> 26 | 27 | 28 | "> 29 | 30 | 31 | "> 32 | 33 | 34 | " style="width: 90px;"> 35 | 36 | 37 | " style="width: 80px;"> 38 | 39 | 40 | "> 41 | 42 | 43 | " style="width: 120px;"> 44 | 45 | 46 | " style="width: 65px;"> 47 | 48 | 49 | " style="width: 100px"> 50 | 51 | 52 | " style="width: 105px;"> 53 | 54 | 55 | " style="width: 100px;"> 56 | 57 |
    58 | 59 | ">Become a Sponsor! 60 | 61 |
    62 |

    OpenCollective Sponsors

    63 |

    Platinum

    64 | 65 |

    Gold

    66 | 67 |
    68 | 69 | 79 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/partials/z-support_vue_dropdown.ejs: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /themes/tech-docs/layout/post.ejs: -------------------------------------------------------------------------------- 1 | 23 |
    24 |

    <%- page.title %>

    25 |

    <%- page.date.format('MMM D[,] YYYY') %>

    26 | <%- page.content %> 27 |
    28 | -------------------------------------------------------------------------------- /themes/tech-docs/scripts/api.js: -------------------------------------------------------------------------------- 1 | 2 | hexo.extend.tag.register('apiReturns', ([name, desc, result = 'result', type = 'Boolean']) => { 3 | // console.log('ApiReturns', name, desc, result, type); 4 | 5 | // handle a bug 6 | if (desc.substr(-1) === ',') desc = desc.substr(0, desc.length - 1); 7 | if (result.substr(-1) === ',') result = result.substr(0, result.length - 1); 8 | if (type.substr(-1) === ',') type = type.substr(0, type.length - 1); 9 | 10 | return ` 11 |
    • 12 | Returns 13 |
      • 14 | {${type}} ${result} 15 |
      16 |
    17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 |
    NameTypeDescription
    ${result}${type}${desc}
    `; 34 | }); 35 | 36 | 37 | hexo.extend.tag.register('apiFootnote', options => { 38 | // console.log('apiFootnote', options); 39 | const name = options[0]; 40 | const repo = options[1]; 41 | 42 | return ` 43 | `; 46 | }); 47 | -------------------------------------------------------------------------------- /themes/tech-docs/scripts/get-pages-in-folder.js: -------------------------------------------------------------------------------- 1 | 2 | hexo.extend.helper.register('getPagesInFolder', (site, path) => { 3 | const folderPath = getFolder(path); 4 | 5 | const ourPages = site.pages.data.filter(page => folderPath === getFolder(page.path)); 6 | 7 | ourPages.sort((a, b) => a.order < b.order ? -1 : 1); 8 | return ourPages; 9 | }); 10 | 11 | function getFolder (path) { 12 | return path.substring(0, path.lastIndexOf('/')); 13 | } 14 | -------------------------------------------------------------------------------- /themes/tech-docs/scripts/hooks-api.js: -------------------------------------------------------------------------------- 1 | 2 | const { inspect } = require('util'); 3 | 4 | const hooksRaw = { 5 | // label: 'fastjoin', 6 | // fileName: 'fast-join', 7 | // src: 'https://github.com/feathers-plus/feathers-hooks-common/blob/master/lib/services/fast-join.js', 8 | 9 | 'act-on-default': { tags: ['code', 'cond', 'data'], desc: 'Runs a series of hooks which mutate context.data or content.result (the Feathers default).', srcFile: 'services/act-on-dispatch.js' }, 10 | 'act-on-dispatch': { tags: ['code', 'cond', 'data'], desc: 'Runs a series of hooks which mutate context.dispatch.' }, 11 | 'alter-items': { tags: ['code', 'cond', 'data', 'pred', 'relation', 'imp'], desc: 'Make changes to data or result items. Very flexible.'}, 12 | 'cache': { tags: ['data', 'services', 'perf'], desc: 'Persistent, least-recently-used record cache for services.'}, 13 | 'combine': { tags: ['code', 'multiNa', 'cond'], desc: 'Sequentially execute multiple sync or async hooks.'}, 14 | 'debug': { tags: 'code', desc: 'Display the current hook context for debugging.' }, 15 | 'de-populate': { tags: 'relation', desc: 'Remove records and properties created by the populate hook.' }, 16 | 'disallow': { tags: 'methods', desc: 'Prevents access to a service method completely or for specific transports.'}, 17 | 'disable-multi-item-create': { tags: ['methods', 'create'], desc: 'Prevents multi-item creates.', 18 | check: [0, 'before', ['create']] }, 19 | 'disable-multi-item-change': { tags: ['methods', 'update', 'patch', 'remove'], desc: 'Prevents null from being used as an id in patch and remove service methods.', 20 | check: [0, 'before', ['update', 'patch', 'remove']] }, 21 | 'disable-pagination': { tags: ['methods', 'data'], desc: 'Disables pagination when query.$limit is -1 or \'-1\'.', 22 | check: [0, 'before', ['find']] }, 23 | 'discard': { tags: 'data', desc: 'Delete certain fields from the record(s).', 24 | check: [1, 'before', ['create', 'update', 'patch']] }, 25 | 'discard-query': { tags: ['query'], desc: 'Delete certain fields from the query object.', 26 | check: [0, 'before', null] }, 27 | 'common/every': { name: 'every', tags: ['cond', 'pred'], desc: 'Return the and of a series of sync or async predicate functions.' }, 28 | 'fast-join': { tags: ['relation', 'perf', 'imp'], desc: 'Join related records. It\'s very fast.', 29 | guide: 'fastJoin' }, 30 | 'common/iff': { name: 'iff', tags: 'cond', desc: 'Execute one or another series of hooks depending on a sync or async predicate.' }, 31 | 'common/iff-else': { name: 'iffElse', tags: 'cond', desc: 'Execute one array of hooks or another based on a sync or async predicate.' }, 32 | 'common/is-not': { name: 'isNot', tags: ['cond', 'pred'], desc: 'Negate a sync or async predicate function.' }, 33 | 'is-provider': { tags: ['cond', 'pred', 'services', 'trans'], desc: 'Check which transport provided the service call.' }, 34 | 'keep': { tags: 'data', desc: 'Keep certain fields in the record(s), deleting the rest.', 35 | check: [1, 'before', ['create', 'update', 'patch']] }, 36 | 'keep-in-array': { tags: 'data', desc: 'Keep certain fields in a nested array inside the record(s), deleting the rest.', 37 | check: [1, 'before', ['create', 'update', 'patch']] }, 38 | 'keep-query' : { tags: ['query'], desc: 'Keep certain fields in the query object, deleting the rest.' }, 39 | 'keep-query-in-array' : { tags: ['query'], desc: 'Keep certain fields in a nested array inside the query object, deleting the rest.' }, 40 | 'lower-case': { check: [1, 'before', ['create', 'update', 'patch']], tags: 'data', desc: 'Convert certain field values to lower case.' }, 41 | 'mongo-keys': { tags: ['mongo', 'data', 'relation'], desc: 'Wrap MongoDB foreign keys in ObjectID.', 42 | check: [0, 'before', null] }, 43 | 'params-from-client': { tags: ['code', 'client', 'trans', 'calls'], desc: 'Pass context.params from client to server. Server hook.' }, 44 | 'populate': { tags: 'relation', desc: 'Join related records.', 45 | guide: 'populate' }, 46 | 'prevent-changes': { tags: ['data', 'methods'], desc: 'Prevent patch service calls from changing certain fields.', 47 | check: [0, 'before', ['patch']] }, 48 | 'required': { tags: ['valid', 'data', 'services'], desc: 'Check selected fields exist and are not falsey. Numeric 0 is acceptable.', 49 | check: [0, 'before', ['create', 'update', 'patch']] }, 50 | 'run-parallel': { tags: ['client', 'data', 'perf', 'services'], desc: 'Run a hook in parallel to the other hooks and the service call.' }, 51 | 'serialize': { tags: 'relation', desc: 'Prune values from related records. Calculate new values.' }, 52 | 'set-now': { tags: 'data', desc: 'Create/update certain fields to the current date-time.' }, 53 | 'set-slug': { tags: ['trans', 'rest'], desc: 'Fix slugs in URL, e.g. /stores/:storeId.' }, 54 | 'sifter': { tags: ['data', 'methods', 'relation', 'services', 'find'], desc: 'Filter data or result records using a MongoDB-like selection syntax.', 55 | check: [0, 'after', 'find'] }, 56 | 'skip-remaining-hooks': { tags: ['cond', 'data', 'services'], desc: 'Conditionally skip running all remaining hooks.' }, 57 | 'soft-delete': { tags: 'services', desc: 'Flag records as logically deleted instead of physically removing them. Deprecated.', 58 | check: [0, 'before', null] }, 59 | 'soft-delete2': { tags: 'services', desc: 'Flag records as logically deleted instead of physically removing them.' }, 60 | 'common/some': { name: 'some', tags: ['cond', 'pred'], desc: 'Return the or of a series of sync or async predicate functions.' }, 61 | 'stash-before': { tags: ['data', 'services'], desc: 'Stash current value of record, usually before mutating it. Performs a get call.', 62 | check: [0, 'before', ['get', 'update', 'patch', 'remove']] }, 63 | 'traverse': { tags: ['data', 'query', 'imp', 'rest'], desc: 'Transform fields & objects in place in the record(s) using a recursive walk. Powerful.' }, 64 | 'common/unless': { name: 'unless', tags: 'cond', desc: 'Execute a series of hooks if a sync or async predicate is falsey.' }, 65 | 'validate': { tags: ['valid', 'data', 'services', 'create', 'update', 'patch'], desc: 'Validate data using a validation function.', 66 | guide: 'validate', check: [0, 'before', ['create', 'update', 'patch']] }, 67 | 'validate-schema': { tags: ['valid', 'data', 'services'], desc: 'Validate data using JSON-Schema.' }, 68 | 'when': { name: 'when', fileName: 'common/iff', tags: 'cond', desc: 'An alias for iff.' }, 69 | 70 | 'calling-params': { tags: ['code', 'calls', 'func'], desc: 'Build params for a service call.', }, 71 | 'calling-params-defaults': { tags: ['code', 'calls', 'func'], desc: 'Set defaults for building params for service calls with callingParams.', srcFile: 'services/calling-params.js' }, 72 | 'check-context': { tags: ['code', 'services', 'func'], desc: 'Restrict a hook to run for certain methods and method types.' }, 73 | 'common/delete-by-dot': { name: 'deleteByDot', tags: ['code', 'dot', 'func'], desc: 'Deletes a property from an object using dot notation, e.g. address.city.' }, 74 | 'common/exists-by-dot': { name: 'existsByDot', tags: ['code', 'dot', 'func'], desc: 'Check if a property exists in an object by using dot notation, e.g. address.city.' }, 75 | 'common/get-by-dot': { name: 'getByDot', tags: ['code', 'dot', 'func'], desc: 'Return a property value from an object using dot notation, e.g. address.city.' }, 76 | 'get-items': { tags: ['code', 'data', 'func'], desc: 'Get the records in context.data or context.result[.data].' }, 77 | 'make-calling-params': { tags: ['code', 'calls', 'func'], desc: 'Build context.params for service calls.', srcFile: 'services/calling-params.js' }, 78 | 'params-for-server': { tags: ['code', 'client', 'trans', 'func', 'calls'], desc: 'Pass an explicit context.params from client to server. Client-side.' }, 79 | 'replace-items': { tags: ['code', 'data', 'func'], desc: 'Replace the records in context.data or context.result[.data].' }, 80 | 'run-hook': { tags: ['code', 'services', 'func'], desc: 'Let\'s you call a hook right after the service call.' }, 81 | 'common/set-by-dot': { name: 'setByDot', tags: ['code', 'dot', 'func'], desc: 'Set a property value in an object using dot notation, e.g. address.city.' }, 82 | }; 83 | 84 | const showTagNames = { 85 | calls: 'Calling services', 86 | client: 'Client/server', 87 | code: 'Coding', 88 | cond: 'Conditionals', 89 | data: 'Data and Results', 90 | dot: 'Dot notation', 91 | imp: 'Imperative API', 92 | methods: 'Service methods', 93 | mongo: 'MongoDB specific', 94 | perf: 'Performance', 95 | pred: 'Predicates', 96 | query: 'Query object', 97 | relation: 'Relations', 98 | rest: 'REST', 99 | services: 'Services', 100 | trans: 'Transport', 101 | valid: 'Validation', 102 | }; 103 | 104 | const all = 'all'; 105 | const yes = 'yes'; 106 | const no = 'no'; 107 | 108 | const methodNames = ['find', 'create', 'get', 'update', 'patch', 'remove']; 109 | const ignoreTags = [].concat(methodNames, 'multiYes', 'multiNo', 'multiNa', 'func', 'check'); 110 | const multiDisplay = { 111 | multiYes: 'yes', 112 | multiNo: 'no', 113 | multiNa: 'n/a', 114 | }; 115 | 116 | const check1 = { 117 | before: [yes, no], 118 | after: [no, yes], 119 | null: [yes, yes], 120 | undefined: [yes, yes], 121 | }; 122 | 123 | const check2 = { 124 | before: ['', yes], 125 | after: [yes, ''], 126 | null: [null, null], 127 | undefined: [null, null], 128 | }; 129 | 130 | const hooks = {}; 131 | const hooksByTag = {}; 132 | 133 | Object.keys(hooksRaw).sort().forEach(fileName => { 134 | const info = hooksRaw[fileName]; 135 | fileName = info.fileName || fileName; 136 | 137 | const name = info.name || toCamelCase(fileName); 138 | const tags = Array.isArray(info.tags) ? info.tags : [info.tags]; 139 | const showTags = tags.filter(name => ignoreTags.indexOf(name) === -1).map(name => showTagNames[name] || name).sort(); 140 | const tagsHtml = showTags.map(name => `${name}`).join(', '); 141 | 142 | const multi = Object.keys(multiDisplay).reduce((result, key) => { 143 | return tags.indexOf(key) !== -1 ? multiDisplay[key] : result; 144 | }, 'yes'); 145 | 146 | let before1 = yes; 147 | let after1 = yes; 148 | let methods1 = all; 149 | let before2 = null; 150 | let after2 = null; 151 | let methods2 = null; 152 | 153 | const check = info.check; 154 | if (check) { 155 | const checkMethods = check[2] ? (Array.isArray(check[2]) ? check[2] : [check[2]]) : ['all']; 156 | [ before1, after1 ] = check1[check[1]]; 157 | methods1 = checkMethods.join(', '); 158 | 159 | if (check[0]) { 160 | before1 = before1 === no ? ' ' : before1; 161 | after1 = after1 === no ? '' : after1; 162 | [ before2, after2 ] = check2[check[1]]; 163 | methods2 = all; 164 | } 165 | } 166 | 167 | // console.log(name, before1, after1, methods1, before2, after2, methods2); 168 | 169 | const srcFileName = info.srcFile || `${fileName.indexOf('/') === -1 ? 'services/' : ''}${fileName}.js`; 170 | 171 | hooks[name] = { 172 | fileName, 173 | label: encodeURIComponent(info.label || name.toLowerCase()), 174 | src: `https://github.com/feathers-plus/feathers-hooks-common/blob/master/lib/${srcFileName}`, 175 | multi, 176 | before1, 177 | after1, 178 | methods1, 179 | before2, 180 | after2, 181 | methods2, 182 | tags: tagsHtml, 183 | desc: info.desc, 184 | guide: info.guide ? `${info.guide}`: '', 185 | func: tags.indexOf('func') !== -1, 186 | }; 187 | 188 | showTags.forEach(tag => { 189 | hooksByTag[tag] = hooksByTag[tag] || []; 190 | hooksByTag[tag].push(name) 191 | }); 192 | }); 193 | 194 | hexo.extend.tag.register('hooksByTags', () => { 195 | // console.log('hooksByTags'); 196 | let html = ''; 197 | 198 | Object.keys(hooksByTag).sort().forEach(tag => { 199 | html += `

    ${tag}

      `; 200 | 201 | hooksByTag[tag].sort().forEach(name => { 202 | const hook = hooks[name]; 203 | html += `\n
    • ${name} - ${hook.desc}${hook.func ? ` Utility function.` : ''}
    • ` 204 | }); 205 | 206 | html += '
    '; 207 | return html; 208 | }); 209 | 210 | return html; 211 | }); 212 | 213 | hexo.extend.tag.register('hooksApi', name => { 214 | // console.log('hooksApi', name); 215 | const hook = hooks[name[0]]; 216 | if (!hook) return `?????????? hook ${name} not defined.`; 217 | 218 | if (hook.func) { 219 | return `${hook.desc}${hook.func ? ' (Utility function.)' : ''}
    220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 |
    guidedetailstags
    ${hook.guide}source${hook.tags}
    `; 236 | } 237 | 238 | let html = `${hook.desc}
    239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | `; 261 | 262 | if (hook.before2 || hook.after2 || hook.methods2) { 263 | html += ` 264 | 265 | 266 | 267 | 268 | `; 269 | } 270 | 271 | html += ` 272 | 273 |
    beforeaftermethodsmulti recsguidedetailstags
    ${hook.before1}${hook.after1}${hook.methods1}${hook.multi}${hook.guide}source${hook.tags}
    ${hook.before2}${hook.after2}${hook.methods2}
    `; 274 | 275 | return html; 276 | }); 277 | 278 | hexo.extend.tag.register('hooksApiFieldNames', ([name, desc, fieldNames = 'fieldNames', type = 'dot notation']) => { 279 | // console.log('hooksApiFieldNames', name, desc, fieldNames, type); 280 | const hook = hooks[name]; 281 | if (!hook) return `?????????? hook ${name} not defined.`; 282 | 283 | return ` 284 |
    • 285 | Arguments 286 |
      • 287 | {Array < String >} ${fieldNames} 288 |
      289 |
    290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 |
    NameTypeDescription
    ${fieldNames}${type}${desc}
    `; 307 | }) 308 | 309 | hexo.extend.tag.register('hooksApiReturns', ([name, desc, result = 'result', type = 'Boolean']) => { 310 | //console.log('hooksApiReturns', name, desc, result, type); 311 | 312 | // handle a bug 313 | if (desc.substr(-1) === ',') desc = desc.substr(0, desc.length - 1); 314 | if (result.substr(-1) === ',') result = result.substr(0, result.length - 1); 315 | if (type.substr(-1) === ',') type = type.substr(0, type.length - 1); 316 | 317 | const hook = hooks[name]; 318 | if (!hook) return `?????????? hook ${name} not defined.`; 319 | 320 | return ` 321 |
    • 322 | Returns 323 |
      • 324 | {${type}} ${result} 325 |
      326 |
    327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 |
    NameTypeDescription
    ${result}${type}${desc}
    `; 344 | }); 345 | 346 | hexo.extend.tag.register('hooksApiFootnote', name => { 347 | // console.log('hooksApiFootnote', name); 348 | const hook = hooks[name[0]]; 349 | if (!hook) return `?????????? hook ${name} not defined.`; 350 | 351 | return ` 352 | 355 |
      356 |
    • See also: ${hook.guide ? `Guide ${hook.guide} and tags ` : ''}${hooks[name].tags}
    • 357 |
    358 | `; 361 | }); 362 | 363 | function setAttr(tags, tag, yes = 'yes', no = 'no') { 364 | return tags.indexOf(tag) === -1 ? no : yes; 365 | } 366 | 367 | function toCamelCase(str) { 368 | // Lower cases the string 369 | return str.toLowerCase() 370 | // Replaces any - or _ characters with a space 371 | .replace( /[-_]+/g, ' ') 372 | // Removes any non alphanumeric characters 373 | .replace( /[^\w\s]/g, '') 374 | // Uppercases the first character in each group immediately following a space 375 | // (delimited by spaces) 376 | .replace( / (.)/g, function($1) { return $1.toUpperCase(); }) 377 | // Removes spaces 378 | .replace( / /g, '' ); 379 | } 380 | 381 | function inspector(desc, obj, depth = 5) { 382 | console.log(desc); 383 | console.log(inspect(obj, { colors: true, depth })); 384 | } 385 | -------------------------------------------------------------------------------- /themes/tech-docs/scripts/inject-feathers-plus.js: -------------------------------------------------------------------------------- 1 | ;(function(root, factory) { 2 | // https://github.com/umdjs/umd/blob/master/returnExports.js 3 | if (typeof exports == 'object') { 4 | // For Node.js. 5 | module.exports = factory(root); 6 | } else if (typeof define == 'function' && define.amd) { 7 | // For AMD. Register as an anonymous module. 8 | define([], factory.bind(root, root)); 9 | } else { 10 | // For browser globals (not exposing the function separately). 11 | factory(root); 12 | } 13 | }( 14 | typeof global != 'undefined' ? global : this, 15 | function(root) { 16 | // Only works in Node 17 | root.feathers = root.feathers || {}; 18 | 19 | root.feathers.feathersBatchLoader = 'a'; 20 | })); 21 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_ad.styl: -------------------------------------------------------------------------------- 1 | // main ad placement (bottom right) 2 | #ad 3 | width: 125px 4 | // text-align: center 5 | position: fixed 6 | z-index: 99 7 | bottom: 10px 8 | right: 10px 9 | padding: 10px 10 | background-color: #fff 11 | border-radius: 3px 12 | font-size: 13px 13 | a 14 | display: inline-block 15 | color: $light 16 | font-weight: normal 17 | span 18 | color: $light 19 | display: inline-block 20 | margin-bottom: 5px 21 | img 22 | width: 125px 23 | .carbon-img, .carbon-text 24 | display: block 25 | margin-bottom: 6px 26 | font-weight: normal 27 | color: $medium 28 | .carbon-poweredby 29 | color: #aaa 30 | font-weight: normal 31 | 32 | // text ad (below page title) 33 | .bsa-cpc 34 | font-size 1em 35 | background-color #f8f8f8 36 | a._default_ 37 | text-align left 38 | display block 39 | padding 10px 15px 12px 40 | margin-bottom 20px 41 | color #666 42 | font-weight 400 43 | line-height 18px 44 | .default-image, .default-title, .default-description 45 | display inline 46 | vertical-align middle 47 | margin-right 6px 48 | .default-image 49 | img 50 | height 20px 51 | border-radius 3px 52 | vertical-align middle 53 | position relative 54 | top -1px 55 | .default-title 56 | font-weight 600 57 | .default-description:after 58 | font-size .85em 59 | content "Sponsored" 60 | color $info 61 | border 1px solid $info 62 | border-radius 3px 63 | padding 0 4px 1px 64 | margin-left 6px 65 | .default-ad 66 | display none 67 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_animations.styl: -------------------------------------------------------------------------------- 1 | .rotating-clockwise 2 | animation: 3s rotating-clockwise linear infinite 3 | 4 | i.rotating-clockwise 5 | display: inline-block 6 | animation-duration: 2s 7 | 8 | @keyframes rotating-clockwise 9 | from 10 | transform: rotate(0) 11 | to 12 | transform: rotate(360deg) 13 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_common.styl: -------------------------------------------------------------------------------- 1 | @import "_settings" 2 | @import "_syntax" 3 | @import "_ad" 4 | 5 | body 6 | font-family: $body-font 7 | font-size: $body-font-size 8 | -webkit-font-smoothing: antialiased 9 | -moz-osx-font-smoothing: grayscale 10 | color: $medium 11 | background-color: white 12 | margin: 0 13 | &.docs 14 | padding-top: $header-height 15 | 16 | @media screen and (max-width: 900px) 17 | body.docs 18 | padding-top: 0 19 | 20 | a 21 | text-decoration: none 22 | color: $medium 23 | 24 | img 25 | border: none 26 | 27 | h1, h2, h3, h4, strong 28 | font-weight: 600 29 | color: $dark 30 | 31 | code, pre 32 | font-family: $code-font 33 | font-size: $code-font-size 34 | background-color: $codebg 35 | -webkit-font-smoothing: initial 36 | -moz-osx-font-smoothing: initial 37 | 38 | code 39 | color: #e96900 40 | padding: 3px 5px 41 | margin: 0 2px 42 | border-radius: 2px 43 | white-space: nowrap 44 | 45 | em 46 | color: $light 47 | 48 | p 49 | word-spacing: 0.05em 50 | 51 | a.button 52 | padding: 0.75em 2em 53 | border-radius: 2em 54 | display: inline-block 55 | color: #fff 56 | background-color: lighten($green, 8%) 57 | transition: all .15s ease 58 | box-sizing: border-box 59 | border: 1px solid lighten($green, 8%) 60 | &.white 61 | background-color: #fff 62 | color: $green 63 | 64 | .highlight 65 | overflow-x: auto 66 | background-color: $codebg 67 | padding: .4em 0 0 68 | line-height: 1.1em 69 | border-radius: $radius 70 | position: relative 71 | table, tr, td 72 | width: 100% 73 | border-collapse: collapse 74 | padding: 0 75 | margin: 0 76 | .gutter 77 | width: 1.5em 78 | .code 79 | $code-line-height = 1.5em 80 | pre 81 | padding: 1.2em 1.4em 82 | line-height: $code-line-height 83 | margin: 0 84 | .line 85 | min-height: $code-line-height 86 | &.html, &.js, &.bash, &.css 87 | .code:before 88 | position: absolute 89 | top: 0 90 | right: 0 91 | color: #ccc 92 | text-align: right 93 | font-size: .75em 94 | padding: 5px 10px 0 95 | line-height: 15px 96 | height: 15px 97 | font-weight: 600 98 | &.html .code:before 99 | content: "HTML" 100 | &.js .code:before 101 | content: "JS" 102 | &.bash .code:before 103 | content: "Shell" 104 | &.css .code:before 105 | content: "CSS" 106 | 107 | #main 108 | position: relative 109 | z-index: 1 110 | padding: 0 60px 30px 111 | overflow-x: hidden 112 | 113 | #nav 114 | .nav-link 115 | cursor: pointer 116 | .nav-dropdown-container 117 | .nav-link 118 | &:hover:not(.current) 119 | border-bottom: none 120 | &:hover 121 | .nav-dropdown 122 | display: block 123 | &.language, &.ecosystem 124 | margin-left: 20px 125 | .arrow 126 | pointer-events: none 127 | .nav-dropdown 128 | display: none 129 | box-sizing: border-box 130 | max-height: "calc(100vh - %s)" % $header-height 131 | overflow-y: auto 132 | position: absolute 133 | top: 100% 134 | right: -15px 135 | background-color: #fff 136 | padding: 10px 0 137 | border: 1px solid #ddd 138 | border-bottom-color: #ccc 139 | text-align: left 140 | border-radius: 4px 141 | white-space: nowrap 142 | li 143 | line-height: 1.8em 144 | margin: 0 145 | display: block 146 | > ul 147 | padding-left: 0 148 | &:first-child 149 | h4 150 | margin-top: 0 151 | padding-top: 0 152 | border-top: 0 153 | a, h4 154 | padding: 0 24px 0 20px 155 | h4 156 | // text-transform: uppercase 157 | margin: .45em 0 0 158 | padding-top: .45em 159 | border-top: 1px solid #eee 160 | a 161 | color: lighten($dark, 10%) 162 | font-size: .9em 163 | display: block 164 | &:hover 165 | color: $green 166 | .arrow 167 | display: inline-block 168 | vertical-align: middle 169 | margin-top: -1px 170 | margin-left: 6px 171 | margin-right: -14px 172 | width: 0 173 | height: 0 174 | border-left: 4px solid transparent 175 | border-right: 4px solid transparent 176 | border-top: 5px solid #ccc 177 | 178 | sup.beta.beta 179 | font-size: .6em 180 | margin-left: .7em 181 | text-transform: uppercase 182 | opacity: .6 183 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_demo.styl: -------------------------------------------------------------------------------- 1 | #demo, .demo, .content .demo 2 | border: 1px solid #eee 3 | border-radius: $radius 4 | padding: 25px 35px 5 | margin-top: 1em 6 | margin-bottom: 40px 7 | font-size: 1.2em 8 | line-height: 1.5em 9 | -webkit-user-select: none 10 | -moz-user-select: none 11 | -ms-user-select: none 12 | user-select: none 13 | overflow-x: auto 14 | h1 15 | margin: 0 0 .5em 16 | font-size: 1.8em 17 | ul, ol 18 | padding-left: 1.5em 19 | padding-bottom: .2em !important 20 | &:first-child 21 | margin-top: 0 22 | &:last-child 23 | margin-bottom: 0 24 | li 25 | color: $medium 26 | // !!TODO: Check to make sure this isn't here for a good reason. 27 | // cursor: pointer 28 | // -ms-user-select: none 29 | // -moz-user-select: none 30 | // -webkit-user-select: none 31 | &.done 32 | color: $light 33 | text-decoration: line-through 34 | p 35 | margin: 0 !important 36 | padding: 0 !important 37 | &:first-child 38 | margin-top: 0 39 | &:last-child 40 | margin-bottom: 0 41 | textarea 42 | width: 100% 43 | resize: vertical 44 | 45 | 46 | 47 | ul#demo, ul.demo 48 | li 49 | margin-left: 1.5em 50 | 51 | @media screen and (max-width: 900px) 52 | #demo, .demo 53 | margin-left: 0 54 | 55 | .benchmark-table 56 | margin: 0 auto 57 | text-align: center 58 | 59 | tbody > tr > th 60 | text-align: right 61 | 62 | th, td 63 | padding: 3px 7px 64 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_header.styl: -------------------------------------------------------------------------------- 1 | $header-height = 40px 2 | 3 | #header 4 | background-color: #fff 5 | height: $header-height 6 | padding: $heading-padding-vertical 60px 7 | position: relative 8 | z-index: 2 9 | 10 | body.docs 11 | #header 12 | position: fixed 13 | width: 100% 14 | top: 0 15 | #nav 16 | position: fixed 17 | 18 | #nav 19 | list-style-type: none 20 | margin: 0 21 | padding: 0 22 | position: absolute 23 | right: 30px 24 | top: $heading-padding-vertical 25 | height: $header-height 26 | line-height: $header-height 27 | .break 28 | display: none 29 | li 30 | display: inline-block 31 | position: relative 32 | margin: 0 .6em 33 | 34 | .nav-dropdown 35 | .nav-link 36 | &:hover, &.current 37 | border-bottom: none 38 | &.current 39 | &::after 40 | content: "" 41 | width: 0 42 | height: 0 43 | border-left: 5px solid $green 44 | border-top: 3px solid transparent 45 | border-bottom: 3px solid transparent 46 | position: absolute 47 | top: 50% 48 | margin-top: -4px 49 | left: 8px 50 | 51 | .nav-link 52 | padding-bottom: 3px 53 | &:hover, &.current 54 | border-bottom: 3px solid $green 55 | &.team 56 | margin-left: 10px 57 | 58 | .new-label 59 | position: absolute 60 | top: 3px 61 | left: 110% 62 | background-color: $green 63 | color: #fff 64 | line-height: 16px 65 | height: 16px 66 | font-size: 9px 67 | font-weight: bold 68 | font-family: $code-font 69 | padding: 1px 4px 0 6px 70 | border-radius: 4px 71 | 72 | .search-query 73 | height: 30px 74 | line-height: 30px 75 | box-sizing: border-box 76 | padding: 0 15px 0 30px 77 | border: 1px solid #e3e3e3 78 | color: $dark 79 | outline: none 80 | border-radius: 15px 81 | margin-right: 10px 82 | transition: border-color .2s ease 83 | background: #fff url(../images/search.png) 8px 5px no-repeat 84 | background-size: 20px 85 | vertical-align: middle !important 86 | &:focus 87 | border-color: $green 88 | 89 | #logo 90 | display: inline-block 91 | font-size: 1.5em 92 | line-height: $header-height 93 | color: $dark 94 | font-family: $logo-font 95 | font-weight: 500 96 | img 97 | vertical-align: middle 98 | margin-right: 6px 99 | width: $header-height 100 | height: $header-height 101 | 102 | #mobile-bar 103 | position: fixed 104 | top: 0 105 | left: 0 106 | width: 100% 107 | height: 40px 108 | background-color: #fff 109 | z-index: 9 110 | display: none 111 | box-shadow: 0 0 2px rgba(0,0,0,.25) 112 | .menu-button 113 | position: absolute 114 | width: 24px 115 | height: 24px 116 | top: 8px 117 | left: 12px 118 | background: url(../images/menu.png) center center no-repeat 119 | background-size: 24px 120 | .logo 121 | position: absolute 122 | width: 30px 123 | height: 30px 124 | background: url(../images/logo.png) center center no-repeat 125 | top: 5px 126 | left: 50% 127 | margin-left: -15px 128 | background-size: 30px 129 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_migration.styl: -------------------------------------------------------------------------------- 1 | .content.guide[class*="migration"] 2 | h2, h3 3 | > sup 4 | margin-left: .3em 5 | color: #b9465c 6 | .upgrade-path 7 | margin-top: 2em 8 | padding: 2em 9 | background: rgba(73, 195, 140, .1) 10 | border-radius: 2px 11 | > h4 12 | margin-top: 0 13 | > p:last-child 14 | margin-bottom: 0 15 | padding-bottom: 0 16 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_offline-menu.styl: -------------------------------------------------------------------------------- 1 | .content.menu 2 | font-size: 1.2em 3 | .menu-root 4 | padding-left: 0 5 | #search-form, .algolia-autocomplete, input 6 | width: 100% 7 | .aa-dropdown-menu 8 | box-sizing: border-box 9 | h3 10 | margin: 1.5em 0 .75em 11 | &:before, &:after 12 | display: none 13 | li 14 | list-style-type: none 15 | margin-top: .1em 16 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_settings.styl: -------------------------------------------------------------------------------- 1 | // font faces 2 | $body-font = "Source Sans Pro", "Helvetica Neue", Arial, sans-serif 3 | $logo-font = "Dosis", "Source Sans Pro", "Helvetica Neue", Arial, sans-serif 4 | $code-font = "Roboto Mono", Monaco, courier, monospace 5 | 6 | // font sizes 7 | $body-font-size = 15px 8 | $code-font-size = .8em 9 | 10 | // colors 11 | $dark = #2c3e50 12 | $medium = #34495e 13 | $light = #7f8c8d 14 | $green = #42b983 15 | $border = #dddddd 16 | $codebg = #f8f8f8 17 | $red = #ff6666 18 | $info = #1C90F3 19 | 20 | $radius = 2px 21 | $content-padding-top = 30px 22 | $header-inner-height = 41px 23 | $heading-padding-vertical = 10px 24 | $header-height = $header-inner-height + $heading-padding-vertical * 2 25 | $mobile-header-height = 40px 26 | $heading-link-padding-top = $header-height + $content-padding-top 27 | $mobile-heading-link-padding-top = $mobile-header-height + $content-padding-top 28 | $h2-margin-top = 45px 29 | $h3-margin-top = 52px 30 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_sidebar.styl: -------------------------------------------------------------------------------- 1 | @import "_settings" 2 | 3 | .sidebar 4 | position: absolute 5 | z-index: 10 6 | top: $header-height 7 | left: 0 8 | bottom: 0 9 | overflow-x: hidden 10 | overflow-y: auto 11 | -webkit-overflow-scrolling: touch 12 | -ms-overflow-style: none 13 | h2 14 | margin-top: .2em 15 | ul 16 | list-style-type: none 17 | margin: 0 18 | line-height: 1.5em 19 | padding-left: 1em 20 | li 21 | margin-top: .5em 22 | .sidebar-inner 23 | width: 260px 24 | padding: $content-padding-top + 10px 20px 60px 60px 25 | .version-select 26 | vertical-align: middle 27 | margin-left: 5px 28 | .menu-root 29 | padding-left: 0 30 | .menu-sub 31 | font-size: .85em 32 | .sidebar-link 33 | color: $light 34 | &.current 35 | font-weight: 600 36 | color: $green 37 | &.new 38 | &:after 39 | content: "NEW" 40 | display: inline-block 41 | font-size: 10px 42 | font-weight: 600 43 | color: #fff 44 | background-color: $green 45 | line-height: 14px 46 | padding: 0 4px 47 | border-radius: 3px 48 | margin-left: 5px 49 | vertical-align: middle 50 | position: relative 51 | top: -1px 52 | &:hover 53 | border-bottom: 2px solid $green 54 | .section-link 55 | &.active 56 | font-weight: bold 57 | color: $green 58 | .main-menu 59 | margin-bottom: 20px 60 | display: none 61 | padding-left: 0 62 | .main-sponsor 63 | color: $light 64 | font-size: .85em 65 | .logo 66 | color: $light 67 | margin-top: 20px 68 | text-align: center 69 | font-weight: bold 70 | display block 71 | &:last-child 72 | margin-top 10px 73 | margin-bottom 20px 74 | img, a 75 | width: 125px 76 | .become-backer 77 | border: 1px solid $green 78 | border-radius: 2em 79 | display: inline-block 80 | color: $green 81 | font-size: .8em 82 | width: 125px 83 | padding: 4px 0 84 | text-align: center 85 | margin-bottom: 20px 86 | .nav-dropdown 87 | h4 88 | font-weight: normal 89 | margin: 0 90 | 91 | @media screen and (max-width: 900px) 92 | .sidebar 93 | position: fixed 94 | z-index: 10 95 | background-color: #f9f9f9 96 | height: 100% 97 | top: 0 98 | left: 0 99 | box-shadow: 0 0 10px rgba(0,0,0,.2) 100 | transition: all .4s cubic-bezier(0.4, 0, 0, 1) 101 | -webkit-transform: translate(-280px, 0) 102 | transform: translate(-280px, 0) 103 | .sidebar-inner 104 | padding: 50px 10px 10px 20px 105 | box-sizing: border-box 106 | .sidebar-inner-index 107 | padding: 10px 10px 10px 20px 108 | .search-query 109 | width: 200px 110 | margin-bottom: 10px 111 | .main-menu 112 | display: block 113 | &.open 114 | -webkit-transform: translate(0, 0) 115 | transform: translate(0, 0) 116 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_sponsor.styl: -------------------------------------------------------------------------------- 1 | // frontpage 2 | #sponsors 3 | text-align: center 4 | padding: 35px 40px 45px 5 | background-color: #f6f6f6 6 | .inner 7 | max-width: 700px 8 | margin: 0px auto 9 | h3 10 | color: #999 11 | margin: 0 0 10px 12 | a 13 | margin: 20px 15px 0 14 | position: relative 15 | a, img 16 | width: 100px 17 | display: inline-block 18 | vertical-align: middle 19 | img 20 | transition: all .3s ease 21 | filter: grayscale(100%) 22 | opacity: 0.66 23 | &:hover 24 | filter: none 25 | opacity: 1 26 | a.vip 27 | display: block 28 | margin: 30px auto 15px 29 | width: 200px 30 | img 31 | width: 200px 32 | .become-sponsor 33 | margin-top: 40px 34 | font-size: .9em 35 | font-weight: 700 36 | width: auto 37 | background-color: transparent 38 | 39 | .open-collective-sponsors 40 | margin-top 60px 41 | a, img 42 | width: auto 43 | max-width: 100px 44 | max-height: 60px 45 | h4 46 | color #999 47 | margin-bottom 0 48 | &.active 49 | img 50 | filter: none 51 | opacity: 1 52 | 53 | // support-vuejs page 54 | .content 55 | .sponsor-section 56 | text-align center 57 | margin-top 0 58 | margin-bottom 60px 59 | 60 | .patreon-sponsors 61 | a, img 62 | width: 120px 63 | display: inline-block 64 | vertical-align: middle 65 | a 66 | margin: 10px 20px 67 | 68 | .open-collective-sponsors 69 | img 70 | max-height 80px 71 | margin-right 20px 72 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_style-guide.styl: -------------------------------------------------------------------------------- 1 | $style-guide-bad-bg = lighten(desaturate($red, 80%), 80%) 2 | $style-guide-bad-text = darken(desaturate($red, 80%), 20%) 3 | $style-guide-good-bg = lighten(desaturate($green, 80%), 85%) 4 | $style-guide-good-text = darken(desaturate($green, 80%), 10%) 5 | 6 | $style-guide-priority-a-bg = $style-guide-bad-text 7 | $style-guide-priority-a-color = white 8 | $style-guide-priority-b-bg = $style-guide-bad-text 9 | $style-guide-priority-b-color = white 10 | $style-guide-priority-c-bg = steelblue 11 | $style-guide-priority-c-color = white 12 | $style-guide-priority-d-bg = $style-guide-bad-text 13 | $style-guide-priority-d-color = white 14 | 15 | .style-guide 16 | .style-example, details, .style-enforcement 17 | border-radius $radius 18 | margin: 1.6em 0 19 | padding: 1.6em 20 | h4 21 | margin-top: 0 22 | figure, p 23 | &:last-child 24 | margin-bottom: 0 25 | padding-bottom: 0 26 | .style-example 27 | &.example-bad 28 | background: $style-guide-bad-bg 29 | h4 30 | color: $style-guide-bad-text 31 | &.example-good 32 | background: $style-guide-good-bg 33 | h4 34 | color: $style-guide-good-text 35 | details, .style-enforcement 36 | background-color: #eee 37 | details 38 | display: block // Add the correct display in IE and Edge. 39 | position: relative 40 | &:not([open]) summary 41 | &::after 42 | content: "..." 43 | &:hover 44 | background: rgba(255, 255, 255, .3) 45 | summary 46 | cursor: pointer 47 | padding: 1.6em 48 | margin: -1.6em 49 | outline: none 50 | > h4 51 | display: inline-block 52 | margin: 0 53 | .style-enforcement 54 | table 55 | width: 100% 56 | background-color: $codebg 57 | border-radius: $radius 58 | th, td 59 | padding: .4em 60 | text-align: center 61 | th 62 | padding-bottom: .2em 63 | td 64 | padding-top: .2em 65 | .style-rule-tag 66 | background-color: $codebg 67 | border-radius: $radius 68 | font-size: .9em 69 | color: $style-guide-good-text 70 | font-weight: 600 71 | text-transform: uppercase 72 | padding: .1em .4em 73 | a > .style-rule-tag 74 | color: $green 75 | sup 76 | text-transform: uppercase 77 | font-size: .7em 78 | margin-left: 1em 79 | pointer-events: all 80 | position: absolute 81 | [data-p="a"] 82 | color: #6b2a2a 83 | [data-p="b"] 84 | color: #8c480a 85 | [data-p="c"] 86 | color: #2b5a99 87 | [data-p="d"] 88 | content: #3f536d 89 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_syntax.styl: -------------------------------------------------------------------------------- 1 | .gutter pre 2 | color: #999 3 | 4 | pre 5 | color: #525252 6 | .function .keyword, 7 | .constant 8 | color: #0092db 9 | .keyword, 10 | .attribute 11 | color: #e96900 12 | .number, 13 | .literal 14 | color: #AE81FF 15 | .tag, 16 | .tag .title, 17 | .change, 18 | .winutils, 19 | .flow, 20 | .lisp .title, 21 | .clojure .built_in, 22 | .nginx .title, 23 | .tex .special 24 | color: #2973b7 25 | .class .title 26 | color: white 27 | .symbol, 28 | .symbol .string, 29 | .value, 30 | .regexp 31 | color: $green 32 | .title 33 | color: #A6E22E 34 | .tag .value, 35 | .string, 36 | .subst, 37 | .haskell .type, 38 | .preprocessor, 39 | .ruby .class .parent, 40 | .built_in, 41 | .sql .aggregate, 42 | .django .template_tag, 43 | .django .variable, 44 | .smalltalk .class, 45 | .javadoc, 46 | .django .filter .argument, 47 | .smalltalk .localvars, 48 | .smalltalk .array, 49 | .attr_selector, 50 | .pseudo, 51 | .addition, 52 | .stream, 53 | .envvar, 54 | .apache .tag, 55 | .apache .cbracket, 56 | .tex .command, 57 | .prompt 58 | color: $green 59 | .comment, 60 | .java .annotation, 61 | .python .decorator, 62 | .template_comment, 63 | .pi, 64 | .doctype, 65 | .deletion, 66 | .shebang, 67 | .apache .sqbracket, 68 | .tex .formula 69 | color: #b3b3b3 70 | .coffeescript .javascript, 71 | .javascript .xml, 72 | .tex .formula, 73 | .xml .javascript, 74 | .xml .vbscript, 75 | .xml .css, 76 | .xml .cdata 77 | opacity: 0.5 78 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/_team.styl: -------------------------------------------------------------------------------- 1 | @import "_settings" 2 | 3 | #team-members 4 | .sort-by-distance-button 5 | display: inline-block 6 | padding: .4em .7em .45em 7 | font-weight: bold 8 | font-size: .5em 9 | text-transform: uppercase 10 | line-height: 1 11 | border: none 12 | background: $medium 13 | color: white 14 | border-radius: 3px 15 | position: relative 16 | cursor: pointer 17 | float: right 18 | margin-top: .3em 19 | i 20 | margin-right: .25em 21 | &:last-child 22 | margin-right: 0 23 | &[disabled] 24 | opacity: .7 25 | cursor: default 26 | .vuer 27 | display: flex 28 | padding: 25px 0 29 | border-bottom: 1px dotted #ddd 30 | &:first-of-type 31 | margin-top: 15px 32 | &:last-of-type 33 | border-bottom: none 34 | .avatar 35 | flex: 0 0 80px 36 | img 37 | border-radius: 50% 38 | .profile 39 | padding-left: 26px 40 | flex: 1 41 | h3 42 | margin: 0 43 | font-size: 1.3em 44 | &::before, &::after 45 | display: none 46 | & > sup 47 | text-transform: uppercase 48 | font-size: .7em 49 | letter-spacing: .3px 50 | padding: 2px 5px 51 | margin-left: 10px 52 | color: transparentify($dark, #f9f7f5, .6) 53 | background: #f9f7f5 54 | border-radius: 5px 55 | // NOTE: Removing the little signal icon for now 56 | // .distance 57 | // position: relative 58 | // &:before 59 | // content: "\f1eb" 60 | // font-family: FontAwesome 61 | // position: absolute 62 | // top: 50% 63 | // margin-top: -.5em 64 | // line-height: 1 65 | // right: 100% 66 | // transform: rotate(-90deg) 67 | .user-match 68 | cursor: help 69 | color: steelblue 70 | &:after 71 | content: "\f06a" 72 | font-family: FontAwesome 73 | font-size: .75em 74 | vertical-align: super 75 | margin-left: 4px 76 | margin-right: 2px 77 | position: relative 78 | dl 79 | margin: .6em 0 0 80 | dt, dd, ul, li 81 | display: inline 82 | padding: 0 83 | margin: 0 84 | line-height: 1.3 85 | dt 86 | text-transform: uppercase 87 | font-size: .84em 88 | font-weight: 600 89 | &::after 90 | content: "" 91 | margin-right: 7px 92 | i 93 | width: 14px 94 | text-align: center 95 | &.fa-map-marker 96 | font-size: 1.15em 97 | &.fa-globe 98 | font-size: 1.2em 99 | &.fa-link 100 | font-size: 1.05em 101 | dd 102 | font-weight: 600 103 | &::after 104 | display: block 105 | content: " " 106 | margin-top: .6em 107 | li 108 | display: inline-block 109 | &::after 110 | display: inline-block 111 | content: "·" 112 | margin: 0 8px 113 | &:last-child::after 114 | content: "" 115 | .social 116 | a 117 | display: inline-block 118 | line-height: 1 119 | vertical-align: middle 120 | margin-right: 4px 121 | &.github 122 | color: #000 123 | &.twitter 124 | color: #1da1f3 125 | i 126 | vertical-align: text-bottom 127 | font-size: 1.3em 128 | 129 | @media (max-width: 640px) 130 | #team-members 131 | .vuer 132 | .profile 133 | h3 134 | sup 135 | display: inline-block 136 | margin-left: 0 137 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/benchmark.styl: -------------------------------------------------------------------------------- 1 | #benchmark-results 2 | margin-bottom: 2em 3 | ul 4 | list-style-type: none 5 | padding: 0 6 | margin-left: 0 7 | 8 | .framework, .time, .bar, .inner 9 | display: inline-block 10 | 11 | .framework 12 | width: 4.2em 13 | // text-align: right 14 | margin-right: 1em 15 | font-weight: 600 16 | 17 | .time 18 | width: 4.2em 19 | margin-right: 1em 20 | 21 | .bar 22 | width: 60% 23 | &.min .inner 24 | background-color: #e74c3c 25 | 26 | .inner 27 | height: 3px 28 | vertical-align: middle 29 | background-color: #3498db 30 | 31 | @media screen and (max-width: 600px) 32 | #benchmark-results 33 | .bar 34 | width: 45% 35 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/index.styl: -------------------------------------------------------------------------------- 1 | @import "_common" 2 | @import "_header" 3 | @import "_sidebar" 4 | @import "_sponsor" 5 | 6 | $width = 900px 7 | $space = 50px 8 | 9 | body 10 | background-color: darken($light, 10%) 11 | 12 | #logo 13 | span 14 | font-size: 1.2em 15 | img 16 | display: none 17 | 18 | .sidebar 19 | display: none 20 | 21 | #mobile-bar 22 | &.top 23 | background-color: transparent 24 | box-shadow: none 25 | .logo 26 | display: none 27 | 28 | #hero 29 | padding: $space 40px 30 | background-color: #fff 31 | .inner 32 | max-width: $width 33 | margin: 0 auto 34 | .left, .right 35 | display: inline-block 36 | vertical-align: top 37 | .left 38 | width: 39% 39 | .right 40 | width: 61% 41 | .hero-logo 42 | width: 215px 43 | height: 215px 44 | float: right 45 | margin-right: 60px 46 | h1 47 | font-weight: 300 48 | margin: 0 49 | font-size: 3.2em 50 | h2 51 | font-family: $logo-font 52 | font-weight: 500 53 | font-size: 2.4em 54 | margin: 0 0 10px 55 | display: none 56 | .button 57 | margin: 1em 0 58 | font-size: 1.05em 59 | font-weight: 600 60 | letter-spacing: .1em 61 | min-width: 8em 62 | text-align: center 63 | &:first-child 64 | margin-right: 1em 65 | .social-buttons 66 | list-style-type: none 67 | padding: 0 68 | li 69 | display: inline-block 70 | vertical-align: middle 71 | margin-right: 15px 72 | 73 | #highlights 74 | background-color: #fff 75 | padding-bottom: 70px 76 | .inner 77 | max-width: $width 78 | margin: 0 auto 79 | text-align: center 80 | .point 81 | width: 33% 82 | display: inline-block 83 | vertical-align: top 84 | box-sizing: border-box 85 | padding: 0 2em 86 | h2 87 | color: $green 88 | font-size: 1.5em 89 | font-weight: 400 90 | margin: 0 91 | padding: .5em 0 92 | p 93 | color: $light 94 | 95 | #footer 96 | padding: $space 0 97 | color: #fff 98 | text-align: center 99 | a 100 | font-weight: 700 101 | color: #fff 102 | 103 | @media screen and (max-width: $width) 104 | body 105 | -webkit-text-size-adjust: none 106 | font-size: 14px 107 | .sidebar 108 | display: block 109 | #header 110 | display: none 111 | #mobile-bar 112 | display: block 113 | #hero 114 | padding: $space 40px 30px 115 | .hero-logo 116 | float: none 117 | margin: 30px 0 15px 118 | width: 140px 119 | height: 140px 120 | .left, .right 121 | text-align: center 122 | width: 100% 123 | h1 124 | font-size: 2em 125 | h2 126 | display: block 127 | .button 128 | font-size: .9em 129 | #highlights 130 | .point 131 | display: block 132 | margin: 0 auto 133 | width: 300px 134 | padding: 0 40px 30px 135 | &:before 136 | content: "—" 137 | color: $green 138 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/page.styl: -------------------------------------------------------------------------------- 1 | @import "_common" 2 | @import "_animations" 3 | @import "_header" 4 | @import "_demo" 5 | @import "_sponsor" 6 | @import "_migration" 7 | @import "_sidebar" 8 | @import "_offline-menu" 9 | @import "_team" 10 | @import "_style-guide" 11 | 12 | #header 13 | box-shadow: 0 0 1px rgba(0,0,0,.25) 14 | transition: background-color .3s ease-in-out 15 | 16 | .content 17 | position: relative 18 | padding: 2.2em 0 19 | max-width: 600px 20 | margin: 0 auto 21 | padding-left: 50px 22 | &.api 23 | > a:first-of-type > h2 24 | margin-top: 0 25 | padding-top: 0 26 | ul 27 | padding-left: 1.25em 28 | line-height: 1.4em 29 | ul, p:not(.tip) 30 | padding-bottom: 0 31 | margin: 1.2em 0 32 | a.button 33 | font-size: .9em 34 | color: #fff 35 | margin: .2em 0 36 | width: 180px 37 | text-align: center 38 | padding: 12px 24px 39 | display: inline-block 40 | vertical-align: middle 41 | img 42 | max-width: 100% 43 | span.light 44 | color: $light 45 | span.info 46 | font-size: .85em 47 | display: inline-block 48 | vertical-align: middle 49 | width: 280px 50 | margin-left: 20px 51 | h1 52 | margin: 0 0 1em 53 | h2, h3 54 | &:before 55 | content: "" 56 | display: block 57 | margin-top: -1 * $heading-link-padding-top 58 | height: $heading-link-padding-top 59 | visibility: hidden 60 | h2 61 | margin: $h2-margin-top 0 .8em 62 | padding-bottom: .7em 63 | border-bottom: 1px solid $border 64 | z-index: -1 65 | h3 66 | margin: $h3-margin-top 0 1.2em 67 | position: relative 68 | z-index: -1 69 | &:after 70 | content: "#" 71 | color: $green 72 | position: absolute 73 | left: -0.7em 74 | bottom: -2px 75 | font-size: 1.2em 76 | font-weight: bold 77 | figure 78 | margin: 1.2em 0 79 | p, ul, ol 80 | line-height: 1.6em 81 | // HACK: Create area underneath paragraphs 82 | // and lists that will be on top of heading 83 | // anchors, for easier text highlighting. 84 | margin: 1.2em 0 -1.2em 85 | padding-bottom: 1.2em 86 | position: relative 87 | z-index: 1 88 | ul, ol 89 | padding-left: 1.5em 90 | // FIX: Some link click targets are covered without this 91 | position: inherit 92 | a 93 | color: $green 94 | font-weight: 600 95 | blockquote 96 | margin: 2em 0 97 | padding-left: 20px 98 | border-left: 4px solid $green 99 | p 100 | font-weight: 600 101 | margin-left: 0 102 | margin-bottom: 0 103 | padding-bottom: 0 104 | iframe 105 | margin: 1em 0 106 | > table 107 | border-spacing: 0 108 | border-collapse: collapse 109 | margin: 1.2em auto 110 | margin-left: 1.8em 111 | padding: 0 112 | display: block 113 | overflow-x: auto 114 | font-size: smaller 115 | td, th 116 | line-height: 1.5em 117 | padding: .4em .8em 118 | border: none 119 | border: 1px solid #ddd 120 | th 121 | font-weight: bold 122 | text-align: left 123 | background-color: #f8f8f8 124 | code 125 | background-color: #efefef 126 | tr:nth-child(2n) 127 | background-color: #f8f8f8 128 | code 129 | background-color: #efefef 130 | p 131 | &.tip, &.success 132 | padding: 12px 24px 12px 30px 133 | margin: 2em 0 134 | border-left-width: 4px 135 | border-left-style: solid 136 | background-color: $codebg 137 | position: relative 138 | border-bottom-right-radius: $radius 139 | border-top-right-radius: $radius 140 | &:before 141 | position: absolute 142 | top: 14px 143 | left: -12px 144 | color: #fff 145 | width: 20px 146 | height: 20px 147 | border-radius: 100% 148 | text-align: center 149 | line-height: 20px 150 | font-weight: bold 151 | font-family: $logo-font 152 | font-size: 14px 153 | code 154 | background-color: #efefef 155 | em 156 | color: $medium 157 | &.tip 158 | border-left-color: $red 159 | &:before 160 | content: "!" 161 | background-color: $red 162 | &.success 163 | border-left-color: $green 164 | &:before 165 | content: "\f00c" 166 | font-family: FontAwesome 167 | background-color: $green 168 | 169 | .guide-links 170 | margin-top: 2em 171 | height: 1em 172 | 173 | .footer 174 | color: $light 175 | margin-top: 2em 176 | padding-top: 2em 177 | border-top: 1px solid #e5e5e5 178 | font-size: .9em 179 | 180 | #main.fix-sidebar 181 | position: static 182 | .sidebar 183 | position: fixed 184 | 185 | @media screen and (min-width: 1590px) 186 | #header 187 | background-color: rgba(255,255,255,.4) 188 | 189 | @media screen and (max-width: 1300px) 190 | .content.with-sidebar 191 | margin-left: 290px 192 | #ad 193 | z-index: 1 194 | position: relative 195 | padding: 0 196 | bottom: 0 197 | right: 0 198 | float: right 199 | padding: 0 0 20px 30px 200 | 201 | @media screen and (max-width: 900px) 202 | body 203 | -webkit-text-size-adjust: none 204 | font-size: 14px 205 | #header 206 | display: none 207 | #logo 208 | display: none 209 | .nav-link 210 | padding-bottom: 1px 211 | &:hover, &.current 212 | border-bottom: 2px solid $green 213 | #mobile-bar 214 | display: block 215 | #main 216 | padding: 2em 1.4em 0 217 | .highlight pre 218 | padding: 1.2em 1em 219 | .content 220 | padding-left 0 221 | &.with-sidebar 222 | margin: auto 223 | h2, h3 224 | &:before 225 | content: "" 226 | display: block 227 | margin-top: -1 * $mobile-heading-link-padding-top 228 | height: $mobile-heading-link-padding-top 229 | visibility: hidden 230 | .footer 231 | margin-left: 0 232 | text-align: center 233 | 234 | @media screen and (max-width: 560px) 235 | #downloads 236 | text-align: center 237 | margin-bottom: 25px 238 | .info 239 | margin-top: 5px 240 | margin-left: 0 241 | iframe 242 | margin: 0 !important 243 | -------------------------------------------------------------------------------- /themes/tech-docs/source/css/search.styl: -------------------------------------------------------------------------------- 1 | @import "_settings" 2 | 3 | $border = #ddd 4 | 5 | .algolia-autocomplete 6 | line-height: normal 7 | 8 | .aa-dropdown-menu 9 | width: 100% 10 | border-color: #999 11 | font-size: 0.9rem 12 | 13 | @media (min-width: 768px) 14 | .aa-dropdown-menu 15 | min-width: 515px 16 | 17 | .algolia-docsearch-suggestion 18 | border-color: $border 19 | 20 | .algolia-docsearch-suggestion--content 21 | color: $dark 22 | 23 | .algolia-docsearch-suggestion--subcategory-column 24 | border-color: $border 25 | 26 | .algolia-docsearch-suggestion--category-header 27 | background: $green 28 | 29 | .algolia-docsearch-footer 30 | border-color: $border 31 | 32 | .algolia-docsearch-suggestion--category-header .algolia-docsearch-suggestion--highlight 33 | background: rgba(255, 255, 255, 0.6) 34 | 35 | .algolia-docsearch-suggestion--highlight 36 | color: #2c815b 37 | 38 | .aa-cursor .algolia-docsearch-suggestion--content 39 | color: $dark 40 | -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/android-icon-144x144.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/android-icon-192x192.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/android-icon-36x36.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/android-icon-48x48.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/android-icon-72x72.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/android-icon-96x96.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon-114x114.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon-120x120.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon-144x144.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon-152x152.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon-180x180.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon-57x57.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon-60x60.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon-72x72.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon-76x76.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon-precomposed.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/apple-icon.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff 3 | -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/favicon-16x16.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/favicon-32x32.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/favicon-96x96.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/favicon.ico -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/ms-icon-144x144.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/ms-icon-150x150.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/ms-icon-310x310.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/icons/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/icons/ms-icon-70x70.png -------------------------------------------------------------------------------- /themes/tech-docs/source/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/source/images/logo.png -------------------------------------------------------------------------------- /themes/tech-docs/source/js/common.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 3 | initMobileMenu() 4 | if (PAGE_TYPE) { 5 | initVersionSelect() 6 | initSubHeaders() 7 | initApiSpecLinks() 8 | initLocationHashFuzzyMatching() 9 | } 10 | 11 | function initApiSpecLinks () { 12 | var apiContent = document.querySelector('.content.api') 13 | if (apiContent) { 14 | var apiTitles = [].slice.call(apiContent.querySelectorAll('h3')) 15 | apiTitles.forEach(function (titleNode) { 16 | var ulNode = titleNode.parentNode.nextSibling 17 | if (ulNode.tagName !== 'UL') { 18 | ulNode = ulNode.nextSibling 19 | } 20 | if (ulNode.tagName === 'UL') { 21 | var specNode = document.createElement('li') 22 | var specLink = createSourceSearchPath(titleNode.textContent) 23 | specNode.innerHTML = 'Source' 24 | ulNode.appendChild(specNode) 25 | } 26 | }) 27 | } 28 | 29 | function createSourceSearchPath(query) { 30 | query = query 31 | .replace(/\([^\)]*?\)/g, '') 32 | .replace(/(Vue\.)(\w+)/g, '$1$2" OR "$2') 33 | .replace(/vm\./g, 'Vue.prototype.') 34 | return 'https://github.com/search?utf8=%E2%9C%93&q=repo%3Avuejs%2Fvue+extension%3Ajs+' + encodeURIComponent('"' + query + '"') + '&type=Code' 35 | } 36 | } 37 | 38 | function parseRawHash (hash) { 39 | // Remove leading hash 40 | if (hash.charAt(0) === '#') { 41 | hash = hash.substr(1) 42 | } 43 | 44 | // Escape characthers 45 | try { 46 | hash = decodeURIComponent(hash) 47 | } catch (e) {} 48 | return CSS.escape(hash) 49 | } 50 | 51 | function initLocationHashFuzzyMatching () { 52 | var rawHash = window.location.hash 53 | if (!rawHash) return 54 | var hash = parseRawHash(rawHash) 55 | var hashTarget = document.getElementById(hash) 56 | if (!hashTarget) { 57 | var normalizedHash = normalizeHash(hash) 58 | var possibleHashes = [].slice.call(document.querySelectorAll('[id]')) 59 | .map(function (el) { return el.id }) 60 | possibleHashes.sort(function (hashA, hashB) { 61 | var distanceA = levenshteinDistance(normalizedHash, normalizeHash(hashA)) 62 | var distanceB = levenshteinDistance(normalizedHash, normalizeHash(hashB)) 63 | if (distanceA < distanceB) return -1 64 | if (distanceA > distanceB) return 1 65 | return 0 66 | }) 67 | window.location.hash = '#' + possibleHashes[0] 68 | } 69 | 70 | function normalizeHash (rawHash) { 71 | return rawHash 72 | .toLowerCase() 73 | .replace(/\-(?:deprecated|removed|replaced|changed|obsolete)$/, '') 74 | } 75 | 76 | function levenshteinDistance (a, b) { 77 | var m = [] 78 | if (!(a && b)) return (b || a).length 79 | for (var i = 0; i <= b.length; m[i] = [i++]) {} 80 | for (var j = 0; j <= a.length; m[0][j] = j++) {} 81 | for (var i = 1; i <= b.length; i++) { 82 | for (var j = 1; j <= a.length; j++) { 83 | m[i][j] = b.charAt(i - 1) === a.charAt(j - 1) 84 | ? m[i - 1][j - 1] 85 | : m[i][j] = Math.min( 86 | m[i - 1][j - 1] + 1, 87 | Math.min(m[i][j - 1] + 1, m[i - 1][j] + 1)) 88 | } 89 | } 90 | return m[b.length][a.length] 91 | } 92 | } 93 | 94 | /** 95 | * Mobile burger menu button and gesture for toggling sidebar 96 | */ 97 | 98 | function initMobileMenu () { 99 | var mobileBar = document.getElementById('mobile-bar') 100 | var sidebar = document.querySelector('.sidebar') 101 | var menuButton = mobileBar.querySelector('.menu-button') 102 | 103 | menuButton.addEventListener('click', function () { 104 | sidebar.classList.toggle('open') 105 | }) 106 | 107 | document.body.addEventListener('click', function (e) { 108 | if (e.target !== menuButton && !sidebar.contains(e.target)) { 109 | sidebar.classList.remove('open') 110 | } 111 | }) 112 | 113 | // Toggle sidebar on swipe 114 | var start = {}, end = {} 115 | 116 | document.body.addEventListener('touchstart', function (e) { 117 | start.x = e.changedTouches[0].clientX 118 | start.y = e.changedTouches[0].clientY 119 | }) 120 | 121 | document.body.addEventListener('touchend', function (e) { 122 | end.y = e.changedTouches[0].clientY 123 | end.x = e.changedTouches[0].clientX 124 | 125 | var xDiff = end.x - start.x 126 | var yDiff = end.y - start.y 127 | 128 | if (Math.abs(xDiff) > Math.abs(yDiff)) { 129 | if (xDiff > 0 && start.x <= 80) sidebar.classList.add('open') 130 | else sidebar.classList.remove('open') 131 | } 132 | }) 133 | } 134 | 135 | /** 136 | * Doc version select 137 | */ 138 | 139 | function initVersionSelect () { 140 | // version select 141 | var versionSelect = document.querySelector('.version-select') 142 | versionSelect && versionSelect.addEventListener('change', function (e) { 143 | var version = e.target.value 144 | var section = window.location.pathname.match(/\/v\d\/(\w+?)\//)[1] 145 | if (version === 'SELF') return 146 | window.location.assign( 147 | 'https://' + 148 | version + 149 | (version && '.') + 150 | 'vuejs.org/' + section + '/' 151 | ) 152 | }) 153 | } 154 | 155 | /** 156 | * Sub headers in sidebar 157 | */ 158 | 159 | function initSubHeaders () { 160 | var each = [].forEach 161 | var main = document.getElementById('main') 162 | var header = document.getElementById('header') 163 | var sidebar = document.querySelector('.sidebar') 164 | var content = document.querySelector('.content') 165 | 166 | // build sidebar 167 | var currentPageAnchor = sidebar.querySelector('.sidebar-link.current') 168 | var contentClasses = document.querySelector('.content').classList 169 | var isAPIOrStyleGuide = ( 170 | contentClasses.contains('api') || 171 | contentClasses.contains('style-guide') 172 | ) 173 | if (currentPageAnchor || isAPIOrStyleGuide) { 174 | var allHeaders = [] 175 | var sectionContainer 176 | if (isAPIOrStyleGuide) { 177 | sectionContainer = document.querySelector('.menu-root') 178 | } else { 179 | sectionContainer = document.createElement('ul') 180 | sectionContainer.className = 'menu-sub' 181 | currentPageAnchor.parentNode.appendChild(sectionContainer) 182 | } 183 | var headers = content.querySelectorAll('h2') 184 | if (headers.length) { 185 | each.call(headers, function (h) { 186 | sectionContainer.appendChild(makeLink(h)) 187 | var h3s = collectH3s(h) 188 | allHeaders.push(h) 189 | allHeaders.push.apply(allHeaders, h3s) 190 | if (h3s.length) { 191 | sectionContainer.appendChild(makeSubLinks(h3s, isAPIOrStyleGuide)) 192 | } 193 | }) 194 | } else { 195 | headers = content.querySelectorAll('h3') 196 | each.call(headers, function (h) { 197 | sectionContainer.appendChild(makeLink(h)) 198 | allHeaders.push(h) 199 | }) 200 | } 201 | 202 | var animating = false 203 | sectionContainer.addEventListener('click', function (e) { 204 | 205 | // Not prevent hashchange for smooth-scroll 206 | // e.preventDefault() 207 | 208 | if (e.target.classList.contains('section-link')) { 209 | sidebar.classList.remove('open') 210 | setActive(e.target) 211 | animating = true 212 | setTimeout(function () { 213 | animating = false 214 | }, 400) 215 | } 216 | }, true) 217 | 218 | // make links clickable 219 | allHeaders.forEach(makeHeaderClickable) 220 | 221 | smoothScroll.init({ 222 | speed: 400, 223 | offset: 0 224 | }) 225 | } 226 | 227 | var hoveredOverSidebar = false 228 | sidebar.addEventListener('mouseover', function () { 229 | hoveredOverSidebar = true 230 | }) 231 | sidebar.addEventListener('mouseleave', function () { 232 | hoveredOverSidebar = false 233 | }) 234 | 235 | // listen for scroll event to do positioning & highlights 236 | window.addEventListener('scroll', updateSidebar) 237 | window.addEventListener('resize', updateSidebar) 238 | 239 | function updateSidebar () { 240 | var doc = document.documentElement 241 | var top = doc && doc.scrollTop || document.body.scrollTop 242 | if (animating || !allHeaders) return 243 | var last 244 | for (var i = 0; i < allHeaders.length; i++) { 245 | var link = allHeaders[i] 246 | if (link.offsetTop > top) { 247 | if (!last) last = link 248 | break 249 | } else { 250 | last = link 251 | } 252 | } 253 | if (last) 254 | setActive(last.id, !hoveredOverSidebar) 255 | } 256 | 257 | function makeLink (h) { 258 | var link = document.createElement('li') 259 | window.arst = h 260 | var text = [].slice.call(h.childNodes).map(function (node) { 261 | if (node.nodeType === Node.TEXT_NODE) { 262 | return node.nodeValue 263 | } else if (['CODE', 'SPAN'].indexOf(node.tagName) !== -1) { 264 | return node.textContent 265 | } else { 266 | return '' 267 | } 268 | }).join('').replace(/\(.*\)$/, '') 269 | link.innerHTML = 270 | '' + 271 | htmlEscape(text) + 272 | '' 273 | return link 274 | } 275 | 276 | function htmlEscape (text) { 277 | return text 278 | .replace(/&/g, '&') 279 | .replace(/"/g, '"') 280 | .replace(/'/g, ''') 281 | .replace(//g, '>') 283 | } 284 | 285 | function collectH3s (h) { 286 | var h3s = [] 287 | var next = h.nextSibling 288 | while (next && next.tagName !== 'H2') { 289 | if (next.tagName === 'H3') { 290 | h3s.push(next) 291 | } 292 | next = next.nextSibling 293 | } 294 | return h3s 295 | } 296 | 297 | function makeSubLinks (h3s, small) { 298 | var container = document.createElement('ul') 299 | if (small) { 300 | container.className = 'menu-sub' 301 | } 302 | h3s.forEach(function (h) { 303 | container.appendChild(makeLink(h)) 304 | }) 305 | return container 306 | } 307 | 308 | function setActive (id, shouldScrollIntoView) { 309 | var previousActive = sidebar.querySelector('.section-link.active') 310 | var currentActive = typeof id === 'string' 311 | ? sidebar.querySelector('.section-link[href="#' + id + '"]') 312 | : id 313 | if (currentActive !== previousActive) { 314 | if (previousActive) previousActive.classList.remove('active') 315 | currentActive.classList.add('active') 316 | if (shouldScrollIntoView) { 317 | var currentPageOffset = currentPageAnchor 318 | ? currentPageAnchor.offsetTop - 8 319 | : 0 320 | var currentActiveOffset = currentActive.offsetTop + currentActive.parentNode.clientHeight 321 | var sidebarHeight = sidebar.clientHeight 322 | var currentActiveIsInView = ( 323 | currentActive.offsetTop >= sidebar.scrollTop && 324 | currentActiveOffset <= sidebar.scrollTop + sidebarHeight 325 | ) 326 | var linkNotFurtherThanSidebarHeight = currentActiveOffset - currentPageOffset < sidebarHeight 327 | var newScrollTop = currentActiveIsInView 328 | ? sidebar.scrollTop 329 | : linkNotFurtherThanSidebarHeight 330 | ? currentPageOffset 331 | : currentActiveOffset - sidebarHeight 332 | sidebar.scrollTop = newScrollTop 333 | } 334 | } 335 | } 336 | 337 | function makeHeaderClickable (link) { 338 | var wrapper = document.createElement('a') 339 | wrapper.href = '#' + link.id 340 | wrapper.setAttribute('data-scroll', '') 341 | link.parentNode.insertBefore(wrapper, link) 342 | wrapper.appendChild(link) 343 | } 344 | } 345 | })() 346 | -------------------------------------------------------------------------------- /themes/tech-docs/source/js/css.escape.js: -------------------------------------------------------------------------------- 1 | /*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */ 2 | ;(function(root, factory) { 3 | // https://github.com/umdjs/umd/blob/master/returnExports.js 4 | if (typeof exports == 'object') { 5 | // For Node.js. 6 | module.exports = factory(root); 7 | } else if (typeof define == 'function' && define.amd) { 8 | // For AMD. Register as an anonymous module. 9 | define([], factory.bind(root, root)); 10 | } else { 11 | // For browser globals (not exposing the function separately). 12 | factory(root); 13 | } 14 | }(typeof global != 'undefined' ? global : this, function(root) { 15 | 16 | if (root.CSS && root.CSS.escape) { 17 | return root.CSS.escape; 18 | } 19 | 20 | // https://drafts.csswg.org/cssom/#serialize-an-identifier 21 | var cssEscape = function(value) { 22 | if (arguments.length == 0) { 23 | throw new TypeError('`CSS.escape` requires an argument.'); 24 | } 25 | var string = String(value); 26 | var length = string.length; 27 | var index = -1; 28 | var codeUnit; 29 | var result = ''; 30 | var firstCodeUnit = string.charCodeAt(0); 31 | while (++index < length) { 32 | codeUnit = string.charCodeAt(index); 33 | // Note: there’s no need to special-case astral symbols, surrogate 34 | // pairs, or lone surrogates. 35 | 36 | // If the character is NULL (U+0000), then the REPLACEMENT CHARACTER 37 | // (U+FFFD). 38 | if (codeUnit == 0x0000) { 39 | result += '\uFFFD'; 40 | continue; 41 | } 42 | 43 | if ( 44 | // If the character is in the range [\1-\1F] (U+0001 to U+001F) or is 45 | // U+007F, […] 46 | (codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F || 47 | // If the character is the first character and is in the range [0-9] 48 | // (U+0030 to U+0039), […] 49 | (index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) || 50 | // If the character is the second character and is in the range [0-9] 51 | // (U+0030 to U+0039) and the first character is a `-` (U+002D), […] 52 | ( 53 | index == 1 && 54 | codeUnit >= 0x0030 && codeUnit <= 0x0039 && 55 | firstCodeUnit == 0x002D 56 | ) 57 | ) { 58 | // https://drafts.csswg.org/cssom/#escape-a-character-as-code-point 59 | result += '\\' + codeUnit.toString(16) + ' '; 60 | continue; 61 | } 62 | 63 | if ( 64 | // If the character is the first character and is a `-` (U+002D), and 65 | // there is no second character, […] 66 | index == 0 && 67 | length == 1 && 68 | codeUnit == 0x002D 69 | ) { 70 | result += '\\' + string.charAt(index); 71 | continue; 72 | } 73 | 74 | // If the character is not handled by one of the above rules and is 75 | // greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or 76 | // is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to 77 | // U+005A), or [a-z] (U+0061 to U+007A), […] 78 | if ( 79 | codeUnit >= 0x0080 || 80 | codeUnit == 0x002D || 81 | codeUnit == 0x005F || 82 | codeUnit >= 0x0030 && codeUnit <= 0x0039 || 83 | codeUnit >= 0x0041 && codeUnit <= 0x005A || 84 | codeUnit >= 0x0061 && codeUnit <= 0x007A 85 | ) { 86 | // the character itself 87 | result += string.charAt(index); 88 | continue; 89 | } 90 | 91 | // Otherwise, the escaped character. 92 | // https://drafts.csswg.org/cssom/#escape-a-character 93 | result += '\\' + string.charAt(index); 94 | 95 | } 96 | return result; 97 | }; 98 | 99 | if (!root.CSS) { 100 | root.CSS = {}; 101 | } 102 | 103 | root.CSS.escape = cssEscape; 104 | return cssEscape; 105 | 106 | })); 107 | -------------------------------------------------------------------------------- /themes/tech-docs/source/js/smooth-scroll.min.js: -------------------------------------------------------------------------------- 1 | /*! smooth-scroll v10.2.1 | (c) 2016 Chris Ferdinandi | MIT License | http://github.com/cferdinandi/smooth-scroll */ 2 | !(function(e,t){"function"==typeof define&&define.amd?define([],t(e)):"object"==typeof exports?module.exports=t(e):e.smoothScroll=t(e)})("undefined"!=typeof global?global:this.window||this.global,(function(e){"use strict";var t,n,o,r,a,c,l,i={},u="querySelector"in document&&"addEventListener"in e,s={selector:"[data-scroll]",selectorHeader:null,speed:500,easing:"easeInOutCubic",offset:0,callback:function(){}},d=function(){var e={},t=!1,n=0,o=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(t=arguments[0],n++);for(var r=function(n){for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(t&&"[object Object]"===Object.prototype.toString.call(n[o])?e[o]=d(!0,e[o],n[o]):e[o]=n[o])};n=0&&t.item(n)!==this;);return n>-1});e&&e!==document;e=e.parentNode)if(e.matches(t))return e;return null},m=function(e){"#"===e.charAt(0)&&(e=e.substr(1));for(var t,n=String(e),o=n.length,r=-1,a="",c=n.charCodeAt(0);++r=1&&t<=31||127==t||0===r&&t>=48&&t<=57||1===r&&t>=48&&t<=57&&45===c?"\\"+t.toString(16)+" ":t>=128||45===t||95===t||t>=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122?n.charAt(r):"\\"+n.charAt(r)}return"#"+a},p=function(e,t){var n;return"easeInQuad"===e&&(n=t*t),"easeOutQuad"===e&&(n=t*(2-t)),"easeInOutQuad"===e&&(n=t<.5?2*t*t:-1+(4-2*t)*t),"easeInCubic"===e&&(n=t*t*t),"easeOutCubic"===e&&(n=--t*t*t+1),"easeInOutCubic"===e&&(n=t<.5?4*t*t*t:(t-1)*(2*t-2)*(2*t-2)+1),"easeInQuart"===e&&(n=t*t*t*t),"easeOutQuart"===e&&(n=1- --t*t*t*t),"easeInOutQuart"===e&&(n=t<.5?8*t*t*t*t:1-8*--t*t*t*t),"easeInQuint"===e&&(n=t*t*t*t*t),"easeOutQuint"===e&&(n=1+--t*t*t*t*t),"easeInOutQuint"===e&&(n=t<.5?16*t*t*t*t*t:1+16*--t*t*t*t*t),n||t},g=function(e,t,n){var o=0;if(e.offsetParent)do o+=e.offsetTop,e=e.offsetParent;while(e);return o=Math.max(o-t-n,0),Math.min(o,v()-b())},b=function(){return Math.max(document.documentElement.clientHeight,e.innerHeight||0)},v=function(){return Math.max(document.body.scrollHeight,document.documentElement.scrollHeight,document.body.offsetHeight,document.documentElement.offsetHeight,document.body.clientHeight,document.documentElement.clientHeight)},y=function(e){return e&&"object"==typeof JSON&&"function"==typeof JSON.parse?JSON.parse(e):{}},O=function(e){return e?f(e)+e.offsetTop:0},S=function(t,n,o){o||(t.focus(),document.activeElement.id!==t.id&&(t.setAttribute("tabindex","-1"),t.focus(),t.style.outline="none"),e.scrollTo(0,n))};i.animateScroll=function(n,o,c){var i=y(o?o.getAttribute("data-options"):null),u=d(t||s,c||{},i),f="[object Number]"===Object.prototype.toString.call(n),h=f||!n.tagName?null:n;if(f||h){var m=e.pageYOffset;u.selectorHeader&&!r&&(r=document.querySelector(u.selectorHeader)),a||(a=O(r));var b,E,I=f?n:g(h,a,parseInt(u.offset,10)),H=I-m,A=v(),j=0,C=function(t,r,a){var c=e.pageYOffset;(t==r||c==r||e.innerHeight+c>=A)&&(clearInterval(a),S(n,r,f),u.callback(n,o))},M=function(){j+=16,b=j/parseInt(u.speed,10),b=b>1?1:b,E=m+H*p(u.easing,b),e.scrollTo(0,Math.floor(E)),C(E,I,l)},w=function(){clearInterval(l),l=setInterval(M,16)};0===e.pageYOffset&&e.scrollTo(0,0),w()}};var E=function(t){var r;try{r=m(decodeURIComponent(e.location.hash))}catch(t){r=m(e.location.hash)}n&&(n.id=n.getAttribute("data-scroll-id"),i.animateScroll(n,o),n=null,o=null)},I=function(r){if(0===r.button&&!r.metaKey&&!r.ctrlKey&&(o=h(r.target,t.selector),o&&"a"===o.tagName.toLowerCase()&&o.hostname===e.location.hostname&&o.pathname===e.location.pathname&&/#/.test(o.href))){var a;try{a=m(decodeURIComponent(o.hash))}catch(e){a=m(o.hash)}if("#"===a){r.preventDefault(),n=document.body;var c=n.id?n.id:"smooth-scroll-top";return n.setAttribute("data-scroll-id",c),n.id="",void(e.location.hash.substring(1)===c?E():e.location.hash=c)}n=document.querySelector(a),n&&(n.setAttribute("data-scroll-id",n.id),n.id="",o.hash===e.location.hash&&(r.preventDefault(),E()))}},H=function(e){c||(c=setTimeout((function(){c=null,a=O(r)}),66))};return i.destroy=function(){t&&(document.removeEventListener("click",I,!1),e.removeEventListener("resize",H,!1),t=null,n=null,o=null,r=null,a=null,c=null,l=null)},i.init=function(n){u&&(i.destroy(),t=d(s,n||{}),r=t.selectorHeader?document.querySelector(t.selectorHeader):null,a=O(r),document.addEventListener("click",I,!1),e.addEventListener("hashchange",E,!1),r&&e.addEventListener("resize",H,!1))},i})); -------------------------------------------------------------------------------- /themes/tech-docs/standards/hook-api-args-dot-notation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/standards/hook-api-args-dot-notation.jpg -------------------------------------------------------------------------------- /themes/tech-docs/standards/hook-api-args-dot-notation.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | {% hooksApiFieldNames setNow "The fields that you want to add or set to the current date-time." %} 4 | */ 5 | 6 | hexo.extend.tag.register('hooksApiFieldNames', ([name, desc, fieldNames = 'fieldNames', type = 'dot notation']) => { 7 | // console.log('hooksApiFieldNames', name, desc, fieldNames, type); 8 | const hook = hooks[name]; 9 | if (!hook) return `?????????? hook ${name} not defined.`; 10 | 11 | return ` 12 |
    • 13 | Arguments 14 |
      • 15 | {Array < String >} ${fieldNames} 16 |
      17 |
    18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
    NameTypeDescription
    ${fieldNames}${type}${desc}
    `; 35 | }) 36 | -------------------------------------------------------------------------------- /themes/tech-docs/standards/hook-api-args-value-return.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/standards/hook-api-args-value-return.jpg -------------------------------------------------------------------------------- /themes/tech-docs/standards/hook-api-args-value-return.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | - **Arguments** 5 | - `{Array< String >} transports` 6 | 7 | Name | Type | Default | Description 8 | ---|:---:|---|--- 9 | `transports` | `Array< String >` | | The transports you want to allow. 10 | 11 | `transports` | Value | Description 12 | ---|:---:|--- 13 | | `socketio` | Allow calls by Socket.IO transport. 14 | | `primus` | Allow calls by Primus transport. 15 | | `rest` | Allow calls by REST transport. 16 | | `external` | Allow calls other than from server. 17 | | `server` | Allow calls from server. 18 | 19 | {% hooksApiReturns isProvider "If the call was made by one of the transports." %} 20 | 21 | ===or=== 22 | 23 | {% apiReturns isProvider "If the call was made by one of the transports." %} 24 | */ 25 | 26 | hexo.extend.tag.register('hooksApiReturns', ([name, desc, result = 'result', type = 'Boolean']) => { 27 | //console.log('hooksApiReturns', name, desc, result, type); 28 | 29 | // handle a bug 30 | if (desc.substr(-1) === ',') desc = desc.substr(0, desc.length - 1); 31 | if (result.substr(-1) === ',') result = result.substr(0, result.length - 1); 32 | if (type.substr(-1) === ',') type = type.substr(0, type.length - 1); 33 | 34 | const hook = hooks[name]; 35 | if (!hook) return `?????????? hook ${name} not defined.`; 36 | 37 | return ` 38 |
    • 39 | Returns 40 |
      • 41 | {${type}} ${result} 42 |
      43 |
    44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 |
    NameTypeDescription
    ${result}${type}${desc}
    `; 61 | }); 62 | -------------------------------------------------------------------------------- /themes/tech-docs/standards/hook-api-args-with-object.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feathers-plus/docs/2bc09204749c6b8180830be986a8cf09eda7e3c1/themes/tech-docs/standards/hook-api-args-with-object.jpg -------------------------------------------------------------------------------- /themes/tech-docs/standards/hook-api-args-with-object.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | 4 | - **Arguments** 5 | - `{Object | Function} schema` 6 | - `{Array< String> | String} only` 7 | - `{Array< String> | String} exclude` 8 | - `[fieldName]: {Object} schema` 9 | - `{Object} computed` 10 | - `[fieldName]: {Function} computeFunc` 11 | 12 | Argument | Type | Default | Description 13 | ---|:---:|---|--- 14 | `schema` | `Object` `Function` | `context` `=> schema` | How to serialize the items. 15 | 16 | `schema` | Argument | Type | Default | Description 17 | ---|---|:---:|---|--- 18 | | `only` | `Array<` `String>` or `String` | | The names of the fields to keep in each item. The names for included sets of items plus `_include` and `_elapsed` are not removed by `only`. 19 | | `exclude` | `Array<` `String>` or `String` | | The names of fields to drop in each item. You may drop, at your own risk, names of included sets of items, `_include` and `_elapsed`. 20 | | `computed` | `Object` | | The new names you want added and how to compute their values. 21 | 22 | `schema` `.computed` | Argument | Type | Default | Description 23 | ---|---|:---:|---|--- 24 | | `fieldName` | `String` | | The name of the field to add to the records. 25 | | `computeFunnc` | `Function` | `(record,` `context)` `=> value` | Function to calculate the computed value. `item`: The item with all its initial values, plus all of its included items. The function can still reference values which will be later removed by only and exclude. `context`: The context passed to `serialize`. 26 | 27 | */ 28 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | const path = require('path'); 3 | const webpack = require('webpack'); 4 | // const BabiliPlugin = require('babili-webpack-plugin'); 5 | 6 | module.exports = { 7 | entry: './client-bundle/index.js', 8 | output: { 9 | path: path.resolve(__dirname, 'themes', 'tech-docs', 'source', 'js'), 10 | filename: 'client-bundle.js' 11 | }, 12 | /* 13 | plugins: [ 14 | new BabiliPlugin(), 15 | ], 16 | */ 17 | module: { 18 | loaders: [ 19 | { 20 | test: /\.js$/, 21 | loader: 'babel-loader', 22 | query: { 23 | presets: ['es2015'] //, 'babili'] 24 | } 25 | } 26 | ] 27 | }, 28 | stats: { 29 | colors: true 30 | } 31 | }; 32 | --------------------------------------------------------------------------------