blob: 5a632605a4d8a41543677435d6588e670084ddc2 [file] [log] [blame]
Marc Kupietz9c036a42024-05-14 13:17:25 +02001import {
2 SLIDE_NUMBER_FORMAT_CURRENT,
3 SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL
4} from "../utils/constants";
5
Marc Kupietz09b75752023-10-07 09:32:19 +02006/**
7 * Makes it possible to jump to a slide by entering its
8 * slide number or id.
9 */
10export default class JumpToSlide {
11
12 constructor( Reveal ) {
13
14 this.Reveal = Reveal;
15
16 this.onInput = this.onInput.bind( this );
17 this.onBlur = this.onBlur.bind( this );
18 this.onKeyDown = this.onKeyDown.bind( this );
19
20 }
21
22 render() {
23
24 this.element = document.createElement( 'div' );
25 this.element.className = 'jump-to-slide';
26
27 this.jumpInput = document.createElement( 'input' );
28 this.jumpInput.type = 'text';
29 this.jumpInput.className = 'jump-to-slide-input';
30 this.jumpInput.placeholder = 'Jump to slide';
31 this.jumpInput.addEventListener( 'input', this.onInput );
32 this.jumpInput.addEventListener( 'keydown', this.onKeyDown );
33 this.jumpInput.addEventListener( 'blur', this.onBlur );
34
35 this.element.appendChild( this.jumpInput );
36
37 }
38
39 show() {
40
41 this.indicesOnShow = this.Reveal.getIndices();
42
43 this.Reveal.getRevealElement().appendChild( this.element );
44 this.jumpInput.focus();
45
46 }
47
48 hide() {
49
50 if( this.isVisible() ) {
51 this.element.remove();
52 this.jumpInput.value = '';
53
54 clearTimeout( this.jumpTimeout );
55 delete this.jumpTimeout;
56 }
57
58 }
59
60 isVisible() {
61
62 return !!this.element.parentNode;
63
64 }
65
66 /**
67 * Parses the current input and jumps to the given slide.
68 */
69 jump() {
70
71 clearTimeout( this.jumpTimeout );
72 delete this.jumpTimeout;
73
Marc Kupietz9c036a42024-05-14 13:17:25 +020074 let query = this.jumpInput.value.trim( '' );
75 let indices;
Marc Kupietz09b75752023-10-07 09:32:19 +020076
Marc Kupietz9c036a42024-05-14 13:17:25 +020077 // When slide numbers are formatted to be a single linear mumber
78 // (instead of showing a separate horizontal/vertical index) we
79 // use the same format for slide jumps
80 if( /^\d+$/.test( query ) ) {
81 const slideNumberFormat = this.Reveal.getConfig().slideNumber;
82 if( slideNumberFormat === SLIDE_NUMBER_FORMAT_CURRENT || slideNumberFormat === SLIDE_NUMBER_FORMAT_CURRENT_SLASH_TOTAL ) {
83 const slide = this.Reveal.getSlides()[ parseInt( query, 10 ) - 1 ];
84 if( slide ) {
85 indices = this.Reveal.getIndices( slide );
86 }
87 }
88 }
89
90 if( !indices ) {
91 // If the query uses "horizontal.vertical" format, convert to
92 // "horizontal/vertical" so that our URL parser can understand
93 if( /^\d+\.\d+$/.test( query ) ) {
94 query = query.replace( '.', '/' );
95 }
96
97 indices = this.Reveal.location.getIndicesFromHash( query, { oneBasedIndex: true } );
98 }
99
100 // Still no valid index? Fall back on a text search
Marc Kupietz09b75752023-10-07 09:32:19 +0200101 if( !indices && /\S+/i.test( query ) && query.length > 1 ) {
102 indices = this.search( query );
103 }
104
105 if( indices && query !== '' ) {
106 this.Reveal.slide( indices.h, indices.v, indices.f );
107 return true;
108 }
109 else {
110 this.Reveal.slide( this.indicesOnShow.h, this.indicesOnShow.v, this.indicesOnShow.f );
111 return false;
112 }
113
114 }
115
116 jumpAfter( delay ) {
117
118 clearTimeout( this.jumpTimeout );
119 this.jumpTimeout = setTimeout( () => this.jump(), delay );
120
121 }
122
123 /**
124 * A lofi search that looks for the given query in all
125 * of our slides and returns the first match.
126 */
127 search( query ) {
128
129 const regex = new RegExp( '\\b' + query.trim() + '\\b', 'i' );
130
131 const slide = this.Reveal.getSlides().find( ( slide ) => {
132 return regex.test( slide.innerText );
133 } );
134
135 if( slide ) {
136 return this.Reveal.getIndices( slide );
137 }
138 else {
139 return null;
140 }
141
142 }
143
144 /**
145 * Reverts back to the slide we were on when jump to slide was
146 * invoked.
147 */
148 cancel() {
149
150 this.Reveal.slide( this.indicesOnShow.h, this.indicesOnShow.v, this.indicesOnShow.f );
151 this.hide();
152
153 }
154
155 confirm() {
156
157 this.jump();
158 this.hide();
159
160 }
161
162 destroy() {
163
164 this.jumpInput.removeEventListener( 'input', this.onInput );
165 this.jumpInput.removeEventListener( 'keydown', this.onKeyDown );
166 this.jumpInput.removeEventListener( 'blur', this.onBlur );
167
168 this.element.remove();
169
170 }
171
172 onKeyDown( event ) {
173
174 if( event.keyCode === 13 ) {
175 this.confirm();
176 }
177 else if( event.keyCode === 27 ) {
178 this.cancel();
179
180 event.stopImmediatePropagation();
181 }
182
183 }
184
185 onInput( event ) {
186
187 this.jumpAfter( 200 );
188
189 }
190
191 onBlur() {
192
193 setTimeout( () => this.hide(), 1 );
194
195 }
196
197}