mousetrap-rails 0.0.12 → 1.4.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d7c1c1518dc3723ad152da8bdbeb6731a807f5e7
4
- data.tar.gz: b109201e9f8dab37a40a1eb2d05bc140625d1e4d
3
+ metadata.gz: 504385d122d04d11a10a3f30d00ae2c185f694df
4
+ data.tar.gz: c4bcc42500168cc81ffa7eca59dd3f96dbd95265
5
5
  SHA512:
6
- metadata.gz: d87bdda376611d3a0c9a0cc48affa09fd1f41e94fbfba42c2643f322a5274177d49aa7053e09b50ce7ab0e39679ea9d1517429ec799252e5e6bc07da7dc71149
7
- data.tar.gz: 0a41c7856d6e1c9d280f36dfd8f80cd9e18cb82e8c8e3d08a2d4a516a1ad6cbb8d4e528ffac3dfc136681c87bfc3e1e7bbd3bd3b9b3756e4e8f37063c9978c22
6
+ metadata.gz: 1fa0dcfe2a6c3c80ccb1d25ee2ba3558d390633101eb8d6872da0853332c8f3143a68ca6c7fe8c8f91e6ec71b86137b679fabee80f3827dcaba623b9f31f15a7
7
+ data.tar.gz: 7819598758d59a895f34f28e470e5cbef65ee4c13418a15a12c0a257bb2f9fa93dbe712d3600910834c6155bdf2dac7d88393c86d903e667040bd46b59895085
@@ -3,4 +3,8 @@ rvm:
3
3
  - 1.9.2
4
4
  - 1.9.3
5
5
  - 2.0.0
6
- - rbx-19mode
6
+ - rbx-2.0.0
7
+ - rbx-2.1.0
8
+ - rbx-2.1.1
9
+ - rbx-2.2.0
10
+ - rbx-2.2.1
@@ -1,6 +1,10 @@
1
1
  ## v0.0.13.wip
2
2
 
3
- * [] Add moustrap extensions generator
3
+ * update mousetrap.js to 1.4.6
4
+ * update readme
5
+ * add rubinius versions in travis-ci config
6
+ * add mousetrap/plugins
7
+ * add mousetrap:update:plugins raketask
4
8
 
5
9
  ## v0.0.12
6
10
 
data/README.md CHANGED
@@ -5,7 +5,6 @@
5
5
 
6
6
  The `mousetrap-rails` gem integrates Mousetrap javascript library with Rails asset pipeline.
7
7
 
8
-
9
8
  ## Installation
10
9
 
11
10
  ### Add mousetrap-rails gem to app
@@ -37,6 +36,18 @@ It will create a sample `keybindings.js.coffee` file in `app/assets/javascripts`
37
36
 
38
37
  Voila!
39
38
 
39
+ Also you can use mousetrap plugins. Require them in your `application.js` file
40
+
41
+ ```javascript
42
+ //= require mousetrap/plugins # To require all plugins
43
+ //= require mousetrap/dictionary # To require dictionary plugin
44
+ //= require mousetrap/global # To require global plugin
45
+ //= require mousetrap/pause # To require pause plugin
46
+ //= require mousetrap/record # To require record plugin
47
+ ```
48
+
49
+ See plugin descriptions below.
50
+
40
51
  ### Latest (may be unstable) version
41
52
 
42
53
  Instead of `gem 'mousetrap-rails'` add to your Gemfile
@@ -45,6 +56,8 @@ Instead of `gem 'mousetrap-rails'` add to your Gemfile
45
56
  gem 'mousetrap-rails', github: 'kugaevsky/mousetrap-rails'
46
57
  ```
47
58
 
59
+ `Mousetrap-rails` versioning use `mousetrap.js` library version number.
60
+
48
61
  ## Usage
49
62
 
50
63
  ### Via data-attributes
@@ -59,7 +72,7 @@ You can add keyboard navigation to your links by using `data-keybinding` attribu
59
72
  You can jump to an input
60
73
 
61
74
  ```haml
62
- = text_field_tag 'Username', nil, data: { keybinding: 'u' } # Press 'u' to focus username input field
75
+ = text_field_tag 'Username', nil, data: { keybinding: 'u' } # Press 'u' to focus username input field
63
76
  ```
64
77
 
65
78
  ### Via javascript
@@ -67,7 +80,7 @@ You can jump to an input
67
80
  Any javascript function can be called with mousetrap
68
81
 
69
82
  ```coffeescript
70
- Mousetrap.bind 'f', (e) -> alert 'My perfect function called' # Press 'f' to popup alert
83
+ Mousetrap.bind 'f', (e) -> alert 'My perfect function called' # Press 'f' to popup alert
71
84
  ```
72
85
 
73
86
  ### More examples (from official guide)
@@ -100,18 +113,84 @@ You can find full documentation on [Mousetrap library page](http://craig.is/kill
100
113
 
101
114
  You can display key binding hints near links with `data-keybinding` attribute by pressing `Alt+Shift+h`. Now it's just experimental feature for debugging purposes only.
102
115
 
103
- ## TODO
116
+ ## Plugins
117
+
118
+ ### Global Bindings
119
+
120
+ //= require mousetrap/global # ---> application.js
121
+
122
+ This extension allows you to specify keyboard events that will work anywhere including inside textarea/input fields.
123
+
124
+ ```coffeescript
125
+ Mousetrap.bindGlobal 'ctrl+s', -> _save()
126
+ ```
127
+
128
+ This means that a keyboard event bound using `Mousetrap.bind` will only work outside of form input fields, but using `Moustrap.bindGlobal` will work in both places.
129
+
130
+
131
+ ### Bind dictionary
132
+
133
+ //= require mousetrap/dictionary # ---> application.js
134
+
135
+ This extension overwrites the default bind behavior and allows you to bind multiple combinations in a single bind call.
136
+
137
+ Usage looks like:
138
+
139
+ ```coffeescript
140
+ Mousetrap.bind
141
+ 'a': -> console.log('a')
142
+ 'b': -> console.log('b')
143
+ ```
144
+
145
+ You can optionally pass in `keypress`, `keydown` or `keyup` as a second argument.
146
+
147
+ Other bind calls work the same way as they do by default.
148
+
149
+
150
+ ### Pause/unpause
151
+
152
+ //= require mousetrap/pause # ---> application.js
153
+
154
+ This extension allows Mousetrap to be paused and unpaused without having to reset keyboard shortcuts and rebind them.
155
+
156
+ ```coffeescript
157
+ # stop Mousetrap events from firing
158
+ Mousetrap.pause()
159
+
160
+ # allow Mousetrap events to fire again
161
+ Mousetrap.unpause()
162
+ ```
163
+
164
+
165
+ ### Record
166
+
167
+ //= require mousetrap/record # ---> application.js
168
+
169
+ This extension lets you use Mousetrap to record keyboard sequences and play them back:
170
+
171
+ ```slim
172
+ button onclick="recordSequence()" Record
173
+ ```
174
+
175
+ ```coffeescript
176
+ recordSequence = () ->
177
+ Mousetrap.record (sequence) ->
178
+ # sequence is an array like ['ctrl+k', 'c']
179
+ alert('You pressed: ' + sequence.join(' '))
180
+ ````
104
181
 
105
- - [ ] Add moustrap extensions generator
182
+ [More detailed plugins description](http://craig.is/killing/mice#extensions)
106
183
 
107
184
  ## Contributing
108
185
 
109
186
  Please submit all pull requests against latest `*.wip` branch. If your pull request contains new features, you **must** include relevant tests.
110
187
 
111
- You can easily update mousetrap.js library via rake task.
188
+ You can easily update mousetrap.js library via rake tasks.
112
189
 
113
190
  ```bash
114
- $ rake mousetrap:update
191
+ $ rake mousetrap:update # Update main mousetrap javascript lib and its plugins
192
+ $ rake mousetrap:update:main # Update main mousetrap javascript lib
193
+ $ rake mousetrap:update:plugins # Update mousetrap javascript lib plugins
115
194
  ```
116
195
 
117
196
  Thanks in advance!
data/Rakefile CHANGED
@@ -4,14 +4,43 @@ require 'rspec/core/rake_task'
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
  task :default => :spec
6
6
 
7
+ ORIGIN_URL = "https://raw.github.com/ccampbell/mousetrap/master"
8
+ BASE_FILE_PATH = "vendor/assets/javascripts"
9
+
7
10
  namespace :mousetrap do
8
- desc "Update Mousetrap javascript library from https://raw.github.com/ccampbell/mousetrap/master/mousetrap.js"
11
+ desc "Update main mousetrap javascript lib and its plugins"
9
12
  task :update do
10
- ORIGIN_URL = "https://raw.github.com/ccampbell/mousetrap/master/mousetrap.js"
11
- FILE_PATH = "vendor/assets/javascripts/mousetrap.js"
12
- puts "Updating Mousetrap.js library from origin"
13
- puts "#{ORIGIN_URL} -> #{FILE_PATH}\n\n"
14
- system 'wget', ORIGIN_URL, "-O", FILE_PATH
15
- puts "Mousetrap.js updated!"
13
+ %w(main plugins).each do |task|
14
+ Rake::Task["mousetrap:update:#{task}"].invoke
15
+ end
16
+ end
17
+
18
+ namespace :update do
19
+ desc "Update main mousetrap javascript lib"
20
+ task :main do
21
+ origin_url = "#{ORIGIN_URL}/mousetrap.js"
22
+ file_path = "#{BASE_FILE_PATH}/mousetrap.js"
23
+ download origin_url, file_path
24
+ puts "\033[32m-> Main mousetrap lib updated!\033[0m\n\n"
25
+ end
26
+
27
+ desc "Update mousetrap javascript lib plugins"
28
+ task :plugins do
29
+ plugins = { dictionary: 'plugins/bind-dictionary/mousetrap-bind-dictionary.js',
30
+ global: 'plugins/global-bind/mousetrap-global-bind.js',
31
+ pause: 'plugins/pause/mousetrap-pause.js',
32
+ record: 'plugins/record/mousetrap-record.js'
33
+ }
34
+ plugins.each_pair do |name, file|
35
+ origin_url = "#{ORIGIN_URL}/#{file}"
36
+ file_path = "#{BASE_FILE_PATH}/mousetrap/#{name}.js"
37
+ download origin_url, file_path
38
+ puts "\033[32m-> #{name} mousetrap plugin updated!\033[0m\n\n"
39
+ end
40
+ end
16
41
  end
17
42
  end
43
+
44
+ def download(source, dest)
45
+ system 'wget', source, "-O", dest
46
+ end
@@ -1,5 +1,5 @@
1
1
  module Mousetrap
2
2
  module Rails
3
- VERSION = "0.0.12"
3
+ VERSION = "1.4.6"
4
4
  end
5
5
  end
@@ -17,7 +17,7 @@
17
17
  * Mousetrap is a simple keyboard shortcut library for Javascript with
18
18
  * no external dependencies
19
19
  *
20
- * @version 1.4.5
20
+ * @version 1.4.6
21
21
  * @url craig.is/killing/mice
22
22
  */
23
23
  (function(window, document, undefined) {
@@ -402,6 +402,36 @@
402
402
  return modifiers;
403
403
  }
404
404
 
405
+ /**
406
+ * prevents default for this event
407
+ *
408
+ * @param {Event} e
409
+ * @returns void
410
+ */
411
+ function _preventDefault(e) {
412
+ if (e.preventDefault) {
413
+ e.preventDefault();
414
+ return;
415
+ }
416
+
417
+ e.returnValue = false;
418
+ }
419
+
420
+ /**
421
+ * stops propogation for this event
422
+ *
423
+ * @param {Event} e
424
+ * @returns void
425
+ */
426
+ function _stopPropagation(e) {
427
+ if (e.stopPropagation) {
428
+ e.stopPropagation();
429
+ return;
430
+ }
431
+
432
+ e.cancelBubble = true;
433
+ }
434
+
405
435
  /**
406
436
  * actually calls the callback function
407
437
  *
@@ -412,24 +442,16 @@
412
442
  * @param {Event} e
413
443
  * @returns void
414
444
  */
415
- function _fireCallback(callback, e, combo) {
445
+ function _fireCallback(callback, e, combo, sequence) {
416
446
 
417
447
  // if this event should not happen stop here
418
- if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo)) {
448
+ if (Mousetrap.stopCallback(e, e.target || e.srcElement, combo, sequence)) {
419
449
  return;
420
450
  }
421
451
 
422
452
  if (callback(e, combo) === false) {
423
- if (e.preventDefault) {
424
- e.preventDefault();
425
- }
426
-
427
- if (e.stopPropagation) {
428
- e.stopPropagation();
429
- }
430
-
431
- e.returnValue = false;
432
- e.cancelBubble = true;
453
+ _preventDefault(e);
454
+ _stopPropagation(e);
433
455
  }
434
456
  }
435
457
 
@@ -481,7 +503,7 @@
481
503
 
482
504
  // keep a list of which sequences were matches for later
483
505
  doNotReset[callbacks[i].seq] = 1;
484
- _fireCallback(callbacks[i].callback, e, callbacks[i].combo);
506
+ _fireCallback(callbacks[i].callback, e, callbacks[i].combo, callbacks[i].seq);
485
507
  continue;
486
508
  }
487
509
 
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Overwrites default Mousetrap.bind method to optionally accept
3
+ * an object to bind multiple key events in a single call
4
+ *
5
+ * You can pass it in like:
6
+ *
7
+ * Mousetrap.bind({
8
+ * 'a': function() { console.log('a'); },
9
+ * 'b': function() { console.log('b'); }
10
+ * });
11
+ *
12
+ * And can optionally pass in 'keypress', 'keydown', or 'keyup'
13
+ * as a second argument
14
+ *
15
+ */
16
+ /* global Mousetrap:true */
17
+ Mousetrap = (function(Mousetrap) {
18
+ var self = Mousetrap,
19
+ _oldBind = self.bind,
20
+ args;
21
+
22
+ self.bind = function() {
23
+ args = arguments;
24
+
25
+ // normal call
26
+ if (typeof args[0] == 'string' || args[0] instanceof Array) {
27
+ return _oldBind(args[0], args[1], args[2]);
28
+ }
29
+
30
+ // object passed in
31
+ for (var key in args[0]) {
32
+ if (args[0].hasOwnProperty(key)) {
33
+ _oldBind(key, args[0][key], args[1]);
34
+ }
35
+ }
36
+ };
37
+
38
+ return self;
39
+ }) (Mousetrap);
@@ -0,0 +1,36 @@
1
+ /**
2
+ * adds a bindGlobal method to Mousetrap that allows you to
3
+ * bind specific keyboard shortcuts that will still work
4
+ * inside a text input field
5
+ *
6
+ * usage:
7
+ * Mousetrap.bindGlobal('ctrl+s', _saveChanges);
8
+ */
9
+ /* global Mousetrap:true */
10
+ Mousetrap = (function(Mousetrap) {
11
+ var _globalCallbacks = {},
12
+ _originalStopCallback = Mousetrap.stopCallback;
13
+
14
+ Mousetrap.stopCallback = function(e, element, combo, sequence) {
15
+ if (_globalCallbacks[combo] || _globalCallbacks[sequence]) {
16
+ return false;
17
+ }
18
+
19
+ return _originalStopCallback(e, element, combo);
20
+ };
21
+
22
+ Mousetrap.bindGlobal = function(keys, callback, action) {
23
+ Mousetrap.bind(keys, callback, action);
24
+
25
+ if (keys instanceof Array) {
26
+ for (var i = 0; i < keys.length; i++) {
27
+ _globalCallbacks[keys[i]] = true;
28
+ }
29
+ return;
30
+ }
31
+
32
+ _globalCallbacks[keys] = true;
33
+ };
34
+
35
+ return Mousetrap;
36
+ }) (Mousetrap);
@@ -0,0 +1,29 @@
1
+ /**
2
+ * adds a pause and unpause method to Mousetrap
3
+ * this allows you to enable or disable keyboard shortcuts
4
+ * without having to reset Mousetrap and rebind everything
5
+ */
6
+ /* global Mousetrap:true */
7
+ Mousetrap = (function(Mousetrap) {
8
+ var self = Mousetrap,
9
+ _originalStopCallback = self.stopCallback,
10
+ enabled = true;
11
+
12
+ self.stopCallback = function(e, element, combo) {
13
+ if (!enabled) {
14
+ return true;
15
+ }
16
+
17
+ return _originalStopCallback(e, element, combo);
18
+ };
19
+
20
+ self.pause = function() {
21
+ enabled = false;
22
+ };
23
+
24
+ self.unpause = function() {
25
+ enabled = true;
26
+ };
27
+
28
+ return self;
29
+ }) (Mousetrap);
@@ -0,0 +1,4 @@
1
+ //= require mousetrap/dictionary
2
+ //= require mousetrap/global
3
+ //= require mousetrap/pause
4
+ //= require mousetrap/record
@@ -0,0 +1,189 @@
1
+ /**
2
+ * This extension allows you to record a sequence using Mousetrap.
3
+ *
4
+ * @author Dan Tao <daniel.tao@gmail.com>
5
+ */
6
+ (function(Mousetrap) {
7
+ /**
8
+ * the sequence currently being recorded
9
+ *
10
+ * @type {Array}
11
+ */
12
+ var _recordedSequence = [],
13
+
14
+ /**
15
+ * a callback to invoke after recording a sequence
16
+ *
17
+ * @type {Function|null}
18
+ */
19
+ _recordedSequenceCallback = null,
20
+
21
+ /**
22
+ * a list of all of the keys currently held down
23
+ *
24
+ * @type {Array}
25
+ */
26
+ _currentRecordedKeys = [],
27
+
28
+ /**
29
+ * temporary state where we remember if we've already captured a
30
+ * character key in the current combo
31
+ *
32
+ * @type {boolean}
33
+ */
34
+ _recordedCharacterKey = false,
35
+
36
+ /**
37
+ * a handle for the timer of the current recording
38
+ *
39
+ * @type {null|number}
40
+ */
41
+ _recordTimer = null,
42
+
43
+ /**
44
+ * the original handleKey method to override when Mousetrap.record() is
45
+ * called
46
+ *
47
+ * @type {Function}
48
+ */
49
+ _origHandleKey = Mousetrap.handleKey;
50
+
51
+ /**
52
+ * handles a character key event
53
+ *
54
+ * @param {string} character
55
+ * @param {Array} modifiers
56
+ * @param {Event} e
57
+ * @returns void
58
+ */
59
+ function _handleKey(character, modifiers, e) {
60
+ // remember this character if we're currently recording a sequence
61
+ if (e.type == 'keydown') {
62
+ if (character.length === 1 && _recordedCharacterKey) {
63
+ _recordCurrentCombo();
64
+ }
65
+
66
+ for (i = 0; i < modifiers.length; ++i) {
67
+ _recordKey(modifiers[i]);
68
+ }
69
+ _recordKey(character);
70
+
71
+ // once a key is released, all keys that were held down at the time
72
+ // count as a keypress
73
+ } else if (e.type == 'keyup' && _currentRecordedKeys.length > 0) {
74
+ _recordCurrentCombo();
75
+ }
76
+ }
77
+
78
+ /**
79
+ * marks a character key as held down while recording a sequence
80
+ *
81
+ * @param {string} key
82
+ * @returns void
83
+ */
84
+ function _recordKey(key) {
85
+ var i;
86
+
87
+ // one-off implementation of Array.indexOf, since IE6-9 don't support it
88
+ for (i = 0; i < _currentRecordedKeys.length; ++i) {
89
+ if (_currentRecordedKeys[i] === key) {
90
+ return;
91
+ }
92
+ }
93
+
94
+ _currentRecordedKeys.push(key);
95
+
96
+ if (key.length === 1) {
97
+ _recordedCharacterKey = true;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * marks whatever key combination that's been recorded so far as finished
103
+ * and gets ready for the next combo
104
+ *
105
+ * @returns void
106
+ */
107
+ function _recordCurrentCombo() {
108
+ _recordedSequence.push(_currentRecordedKeys);
109
+ _currentRecordedKeys = [];
110
+ _recordedCharacterKey = false;
111
+ _restartRecordTimer();
112
+ }
113
+
114
+ /**
115
+ * ensures each combo in a sequence is in a predictable order and formats
116
+ * key combos to be '+'-delimited
117
+ *
118
+ * modifies the sequence in-place
119
+ *
120
+ * @param {Array} sequence
121
+ * @returns void
122
+ */
123
+ function _normalizeSequence(sequence) {
124
+ var i;
125
+
126
+ for (i = 0; i < sequence.length; ++i) {
127
+ sequence[i].sort(function(x, y) {
128
+ // modifier keys always come first, in alphabetical order
129
+ if (x.length > 1 && y.length === 1) {
130
+ return -1;
131
+ } else if (x.length === 1 && y.length > 1) {
132
+ return 1;
133
+ }
134
+
135
+ // character keys come next (list should contain no duplicates,
136
+ // so no need for equality check)
137
+ return x > y ? 1 : -1;
138
+ });
139
+
140
+ sequence[i] = sequence[i].join('+');
141
+ }
142
+ }
143
+
144
+ /**
145
+ * finishes the current recording, passes the recorded sequence to the stored
146
+ * callback, and sets Mousetrap.handleKey back to its original function
147
+ *
148
+ * @returns void
149
+ */
150
+ function _finishRecording() {
151
+ if (_recordedSequenceCallback) {
152
+ _normalizeSequence(_recordedSequence);
153
+ _recordedSequenceCallback(_recordedSequence);
154
+ }
155
+
156
+ // reset all recorded state
157
+ _recordedSequence = [];
158
+ _recordedSequenceCallback = null;
159
+ _currentRecordedKeys = [];
160
+
161
+ Mousetrap.handleKey = _origHandleKey;
162
+ }
163
+
164
+ /**
165
+ * called to set a 1 second timeout on the current recording
166
+ *
167
+ * this is so after each key press in the sequence the recording will wait for
168
+ * 1 more second before executing the callback
169
+ *
170
+ * @returns void
171
+ */
172
+ function _restartRecordTimer() {
173
+ clearTimeout(_recordTimer);
174
+ _recordTimer = setTimeout(_finishRecording, 1000);
175
+ }
176
+
177
+ /**
178
+ * records the next sequence and passes it to a callback once it's
179
+ * completed
180
+ *
181
+ * @param {Function} callback
182
+ * @returns void
183
+ */
184
+ Mousetrap.record = function(callback) {
185
+ Mousetrap.handleKey = _handleKey;
186
+ _recordedSequenceCallback = callback;
187
+ };
188
+
189
+ })(Mousetrap);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mousetrap-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.12
4
+ version: 1.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Kugaevsky
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain:
11
11
  - gem-public_cert.pem
12
- date: 2013-09-15 00:00:00.000000000 Z
12
+ date: 2013-11-25 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -161,6 +161,11 @@ files:
161
161
  - spec/generators/install_generator_spec.rb
162
162
  - spec/spec_helper.rb
163
163
  - vendor/assets/javascripts/mousetrap.js
164
+ - vendor/assets/javascripts/mousetrap/dictionary.js
165
+ - vendor/assets/javascripts/mousetrap/global.js
166
+ - vendor/assets/javascripts/mousetrap/pause.js
167
+ - vendor/assets/javascripts/mousetrap/plugins.js
168
+ - vendor/assets/javascripts/mousetrap/record.js
164
169
  homepage: http://kugaevsky.github.com/mousetrap-rails
165
170
  licenses:
166
171
  - MIT
@@ -184,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
184
189
  version: '0'
185
190
  requirements: []
186
191
  rubyforge_project:
187
- rubygems_version: 2.0.3
192
+ rubygems_version: 2.1.11
188
193
  signing_key:
189
194
  specification_version: 4
190
195
  summary: Integrate Mousetrap javascript library with Rails Asset Pipeline