├── .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 |
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 |
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 |
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 |
2 | Extensions
3 |
4 |
5 |
9 |
10 | Batch Loader
11 |
12 |
16 |
17 | Offline First
18 |
19 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/themes/tech-docs/layout/partials/dropdown_frameworks.ejs:
--------------------------------------------------------------------------------
1 |
2 | Frameworks
3 |
10 |
11 |
--------------------------------------------------------------------------------
/themes/tech-docs/layout/partials/header.ejs:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/themes/tech-docs/layout/partials/main_menu.ejs:
--------------------------------------------------------------------------------
1 |
2 |
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 |
2 |
11 |
12 |
--------------------------------------------------------------------------------
/themes/tech-docs/layout/partials/z-conf.ejs:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/themes/tech-docs/layout/partials/z-ecosystem_dropdown.ejs:
--------------------------------------------------------------------------------
1 |
2 | Ecosystem
3 |
4 | Help
5 |
9 | Tooling
10 |
11 |
16 |
17 | Core Libraries
18 |
23 | News
24 |
30 | Resource Lists
31 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/themes/tech-docs/layout/partials/z-language_dropdown.ejs:
--------------------------------------------------------------------------------
1 |
2 | Translations
3 |
11 |
12 |
--------------------------------------------------------------------------------
/themes/tech-docs/layout/partials/z-learn_dropdown.ejs:
--------------------------------------------------------------------------------
1 |
2 | Learn
3 |
11 |
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 |
60 |
61 |
68 |
69 |
79 |
--------------------------------------------------------------------------------
/themes/tech-docs/layout/partials/z-support_vue_dropdown.ejs:
--------------------------------------------------------------------------------
1 |
2 | Support Vue
3 |
10 |
11 |
--------------------------------------------------------------------------------
/themes/tech-docs/layout/post.ejs:
--------------------------------------------------------------------------------
1 |
23 |
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 | Name
22 | Type
23 | Description
24 |
25 |
26 |
27 |
28 | ${result}
29 | ${type}
30 | ${desc}
31 |
32 |
33 |
`;
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 | guide
224 | details
225 | tags
226 |
227 |
228 |
229 |
230 | ${hook.guide}
231 | source
232 | ${hook.tags}
233 |
234 |
235 |
`;
236 | }
237 |
238 | let html = `${hook.desc}
239 |
240 |
241 |
242 | before
243 | after
244 | methods
245 | multi recs
246 | guide
247 | details
248 | tags
249 |
250 |
251 |
252 |
253 | ${hook.before1}
254 | ${hook.after1}
255 | ${hook.methods1}
256 | ${hook.multi}
257 | ${hook.guide}
258 | source
259 | ${hook.tags}
260 | `;
261 |
262 | if (hook.before2 || hook.after2 || hook.methods2) {
263 | html += `
264 |
265 | ${hook.before2}
266 | ${hook.after2}
267 | ${hook.methods2}
268 | `;
269 | }
270 |
271 | html += `
272 |
273 |
`;
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 | Name
295 | Type
296 | Description
297 |
298 |
299 |
300 |
301 | ${fieldNames}
302 | ${type}
303 | ${desc}
304 |
305 |
306 |
`;
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 | Name
332 | Type
333 | Description
334 |
335 |
336 |
337 |
338 | ${result}
339 | ${type}
340 | ${desc}
341 |
342 |
343 |
`;
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 | Name
23 | Type
24 | Description
25 |
26 |
27 |
28 |
29 | ${fieldNames}
30 | ${type}
31 | ${desc}
32 |
33 |
34 |
`;
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 | Name
49 | Type
50 | Description
51 |
52 |
53 |
54 |
55 | ${result}
56 | ${type}
57 | ${desc}
58 |
59 |
60 |
`;
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 |
--------------------------------------------------------------------------------