",
253 | options: {
254 | disabled: false,
255 |
256 | // callbacks
257 | create: null
258 | },
259 | _createWidget: function( options, element ) {
260 | element = $( element || this.defaultElement || this )[ 0 ];
261 | this.element = $( element );
262 | this.uuid = widget_uuid++;
263 | this.eventNamespace = "." + this.widgetName + this.uuid;
264 |
265 | this.bindings = $();
266 | this.hoverable = $();
267 | this.focusable = $();
268 |
269 | if ( element !== this ) {
270 | $.data( element, this.widgetFullName, this );
271 | this._on( true, this.element, {
272 | remove: function( event ) {
273 | if ( event.target === element ) {
274 | this.destroy();
275 | }
276 | }
277 | });
278 | this.document = $( element.style ?
279 | // element within the document
280 | element.ownerDocument :
281 | // element is window or document
282 | element.document || element );
283 | this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
284 | }
285 |
286 | this.options = $.widget.extend( {},
287 | this.options,
288 | this._getCreateOptions(),
289 | options );
290 |
291 | this._create();
292 | this._trigger( "create", null, this._getCreateEventData() );
293 | this._init();
294 | },
295 | _getCreateOptions: $.noop,
296 | _getCreateEventData: $.noop,
297 | _create: $.noop,
298 | _init: $.noop,
299 |
300 | destroy: function() {
301 | this._destroy();
302 | // we can probably remove the unbind calls in 2.0
303 | // all event bindings should go through this._on()
304 | this.element
305 | .unbind( this.eventNamespace )
306 | .removeData( this.widgetFullName )
307 | // support: jquery <1.6.3
308 | // http://bugs.jquery.com/ticket/9413
309 | .removeData( $.camelCase( this.widgetFullName ) );
310 | this.widget()
311 | .unbind( this.eventNamespace )
312 | .removeAttr( "aria-disabled" )
313 | .removeClass(
314 | this.widgetFullName + "-disabled " +
315 | "ui-state-disabled" );
316 |
317 | // clean up events and states
318 | this.bindings.unbind( this.eventNamespace );
319 | this.hoverable.removeClass( "ui-state-hover" );
320 | this.focusable.removeClass( "ui-state-focus" );
321 | },
322 | _destroy: $.noop,
323 |
324 | widget: function() {
325 | return this.element;
326 | },
327 |
328 | option: function( key, value ) {
329 | var options = key,
330 | parts,
331 | curOption,
332 | i;
333 |
334 | if ( arguments.length === 0 ) {
335 | // don't return a reference to the internal hash
336 | return $.widget.extend( {}, this.options );
337 | }
338 |
339 | if ( typeof key === "string" ) {
340 | // handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
341 | options = {};
342 | parts = key.split( "." );
343 | key = parts.shift();
344 | if ( parts.length ) {
345 | curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
346 | for ( i = 0; i < parts.length - 1; i++ ) {
347 | curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
348 | curOption = curOption[ parts[ i ] ];
349 | }
350 | key = parts.pop();
351 | if ( arguments.length === 1 ) {
352 | return curOption[ key ] === undefined ? null : curOption[ key ];
353 | }
354 | curOption[ key ] = value;
355 | } else {
356 | if ( arguments.length === 1 ) {
357 | return this.options[ key ] === undefined ? null : this.options[ key ];
358 | }
359 | options[ key ] = value;
360 | }
361 | }
362 |
363 | this._setOptions( options );
364 |
365 | return this;
366 | },
367 | _setOptions: function( options ) {
368 | var key;
369 |
370 | for ( key in options ) {
371 | this._setOption( key, options[ key ] );
372 | }
373 |
374 | return this;
375 | },
376 | _setOption: function( key, value ) {
377 | this.options[ key ] = value;
378 |
379 | if ( key === "disabled" ) {
380 | this.widget()
381 | .toggleClass( this.widgetFullName + "-disabled", !!value );
382 |
383 | // If the widget is becoming disabled, then nothing is interactive
384 | if ( value ) {
385 | this.hoverable.removeClass( "ui-state-hover" );
386 | this.focusable.removeClass( "ui-state-focus" );
387 | }
388 | }
389 |
390 | return this;
391 | },
392 |
393 | enable: function() {
394 | return this._setOptions({ disabled: false });
395 | },
396 | disable: function() {
397 | return this._setOptions({ disabled: true });
398 | },
399 |
400 | _on: function( suppressDisabledCheck, element, handlers ) {
401 | var delegateElement,
402 | instance = this;
403 |
404 | // no suppressDisabledCheck flag, shuffle arguments
405 | if ( typeof suppressDisabledCheck !== "boolean" ) {
406 | handlers = element;
407 | element = suppressDisabledCheck;
408 | suppressDisabledCheck = false;
409 | }
410 |
411 | // no element argument, shuffle and use this.element
412 | if ( !handlers ) {
413 | handlers = element;
414 | element = this.element;
415 | delegateElement = this.widget();
416 | } else {
417 | element = delegateElement = $( element );
418 | this.bindings = this.bindings.add( element );
419 | }
420 |
421 | $.each( handlers, function( event, handler ) {
422 | function handlerProxy() {
423 | // allow widgets to customize the disabled handling
424 | // - disabled as an array instead of boolean
425 | // - disabled class as method for disabling individual parts
426 | if ( !suppressDisabledCheck &&
427 | ( instance.options.disabled === true ||
428 | $( this ).hasClass( "ui-state-disabled" ) ) ) {
429 | return;
430 | }
431 | return ( typeof handler === "string" ? instance[ handler ] : handler )
432 | .apply( instance, arguments );
433 | }
434 |
435 | // copy the guid so direct unbinding works
436 | if ( typeof handler !== "string" ) {
437 | handlerProxy.guid = handler.guid =
438 | handler.guid || handlerProxy.guid || $.guid++;
439 | }
440 |
441 | var match = event.match( /^([\w:-]*)\s*(.*)$/ ),
442 | eventName = match[1] + instance.eventNamespace,
443 | selector = match[2];
444 | if ( selector ) {
445 | delegateElement.delegate( selector, eventName, handlerProxy );
446 | } else {
447 | element.bind( eventName, handlerProxy );
448 | }
449 | });
450 | },
451 |
452 | _off: function( element, eventName ) {
453 | eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) +
454 | this.eventNamespace;
455 | element.unbind( eventName ).undelegate( eventName );
456 |
457 | // Clear the stack to avoid memory leaks (#10056)
458 | this.bindings = $( this.bindings.not( element ).get() );
459 | this.focusable = $( this.focusable.not( element ).get() );
460 | this.hoverable = $( this.hoverable.not( element ).get() );
461 | },
462 |
463 | _delay: function( handler, delay ) {
464 | function handlerProxy() {
465 | return ( typeof handler === "string" ? instance[ handler ] : handler )
466 | .apply( instance, arguments );
467 | }
468 | var instance = this;
469 | return setTimeout( handlerProxy, delay || 0 );
470 | },
471 |
472 | _hoverable: function( element ) {
473 | this.hoverable = this.hoverable.add( element );
474 | this._on( element, {
475 | mouseenter: function( event ) {
476 | $( event.currentTarget ).addClass( "ui-state-hover" );
477 | },
478 | mouseleave: function( event ) {
479 | $( event.currentTarget ).removeClass( "ui-state-hover" );
480 | }
481 | });
482 | },
483 |
484 | _focusable: function( element ) {
485 | this.focusable = this.focusable.add( element );
486 | this._on( element, {
487 | focusin: function( event ) {
488 | $( event.currentTarget ).addClass( "ui-state-focus" );
489 | },
490 | focusout: function( event ) {
491 | $( event.currentTarget ).removeClass( "ui-state-focus" );
492 | }
493 | });
494 | },
495 |
496 | _trigger: function( type, event, data ) {
497 | var prop, orig,
498 | callback = this.options[ type ];
499 |
500 | data = data || {};
501 | event = $.Event( event );
502 | event.type = ( type === this.widgetEventPrefix ?
503 | type :
504 | this.widgetEventPrefix + type ).toLowerCase();
505 | // the original event may come from any element
506 | // so we need to reset the target on the new event
507 | event.target = this.element[ 0 ];
508 |
509 | // copy original event properties over to the new event
510 | orig = event.originalEvent;
511 | if ( orig ) {
512 | for ( prop in orig ) {
513 | if ( !( prop in event ) ) {
514 | event[ prop ] = orig[ prop ];
515 | }
516 | }
517 | }
518 |
519 | this.element.trigger( event, data );
520 | return !( $.isFunction( callback ) &&
521 | callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
522 | event.isDefaultPrevented() );
523 | }
524 | };
525 |
526 | $.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
527 | $.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
528 | if ( typeof options === "string" ) {
529 | options = { effect: options };
530 | }
531 | var hasOptions,
532 | effectName = !options ?
533 | method :
534 | options === true || typeof options === "number" ?
535 | defaultEffect :
536 | options.effect || defaultEffect;
537 | options = options || {};
538 | if ( typeof options === "number" ) {
539 | options = { duration: options };
540 | }
541 | hasOptions = !$.isEmptyObject( options );
542 | options.complete = callback;
543 | if ( options.delay ) {
544 | element.delay( options.delay );
545 | }
546 | if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
547 | element[ method ]( options );
548 | } else if ( effectName !== method && element[ effectName ] ) {
549 | element[ effectName ]( options.duration, options.easing, callback );
550 | } else {
551 | element.queue(function( next ) {
552 | $( this )[ method ]();
553 | if ( callback ) {
554 | callback.call( element[ 0 ] );
555 | }
556 | next();
557 | });
558 | }
559 | };
560 | });
561 |
562 | var widget = $.widget;
563 |
564 |
565 |
566 | }));
567 |
--------------------------------------------------------------------------------
/public/swagger.yaml:
--------------------------------------------------------------------------------
1 | swagger: '2.0'
2 | info:
3 | title: The Wolf Among Us API
4 | description: |
5 | # Introduction
6 | Small test project working with Swagger and Redoc.
7 | This REST API provides the (current) characters, locations, and episodes within Telltale's The Wolf Among Us.
8 |
9 | The main repository can be found at [https://github.com/rokublak/the-wolf-among-us-api](https://github.com/rokublak/the-wolf-among-us-api)
10 |
11 | # Access
12 | #### Authentication
13 | No authentication is required for this API.
14 |
15 | #### Base URL
16 | All resource calls must be made with this base URL:
17 | `https://twauapi.herokuapp.com`
18 |
19 | #### GET Requests
20 | Requests can be made with `Curl` or with your desired language's HTTP GET request method. For example, with Curl, to GET the data for ***Bigsby Wolf***:
21 |
22 | `curl https://twauapi.herokuapp.com/characters/1`
23 |
24 | Response:
25 |
26 | ```json
27 | {
28 | "statusCode": 200,
29 | "body":
30 | {
31 | "id": 1,
32 | "name": "Bigby Wolf",
33 | "species": "Wolf",
34 | "gender": "Male",
35 | "occupation": "Fabletown Sherrif",
36 | "hairColour": "Mahogany",
37 | "eyeColour": "Brown",
38 | "description": "The renowned Big Bad Wolf. He's known for tormenting pigs and girls in red hoods, but is trying to put those dark days behind him. Bigby now acts as Fabletown's sheriff and remains in his human form, mostly. However, due to his rough past, the citizens of Fabletown are slow to trust him. Bigby is determined to show that he's truly changed, but some instincts are just too hard to control",
39 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/2/2b/CW_Bigby_Nerissa_Convo.png/revision/latest?cb=20140710174447"
40 | }
41 | }
42 | ```
43 | # Use cases
44 | No use cases really, besides cataloguing characters, locations and episodes...
45 | The JSON data is only ~40KB, so if you wanted to access the data you're better off just downloading it from main [github repo](https://github.com/rokublak/the-wolf-among-us-api) and implement it directly into your project(s). But if you want to access it with HTTP, here you go!
46 |
47 | version: "1.0.0"
48 | x-logo:
49 | url: "./images/logo2.png"
50 | backgroundColor: "#FFFF"
51 | # the domain of the service
52 | host: twauapi.herokuapp.com
53 | # array of all schemes that your API supports
54 | schemes:
55 | - https
56 | x-tagGroups:
57 | - name: Endpoints
58 | tags:
59 | - Characters
60 | - Locations
61 | - Episodes
62 | # will be prefixed to all paths
63 | basePath: /
64 | produces:
65 | - application/json
66 | paths:
67 | /characters:
68 | get:
69 | summary: All characters
70 | description: |
71 | `/characters` endpoint returns information for all the current characters.
72 | tags:
73 | - Characters
74 | parameters:
75 | - fields:
76 | name: fields
77 | in: query
78 | description: comma-separated list of fields to include in the response
79 | required: false
80 | type: string
81 | - limit:
82 | name: limit
83 | in: query
84 | required: false
85 | description: amount of results
86 | type: integer
87 | format: int64
88 | x-code-samples:
89 | - lang: Curl
90 | source: |
91 | curl -X GET -H "Content-Type: application/json" https://twauapi.herokuapp.com/characters
92 | - lang: Python
93 | source: |
94 | import urllib2
95 |
96 | urllib2.urlopen('https://twauapi.herokuapp.com/characters').read()
97 | - lang: JavaScript
98 | source: |
99 | var xmlhttp = new XMLHttpRequest();
100 | var url = 'https://twauapi.herokuapp.com/characters';
101 |
102 | xmlhttp.onreadystatechange = function() {
103 | if (this.readyState == 4 && this.status == 200) {
104 | var characters = JSON.parse(this.responseText);
105 | console.log(characters);
106 | }
107 | };
108 | xmlhttp.open("GET", url, true);
109 | xmlhttp.send();
110 | - lang: Ruby
111 | source: |
112 | require 'net/http'
113 |
114 | uri = URI('https://twauapi.herokuapp.com/characters')
115 | Net::HTTP.get(uri) # => String
116 |
117 | responses:
118 | 200:
119 | description: OK Success
120 | schema:
121 | $ref: '#/definitions/Character'
122 | examples:
123 | application/json:
124 | {
125 | "statusCode": 200,
126 | "body": [
127 | {
128 | "id": 1,
129 | "name": "Bigby Wolf",
130 | "species": "Wolf",
131 | "gender": "Male",
132 | "occupation": "Fabletown Sherrif",
133 | "hairColour": "Mahogany",
134 | "eyeColour": "Brown",
135 | "description": "The renowned Big Bad Wolf. He's known for tormenting pigs and girls in red hoods, but is trying to put those dark days behind him. Bigby now acts as Fabletown's sheriff and remains in his human form, mostly. However, due to his rough past, the citizens of Fabletown are slow to trust him. Bigby is determined to show that he's truly changed, but some instincts are just too hard to control",
136 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/2/2b/CW_Bigby_Nerissa_Convo.png/revision/latest?cb=20140710174447"
137 | },
138 | {
139 | "id": 2,
140 | "name": "Winter Wolf",
141 | "species": "Wolf",
142 | "gender": "Female",
143 | "occupation": "Unknown",
144 | "hairColour": "White",
145 | "eyeColour": "Unknown",
146 | "description": "Winter was the mother of Bigby Wolf and his brothers. Winter was a good and caring mother and Bigby regards her very highly and was displeased that his father, Mr. North, left her. Despite that, Winter loved and was devoted to him even to the moment of her death.",
147 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/e/e2/Winter_Wolf.png/revision/latest?cb=20140808191333"
148 | }
149 | ]
150 | }
151 |
152 | 404:
153 | description: Not found
154 | schema:
155 | $ref: '#/definitions/Error'
156 | examples:
157 | application/json:
158 | {
159 | "statusCode": 404,
160 | "message": "Not found"
161 | }
162 |
163 | /characters/{id}:
164 | get:
165 | summary: Character by ID
166 | description: |
167 | `/characters/{id}` endpoint returns information about a single character based on ID
168 | parameters:
169 | - name: id
170 | in: path
171 | description: character `id`
172 | required: true
173 | type: integer
174 | format: int64
175 | - name: fields
176 | in: query
177 | description: comma-separated list of fields to include in the response
178 | required: false
179 | type: string
180 | tags:
181 | - Characters
182 | x-code-samples:
183 | - lang: Curl
184 | source: |
185 | curl -X GET -H "Content-Type: application/json" https://twauapi.herokuapp.com/characters/7
186 | - lang: Python
187 | source: |
188 | import urllib2
189 |
190 | urllib2.urlopen('https://twauapi.herokuapp.com/characters/7').read()
191 | - lang: JavaScript
192 | source: |
193 | var xmlhttp = new XMLHttpRequest();
194 | var url = 'https://twauapi.herokuapp.com/characters/7';
195 |
196 | xmlhttp.onreadystatechange = function() {
197 | if (this.readyState == 4 && this.status == 200) {
198 | var character = JSON.parse(this.responseText);
199 | console.log(character);
200 | }
201 | };
202 | xmlhttp.open("GET", url, true);
203 | xmlhttp.send();
204 | - lang: Ruby
205 | source: |
206 | require 'net/http'
207 |
208 | uri = URI('https://twauapi.herokuapp.com/characters/7')
209 | Net::HTTP.get(uri) # => String
210 | responses:
211 | 200:
212 | description: OK Success
213 | schema:
214 | $ref: '#/definitions/Character'
215 | examples:
216 | application/json:
217 | statusCode: 200
218 | body:
219 | id: 7
220 | name: "The Woodsman"
221 | species: "Human"
222 | gender: "Male"
223 | occupation: "Unknown"
224 | hairColour: "Brown"
225 | eyeColour: "Green"
226 | description: "The Woodsman is one of the few men who went toe to toe with Bigby in his Black Forest days and lived to tell the tale. In an attempt to save Little Red Riding Hood, he split the great wolf's belly open with his axe, filled him full of rocks, and threw the beast into a river. To his dismay his popularity faded. Even his name is forgotten, and he is only known as The Woodsman."
227 | imagePath: "https://vignette.wikia.nocookie.net/fables/images/4/47/ISC_Woody_Angry.png/revision/latest/scale-to-width-down/250?cb=20140528030339"
228 | '404':
229 | description: Not found
230 | schema:
231 | $ref: '#/definitions/Error'
232 | examples:
233 | application/json:
234 | {
235 | "statusCode": 404,
236 | "message": "Not found"
237 | }
238 |
239 | /locations:
240 | get:
241 | summary: All locations
242 | description: |
243 | `/locations` endpoint returns information about all the locations.
244 | tags:
245 | - Locations
246 | parameters:
247 | - fields:
248 | name: fields
249 | in: query
250 | description: comma-separated list of fields to include in the response
251 | required: false
252 | type: string
253 | - limit:
254 | name: limit
255 | in: query
256 | required: false
257 | description: amount of results
258 | type: integer
259 | format: int64
260 | x-code-samples:
261 | - lang: Curl
262 | source: |
263 | curl -X GET -H "Content-Type: application/json" https://twauapi.herokuapp.com/locations
264 | - lang: Python
265 | source: |
266 | import urllib2
267 |
268 | urllib2.urlopen('https://twauapi.herokuapp.com/locations').read()
269 | - lang: JavaScript
270 | source: |
271 | var xmlhttp = new XMLHttpRequest();
272 | var url = 'https://twauapi.herokuapp.com/locations';
273 |
274 | xmlhttp.onreadystatechange = function() {
275 | if (this.readyState == 4 && this.status == 200) {
276 | var locations = JSON.parse(this.responseText);
277 | console.log(locations);
278 | }
279 | };
280 | xmlhttp.open("GET", url, true);
281 | xmlhttp.send();
282 | - lang: Ruby
283 | source: |
284 | require 'net/http'
285 |
286 | uri = URI('https:/twauapi.herokuapp.com/locations')
287 | Net::HTTP.get(uri) # => String
288 | responses:
289 | 200:
290 | description: OK Success
291 | schema:
292 | $ref: '#/definitions/Location'
293 | examples:
294 | application/json:
295 | {
296 | "statusCode": 200,
297 | "body":[
298 | {
299 | "id": 1,
300 | "locationName": "New York City",
301 | "description": "Fabletown is situated in New York City",
302 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/4/43/FTH_Manhattan_Skyline.png/revision/latest/scale-to-width-down/1300?cb=20140826020954"
303 | },
304 | {
305 | "id": 2,
306 | "locationName": "Toad's Tenement",
307 | "description": "A tenement building owned by Mr. Toad. The Woodsman owns an apartment in it.",
308 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/b/b5/FTH_Toad%27s_Tenement.png/revision/latest/scale-to-width-down/1300?cb=20140614235651"
309 | }
310 | ]
311 | }
312 |
313 | '404':
314 | description: Not found
315 | schema:
316 | $ref: '#/definitions/Error'
317 | examples:
318 | application/json:
319 | {
320 | "statusCode": 404,
321 | "message": "Not found"
322 | }
323 |
324 | /locations/{id}:
325 | get:
326 | summary: Location by ID
327 | description: |
328 | `/locations/{id}` endpoints returns information about a single location based on ID.
329 | parameters:
330 | - name: id
331 | in: path
332 | description: location `id`
333 | required: true
334 | type: integer
335 | format: int64
336 | - name: fields
337 | in: query
338 | description: comma-separated list of fields to include in the response
339 | required: false
340 | type: string
341 | tags:
342 | - Locations
343 | x-code-samples:
344 | - lang: Curl
345 | source: |
346 | curl -X GET -H "Content-Type: application/json" https://twauapi.herokuapp.com/locations/19
347 | - lang: Python
348 | source: |
349 | import urllib2
350 |
351 | urllib2.urlopen('https://twauapi.herokuapp.com/locations/19').read()
352 | - lang: JavaScript
353 | source: |
354 | var xmlhttp = new XMLHttpRequest();
355 | var url = 'https://twauapi.herokuapp.com/locations/19';
356 |
357 | xmlhttp.onreadystatechange = function() {
358 | if (this.readyState == 4 && this.status == 200) {
359 | var location = JSON.parse(this.responseText);
360 | console.log(location);
361 | }
362 | };
363 | xmlhttp.open("GET", url, true);
364 | xmlhttp.send();
365 | - lang: Ruby
366 | source: |
367 | require 'net/http'
368 |
369 | uri = URI('https://twauapi.herokuapp.com/locations/19')
370 | Net::HTTP.get(uri) # => String
371 | responses:
372 | 200:
373 | description: OK Success
374 | schema:
375 | $ref: '#/definitions/Location'
376 | examples:
377 | application/json:
378 | statusCode: 200
379 | body:
380 | id: 19
381 | locationName: "Beauty and Beast's Apartment"
382 | description: "A lavish apartment in the Woodlands that Beauty and Beast live in."
383 | imagePath: "https://vignette.wikia.nocookie.net/fables/images/2/2d/ISC_Fishing_for_Sympathy.png/revision/latest/scale-to-width-down/1300?cb=20140531212737"
384 | '404':
385 | description: Not found
386 | schema:
387 | $ref: '#/definitions/Error'
388 | examples:
389 | application/json:
390 | {
391 | "statusCode": 404,
392 | "message": "Not found"
393 | }
394 |
395 | /episodes:
396 | get:
397 | summary: All episodes
398 | description: |
399 | `/episodes` endpoint returns information about the all current episodes.
400 | tags:
401 | - Episodes
402 | parameters:
403 | - fields:
404 | name: fields
405 | in: query
406 | description: comma-separated list of fields to include in the response
407 | required: false
408 | type: string
409 | - limit:
410 | name: limit
411 | in: query
412 | required: false
413 | description: amount of results
414 | type: integer
415 | format: int64
416 | x-code-samples:
417 | - lang: Curl
418 | source: |
419 | curl -X GET -H "Content-Type: application/json" https://twauapi.herokuapp.com/episodes
420 | - lang: Ruby
421 | source: |
422 | require 'net/http'
423 |
424 | uri = URI('https://twauapi.herokuapp.com/episodes')
425 | Net::HTTP.get(uri) # => String
426 | - lang: Python
427 | source: |
428 | import urllib2
429 |
430 | urllib2.urlopen('https://twauapi.herokuapp.com/episodes').read()
431 | - lang: JavaScript
432 | source: |
433 | var xmlhttp = new XMLHttpRequest();
434 | var url = 'https://twauapi.herokuapp.com/episodes';
435 |
436 | xmlhttp.onreadystatechange = function() {
437 | if (this.readyState == 4 && this.status == 200) {
438 | var episodes = JSON.parse(this.responseText);
439 | console.log(episodes);
440 | }
441 | };
442 | xmlhttp.open("GET", url, true);
443 | xmlhttp.send();
444 | responses:
445 | 200:
446 | description: OK Success
447 | schema:
448 | $ref: '#/definitions/Episode'
449 | examples:
450 | application/json:
451 | {
452 | "statusCode": 200,
453 | "body":[
454 | {
455 | "id": 1,
456 | "episodeName": "Faith",
457 | "episodeNum": 1,
458 | "season": 1,
459 | "description": "Bigby Wolf, Sheriff of Fabletown, must work a murder case when a Fable is killed and he must work with Snow White to find out who the murderer is. The two of them work together and the investigation goes deep, with a shocking ending.",
460 | "directedBy": "Nick Herman & Dennis Lenart",
461 | "writtenBy": "Pierre Shorette & Ryan Kaufman",
462 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/b/b6/FTH_Cover.png/revision/latest?cb=20140214011436"
463 | },
464 | {
465 | "id": 2,
466 | "episodeName": "Smoke & Mirrors",
467 | "episodeNum": 2,
468 | "season": 1,
469 | "description": "The episode starts with Bigby being questioned by a female detective named Kelsey Brannigan. Bigby is reluctant to answer her questions. Suddenly, the detective and police officers in the room pass out, and it is shown that Crane had used a memory-wipe spell to let Bigby out.",
470 | "directedBy": "Jason Latino",
471 | "writtenBy": "Dave Grossman & Joe Pinney",
472 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/a/a2/SAM_Cover.png/revision/latest?cb=20140214011612"
473 | }
474 | ]
475 | }
476 | '404':
477 | description: Not found
478 | schema:
479 | $ref: '#/definitions/Error'
480 | examples:
481 | application/json:
482 | {
483 | "statusCode": 404,
484 | "message": "Not found"
485 | }
486 |
487 | /episodes/{id}:
488 | get:
489 | summary: Episode by ID
490 | description: |
491 | `/episodes` endpoint returns information about a single episode based on ID.
492 | tags:
493 | - Episodes
494 | parameters:
495 | - name: id
496 | in: path
497 | description: episodes `id`
498 | required: true
499 | type: integer
500 | format: int64
501 | - name: fields
502 | in: query
503 | description: comma-separated list of fields to include in the response
504 | required: false
505 | type: string
506 | x-code-samples:
507 | - lang: Curl
508 | source: |
509 | curl -X GET -H "Content-Type: application/json" https://twauapi.herokuapp.com/episodes/3
510 | - lang: Ruby
511 | source: |
512 | require 'net/http'
513 |
514 | uri = URI('https://twauapi.herokuapp.com/episodes/3')
515 | Net::HTTP.get(uri) # => String
516 | - lang: Python
517 | source: |
518 | import urllib2
519 |
520 | urllib2.urlopen('https://twauapi.herokuapp.com/episodes/3').read()
521 | - lang: JavaScript
522 | source: |
523 | var xmlhttp = new XMLHttpRequest();
524 | var url = 'https://twauapi.herokuapp.com/episodes/3';
525 |
526 | xmlhttp.onreadystatechange = function() {
527 | if (this.readyState == 4 && this.status == 200) {
528 | var episode = JSON.parse(this.responseText);
529 | console.log(episode);
530 | }
531 | };
532 | xmlhttp.open("GET", url, true);
533 | xmlhttp.send();
534 | responses:
535 | 200:
536 | description: OK Success
537 | schema:
538 | $ref: '#/definitions/Episode'
539 | examples:
540 | application/json:
541 | statusCode: 200
542 | body:
543 | id: 3
544 | episodeName: "A Crooked Mile"
545 | episodeNum: 3
546 | season: 1
547 | description: "Bigby has discovered that Crane is the murderer; a photo of him in bed with a murdered woman glamoured to look like Snow was found. He sees Beauty and Beast off to arrive at Lily's funeral, where Holly, Grendel, Prince Lawrence, Vivian and Nerissa are all attending. Eventually, Snow returns to the funeral and asks Bigby to wait at a distance."
548 | directedBy: " Martin Montgomery"
549 | writtenBy: "Adam Hines & Ryan Kaufman"
550 | imagePath: "https://vignette.wikia.nocookie.net/fables/images/0/03/ACM_Cover.png/revision/latest/?cb=20140214012342"
551 | '404':
552 | description: Not found
553 | schema:
554 | $ref: '#/definitions/Error'
555 | examples:
556 | application/json:
557 | {
558 | "statusCode": 404,
559 | "message": "Not found"
560 | }
561 |
562 | definitions:
563 | Character:
564 | type: object
565 | properties:
566 | statusCode:
567 | type: integer
568 | description: Response status code
569 | body:
570 | type: array
571 | description: Response body
572 | items:
573 | $ref: '#/definitions/Characters'
574 | Characters:
575 | type: object
576 | properties:
577 | id:
578 | type: integer
579 | description: Unique identifier representing a specific character
580 | name:
581 | type: string
582 | description: Name of the character
583 | species:
584 | type: string
585 | description: Species of the character
586 | gender:
587 | type: string
588 | description: Gender of the character
589 | occupation:
590 | type: string
591 | description: Occupation of the character (if known)
592 | hairColour:
593 | type: string
594 | description: Hair colour of the character
595 | eyeColour:
596 | type: string
597 | description: Eye colour of the character
598 | description:
599 | type: string
600 | description: Brief description of the character
601 | imagePath:
602 | type: string
603 | description: Image of the character URL
604 | Location:
605 | type: object
606 | properties:
607 | statusCode:
608 | type: integer
609 | description: Response status code
610 | body:
611 | type: array
612 | description: Response body
613 | items:
614 | $ref: '#/definitions/Locations'
615 | Locations:
616 | type: object
617 | properties:
618 | id:
619 | type: integer
620 | description: Unique identifier representing a specific location
621 | locationName:
622 | type: string
623 | description: Name of location
624 | description:
625 | type: string
626 | description: Brief description about the location
627 | imagePath:
628 | type: string
629 | description: Image of the location URL
630 | Episode:
631 | type: object
632 | properties:
633 | statusCode:
634 | type: integer
635 | description: Response status code
636 | body:
637 | type: array
638 | description: Response body
639 | items:
640 | $ref: '#/definitions/Episodes'
641 | Episodes:
642 | type: object
643 | properties:
644 | id:
645 | type: integer
646 | description: Unique identifier representing a specific episode
647 | episodeName:
648 | type: string
649 | description: Name of the episode
650 | episodeNum:
651 | type: integer
652 | description: Episode number in it's respected season
653 | season:
654 | type: integer
655 | description: Season number of episode
656 | description:
657 | type: string
658 | description: Brief description of the episode
659 | directedBy:
660 | type: string
661 | description: Episode director(s)
662 | writtenBy:
663 | type: string
664 | description: Episode writer(s)
665 | imagePath:
666 | type: string
667 | description: Image of the episode URL
668 | Error:
669 | type: object
670 | description: Error reponse
671 | properties:
672 | statusCode:
673 | type: integer
674 | format: int32
675 | description: HTTP status code
676 | message:
677 | type: string
678 | description: Status code message
679 |
--------------------------------------------------------------------------------
/public/css/style.css:
--------------------------------------------------------------------------------
1 | /*! normalize.css v3.0.2 | MIT License | git.io/normalize */
2 |
3 | /**
4 | * 1. Set default font family to sans-serif.
5 | * 2. Prevent iOS text size adjust after orientation change, without disabling
6 | * user zoom.
7 | */
8 |
9 | html {
10 | font-family: sans-serif; /* 1 */
11 | -ms-text-size-adjust: 100%; /* 2 */
12 | -webkit-text-size-adjust: 100%; /* 2 */
13 | }
14 |
15 | /**
16 | * Remove default margin.
17 | */
18 |
19 | body {
20 | margin: 0;
21 | }
22 |
23 | /* HTML5 display definitions
24 | ========================================================================== */
25 |
26 | /**
27 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
28 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
29 | * and Firefox.
30 | * Correct `block` display not defined for `main` in IE 11.
31 | */
32 |
33 | article,
34 | aside,
35 | details,
36 | figcaption,
37 | figure,
38 | footer,
39 | header,
40 | hgroup,
41 | main,
42 | menu,
43 | nav,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | /**
50 | * 1. Correct `inline-block` display not defined in IE 8/9.
51 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
52 | */
53 |
54 | audio,
55 | canvas,
56 | progress,
57 | video {
58 | display: inline-block; /* 1 */
59 | vertical-align: baseline; /* 2 */
60 | }
61 |
62 | /**
63 | * Prevent modern browsers from displaying `audio` without controls.
64 | * Remove excess height in iOS 5 devices.
65 | */
66 |
67 | audio:not([controls]) {
68 | display: none;
69 | height: 0;
70 | }
71 |
72 | /**
73 | * Address `[hidden]` styling not present in IE 8/9/10.
74 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
75 | */
76 |
77 | [hidden],
78 | template {
79 | display: none;
80 | }
81 |
82 | /* Links
83 | ========================================================================== */
84 |
85 | /**
86 | * Remove the gray background color from active links in IE 10.
87 | */
88 |
89 | a {
90 | background-color: transparent;
91 | }
92 |
93 | /**
94 | * Improve readability when focused and also mouse hovered in all browsers.
95 | */
96 |
97 | a:active,
98 | a:hover {
99 | outline: 0;
100 | }
101 |
102 | /* Text-level semantics
103 | ========================================================================== */
104 |
105 | /**
106 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
107 | */
108 |
109 | abbr[title] {
110 | border-bottom: 1px dotted;
111 | }
112 |
113 | /**
114 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
115 | */
116 |
117 | b,
118 | strong {
119 | font-weight: bold;
120 | }
121 |
122 | /**
123 | * Address styling not present in Safari and Chrome.
124 | */
125 |
126 | dfn {
127 | font-style: italic;
128 | }
129 |
130 | /**
131 | * Address variable `h1` font-size and margin within `section` and `article`
132 | * contexts in Firefox 4+, Safari, and Chrome.
133 | */
134 |
135 | h1 {
136 | font-size: 2em;
137 | margin: 0.67em 0;
138 | }
139 |
140 | /**
141 | * Address styling not present in IE 8/9.
142 | */
143 |
144 | mark {
145 | background: #ff0;
146 | color: #000;
147 | }
148 |
149 | /**
150 | * Address inconsistent and variable font size in all browsers.
151 | */
152 |
153 | small {
154 | font-size: 80%;
155 | }
156 |
157 | /**
158 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
159 | */
160 |
161 | sub,
162 | sup {
163 | font-size: 75%;
164 | line-height: 0;
165 | position: relative;
166 | vertical-align: baseline;
167 | }
168 |
169 | sup {
170 | top: -0.5em;
171 | }
172 |
173 | sub {
174 | bottom: -0.25em;
175 | }
176 |
177 | /* Embedded content
178 | ========================================================================== */
179 |
180 | /**
181 | * Remove border when inside `a` element in IE 8/9/10.
182 | */
183 |
184 | img {
185 | border: 0;
186 | }
187 |
188 | /**
189 | * Correct overflow not hidden in IE 9/10/11.
190 | */
191 |
192 | svg:not(:root) {
193 | overflow: hidden;
194 | }
195 |
196 | /* Grouping content
197 | ========================================================================== */
198 |
199 | /**
200 | * Address margin not present in IE 8/9 and Safari.
201 | */
202 |
203 | figure {
204 | margin: 1em 40px;
205 | }
206 |
207 | /**
208 | * Address differences between Firefox and other browsers.
209 | */
210 |
211 | hr {
212 | -moz-box-sizing: content-box;
213 | box-sizing: content-box;
214 | height: 0;
215 | }
216 |
217 | /**
218 | * Contain overflow in all browsers.
219 | */
220 |
221 | pre {
222 | overflow: auto;
223 | }
224 |
225 | /**
226 | * Address odd `em`-unit font size rendering in all browsers.
227 | */
228 |
229 | code,
230 | kbd,
231 | pre,
232 | samp {
233 | font-family: monospace, monospace;
234 | font-size: 1em;
235 | }
236 |
237 | /* Forms
238 | ========================================================================== */
239 |
240 | /**
241 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
242 | * styling of `select`, unless a `border` property is set.
243 | */
244 |
245 | /**
246 | * 1. Correct color not being inherited.
247 | * Known issue: affects color of disabled elements.
248 | * 2. Correct font properties not being inherited.
249 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
250 | */
251 |
252 | button,
253 | input,
254 | optgroup,
255 | select,
256 | textarea {
257 | color: inherit; /* 1 */
258 | font: inherit; /* 2 */
259 | margin: 0; /* 3 */
260 | }
261 |
262 | /**
263 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
264 | */
265 |
266 | button {
267 | overflow: visible;
268 | }
269 |
270 | /**
271 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
272 | * All other form control elements do not inherit `text-transform` values.
273 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
274 | * Correct `select` style inheritance in Firefox.
275 | */
276 |
277 | button,
278 | select {
279 | text-transform: none;
280 | }
281 |
282 | /**
283 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
284 | * and `video` controls.
285 | * 2. Correct inability to style clickable `input` types in iOS.
286 | * 3. Improve usability and consistency of cursor style between image-type
287 | * `input` and others.
288 | */
289 |
290 | button,
291 | html input[type="button"], /* 1 */
292 | input[type="reset"],
293 | input[type="submit"] {
294 | -webkit-appearance: button; /* 2 */
295 | cursor: pointer; /* 3 */
296 | }
297 |
298 | /**
299 | * Re-set default cursor for disabled elements.
300 | */
301 |
302 | button[disabled],
303 | html input[disabled] {
304 | cursor: default;
305 | }
306 |
307 | /**
308 | * Remove inner padding and border in Firefox 4+.
309 | */
310 |
311 | button::-moz-focus-inner,
312 | input::-moz-focus-inner {
313 | border: 0;
314 | padding: 0;
315 | }
316 |
317 | /**
318 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
319 | * the UA stylesheet.
320 | */
321 |
322 | input {
323 | line-height: normal;
324 | }
325 |
326 | /**
327 | * It's recommended that you don't attempt to style these elements.
328 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
329 | *
330 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
331 | * 2. Remove excess padding in IE 8/9/10.
332 | */
333 |
334 | input[type="checkbox"],
335 | input[type="radio"] {
336 | box-sizing: border-box; /* 1 */
337 | padding: 0; /* 2 */
338 | }
339 |
340 | /**
341 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
342 | * `font-size` values of the `input`, it causes the cursor style of the
343 | * decrement button to change from `default` to `text`.
344 | */
345 |
346 | input[type="number"]::-webkit-inner-spin-button,
347 | input[type="number"]::-webkit-outer-spin-button {
348 | height: auto;
349 | }
350 |
351 | /**
352 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
353 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
354 | * (include `-moz` to future-proof).
355 | */
356 |
357 | input[type="search"] {
358 | -webkit-appearance: textfield; /* 1 */
359 | -moz-box-sizing: content-box;
360 | -webkit-box-sizing: content-box; /* 2 */
361 | box-sizing: content-box;
362 | }
363 |
364 | /**
365 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
366 | * Safari (but not Chrome) clips the cancel button when the search input has
367 | * padding (and `textfield` appearance).
368 | */
369 |
370 | input[type="search"]::-webkit-search-cancel-button,
371 | input[type="search"]::-webkit-search-decoration {
372 | -webkit-appearance: none;
373 | }
374 |
375 | /**
376 | * Define consistent border, margin, and padding.
377 | */
378 |
379 | fieldset {
380 | border: 1px solid #c0c0c0;
381 | margin: 0 2px;
382 | padding: 0.35em 0.625em 0.75em;
383 | }
384 |
385 | /**
386 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
387 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
388 | */
389 |
390 | legend {
391 | border: 0; /* 1 */
392 | padding: 0; /* 2 */
393 | }
394 |
395 | /**
396 | * Remove default vertical scrollbar in IE 8/9/10/11.
397 | */
398 |
399 | textarea {
400 | overflow: auto;
401 | }
402 |
403 | /**
404 | * Don't inherit the `font-weight` (applied by a rule above).
405 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
406 | */
407 |
408 | optgroup {
409 | font-weight: bold;
410 | }
411 |
412 | /* Tables
413 | ========================================================================== */
414 |
415 | /**
416 | * Remove most spacing between table cells.
417 | */
418 |
419 | table {
420 | border-collapse: collapse;
421 | border-spacing: 0;
422 | }
423 |
424 | td,
425 | th {
426 | padding: 0;
427 | }
428 |
429 | html,
430 | body,
431 | .content h1,
432 | .content h2,
433 | .content h3,
434 | .content h4,
435 | .content h5,
436 | .content h6 {
437 | font-family: "Helvetica Neue", Helvetica, Arial, "Microsoft Yahei", "微软雅黑", STXihei, "华文细黑", sans-serif;
438 | font-size: 13px;
439 | }
440 | .content h1,
441 | .content h2,
442 | .content h3,
443 | .content h4,
444 | .content h5,
445 | .content h6 {
446 | font-weight: bold;
447 | }
448 | .content code,
449 | .content pre {
450 | font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;
451 | font-size: 12px;
452 | line-height: 1.5;
453 | }
454 | .content code {
455 | word-break: break-all;
456 | /* Non standard for webkit */
457 | word-break: break-word;
458 | hyphens: auto;
459 | }
460 | @font-face {
461 | font-family: FontAwesome;
462 | font-style: normal;
463 | font-weight: bold;
464 | src: url("fonts/fontawesome-webfont.eot?v=#4.0.3");
465 | src: url("fonts/fontawesome-webfont.eot?#iefix&v=#4.0.3") format("embedded-opentype"), url("fonts/fontawesome-webfont.woff?v=#4.0.3") format("woff"), url("fonts/fontawesome-webfont.ttf?v=#4.0.3") format("truetype"), url("fonts/fontawesome-webfont.svg#fontawesomeregular?v=#4.0.3") format("svg");
466 | }
467 | .tocify-wrapper > .search:before,
468 | .content aside.notice:before,
469 | .content aside.warning:before,
470 | .content aside.success:before {
471 | font-family: 'FontAwesome';
472 | speak: none;
473 | font-style: normal;
474 | font-weight: normal;
475 | font-variant: normal;
476 | text-transform: none;
477 | line-height: 1;
478 | }
479 | .content aside.warning:before {
480 | content: "\f06a";
481 | }
482 | .content aside.notice:before {
483 | content: "\f05a";
484 | }
485 | .content aside.success:before {
486 | content: "\f058";
487 | }
488 | .tocify-wrapper > .search:before {
489 | content: "\f002";
490 | }
491 | .highlight .c,
492 | .highlight .cm,
493 | .highlight .c1,
494 | .highlight .cs {
495 | color: #909090;
496 | }
497 | .highlight,
498 | .highlight .w {
499 | background-color: #292929;
500 | }
501 | /*
502 |
503 | Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/
504 |
505 | */
506 |
507 | .hljs {
508 | display: block;
509 | overflow-x: auto;
510 | padding: 0.5em;
511 | background: #23241f;
512 | }
513 |
514 | .hljs,
515 | .hljs-tag,
516 | .hljs-subst {
517 | color: #f8f8f2;
518 | }
519 |
520 | .hljs-strong,
521 | .hljs-emphasis {
522 | color: #a8a8a2;
523 | }
524 |
525 | .hljs-bullet,
526 | .hljs-quote,
527 | .hljs-number,
528 | .hljs-regexp,
529 | .hljs-literal,
530 | .hljs-link {
531 | color: #ae81ff;
532 | }
533 |
534 | .hljs-code,
535 | .hljs-title,
536 | .hljs-section,
537 | .hljs-selector-class {
538 | color: #a6e22e;
539 | }
540 |
541 | .hljs-strong {
542 | font-weight: bold;
543 | }
544 |
545 | .hljs-emphasis {
546 | font-style: italic;
547 | }
548 |
549 | .hljs-keyword,
550 | .hljs-selector-tag,
551 | .hljs-name,
552 | .hljs-attr {
553 | color: #f92672;
554 | }
555 |
556 | .hljs-symbol,
557 | .hljs-attribute {
558 | color: #66d9ef;
559 | }
560 |
561 | .hljs-params,
562 | .hljs-class .hljs-title {
563 | color: #f8f8f2;
564 | }
565 |
566 | .hljs-string,
567 | .hljs-type,
568 | .hljs-built_in,
569 | .hljs-builtin-name,
570 | .hljs-selector-id,
571 | .hljs-selector-attr,
572 | .hljs-selector-pseudo,
573 | .hljs-addition,
574 | .hljs-variable,
575 | .hljs-template-variable {
576 | color: #e6db74;
577 | }
578 |
579 | .hljs-comment,
580 | .hljs-deletion,
581 | .hljs-meta {
582 | color: #75715e;
583 | }
584 |
585 | /*
586 | Copyright 2008-2013 Concur Technologies, Inc.
587 |
588 | Licensed under the Apache License, Version 2.0 (the "License"); you may
589 | not use this file except in compliance with the License. You may obtain
590 | a copy of the License at
591 |
592 | http://www.apache.org/licenses/LICENSE-2.0
593 |
594 | Unless required by applicable law or agreed to in writing, software
595 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
596 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
597 | License for the specific language governing permissions and limitations
598 | under the License.
599 | */
600 | html,
601 | body {
602 | color: #333;
603 | padding: 0;
604 | margin: 0;
605 | -webkit-font-smoothing: antialiased;
606 | -moz-osx-font-smoothing: grayscale;
607 | background-color: #eaf2f6;
608 | height: 100%;
609 | -webkit-text-size-adjust: none;
610 | /* Never autoresize text */
611 | }
612 | #toc > ul > li > a > span {
613 | float: right;
614 | background-color: #2484ff;
615 | border-radius: 40px;
616 | width: 20px;
617 | }
618 | .tocify-wrapper {
619 | transition: left 0.3s ease-in-out;
620 | overflow-y: auto;
621 | overflow-x: hidden;
622 | position: fixed;
623 | z-index: 30;
624 | top: 0;
625 | left: 0;
626 | bottom: 0;
627 | width: 230px;
628 | background-color: #393939;
629 | font-size: 13px;
630 | font-weight: bold;
631 | }
632 | .tocify-wrapper .lang-selector {
633 | display: none;
634 | }
635 | .tocify-wrapper .lang-selector a {
636 | padding-top: 0.5em;
637 | padding-bottom: 0.5em;
638 | }
639 | .tocify-wrapper > img {
640 | display: block;
641 | }
642 | .tocify-wrapper > .search {
643 | position: relative;
644 | }
645 | .tocify-wrapper > .search input {
646 | background: #393939;
647 | border-width: 0 0 1px 0;
648 | border-color: #666;
649 | padding: 6px 0 6px 20px;
650 | box-sizing: border-box;
651 | margin: 10px 15px;
652 | width: 200px;
653 | outline: none;
654 | color: #fff;
655 | border-radius: 0;
656 | /* ios has a default border radius */
657 | }
658 | .tocify-wrapper > .search:before {
659 | position: absolute;
660 | top: 17px;
661 | left: 15px;
662 | color: #fff;
663 | }
664 | .tocify-wrapper img+.tocify {
665 | margin-top: 20px;
666 | }
667 | .tocify-wrapper .search-results {
668 | margin-top: 0;
669 | box-sizing: border-box;
670 | height: 0;
671 | overflow-y: auto;
672 | overflow-x: hidden;
673 | transition-property: height, margin;
674 | transition-duration: 180ms;
675 | transition-timing-function: ease-in-out;
676 | background: linear-gradient(to bottom, rgba(0,0,0,0.2), rgba(0,0,0,0) 8px), linear-gradient(to top, rgba(0,0,0,0.2), rgba(0,0,0,0) 8px), linear-gradient(to bottom, #000, rgba(0,0,0,0) 1.5px), linear-gradient(to top, #939393, rgba(147,147,147,0) 1.5px), #262626;
677 | }
678 | .tocify-wrapper .search-results.visible {
679 | height: 30%;
680 | margin-bottom: 1em;
681 | }
682 | .tocify-wrapper .search-results li {
683 | margin: 1em 15px;
684 | line-height: 1;
685 | }
686 | .tocify-wrapper .search-results a {
687 | color: #fff;
688 | text-decoration: none;
689 | }
690 | .tocify-wrapper .search-results a:hover {
691 | text-decoration: underline;
692 | }
693 | .tocify-wrapper .tocify-item>a,
694 | .tocify-wrapper .toc-footer li {
695 | padding: 0 15px 0 15px;
696 | display: block;
697 | overflow-x: hidden;
698 | white-space: nowrap;
699 | text-overflow: ellipsis;
700 | }
701 | .tocify-wrapper ul,
702 | .tocify-wrapper li {
703 | list-style: none;
704 | margin: 0;
705 | padding: 0;
706 | line-height: 28px;
707 | }
708 | .tocify-wrapper li {
709 | color: #fff;
710 | transition-property: background;
711 | transition-timing-function: linear;
712 | transition-duration: 230ms;
713 | }
714 | .tocify-wrapper .tocify-focus {
715 | box-shadow: 0px 1px 0px #000;
716 | background-color: #2467af;
717 | color: #fff;
718 | }
719 | .tocify-wrapper .tocify-subheader {
720 | display: none;
721 | background-color: #262626;
722 | font-weight: 500;
723 | background: linear-gradient(to bottom, rgba(0,0,0,0.2), rgba(0,0,0,0) 8px), linear-gradient(to top, rgba(0,0,0,0.2), rgba(0,0,0,0) 8px), linear-gradient(to bottom, #000, rgba(0,0,0,0) 1.5px), linear-gradient(to top, #939393, rgba(147,147,147,0) 1.5px), #262626;
724 | }
725 | .tocify-wrapper .tocify-subheader .tocify-item>a {
726 | padding-left: 25px;
727 | font-size: 12px;
728 | }
729 | .tocify-wrapper .tocify-subheader > li:last-child {
730 | box-shadow: none;
731 | }
732 | .tocify-wrapper .tocify-subheader .tocify-subheader {
733 | display: none;
734 | background-color: #262626;
735 | font-weight: 500;
736 | }
737 | .tocify-wrapper .tocify-subheader .tocify-subheader .tocify-item>a {
738 | padding-left: 55px;
739 | font-size: 12px;
740 | background: linear-gradient(to bottom, rgba(0,0,0,0.2), rgba(0,0,0,0) 8px), linear-gradient(to top, rgba(0,0,0,0.2), rgba(0,0,0,0) 8px), linear-gradient(to bottom, #000, rgba(0,0,0,0) 1.5px), linear-gradient(to top, #939393, rgba(147,147,147,0) 1.5px), #262626;
741 | }
742 | .tocify-wrapper .tocify-subheader .tocify-subheader .tocify-item>a > li:last-child {
743 | box-shadow: none;
744 | }
745 | .tocify-wrapper .tocify-subheader .tocify-subheader .tocify-subheader {
746 | display: none;
747 | background-color: #262626;
748 | font-weight: 500;
749 | }
750 | .tocify-wrapper .tocify-subheader .tocify-subheader .tocify-subheader .tocify-item>a {
751 | padding-left: 75px;
752 | font-size: 12px;
753 | background: linear-gradient(to bottom, rgba(0,0,0,0.2), rgba(0,0,0,0) 8px), linear-gradient(to top, rgba(0,0,0,0.2), rgba(0,0,0,0) 8px), linear-gradient(to bottom, #000, rgba(0,0,0,0) 1.5px), linear-gradient(to top, #939393, rgba(147,147,147,0) 1.5px), #262626;
754 | }
755 | .tocify-wrapper .tocify-subheader .tocify-subheader .tocify-subheader .tocify-item>a > li:last-child {
756 | box-shadow: none;
757 | }
758 | .tocify-wrapper .toc-footer {
759 | padding: 1em 0;
760 | margin-top: 1em;
761 | border-top: 1px dashed #666;
762 | }
763 | .tocify-wrapper .toc-footer li,
764 | .tocify-wrapper .toc-footer a {
765 | color: #fff;
766 | text-decoration: none;
767 | }
768 | .tocify-wrapper .toc-footer a:hover {
769 | text-decoration: underline;
770 | }
771 | .tocify-wrapper .toc-footer li {
772 | font-size: 0.8em;
773 | line-height: 1.7;
774 | text-decoration: none;
775 | }
776 | #nav-button {
777 | padding: 0 1.5em 5em 0;
778 | display: none;
779 | position: fixed;
780 | top: 0;
781 | left: 0;
782 | z-index: 100;
783 | color: #000;
784 | text-decoration: none;
785 | font-weight: bold;
786 | opacity: 0.7;
787 | line-height: 16px;
788 | transition: left 0.3s ease-in-out;
789 | }
790 | #nav-button span {
791 | display: block;
792 | padding: 6px 6px 6px;
793 | background-color: rgba(234,242,246,0.7);
794 | transform-origin: 0 0;
795 | transform: rotate(-90deg) translate(-100%, 0);
796 | border-radius: 0 0 0 5px;
797 | }
798 | #nav-button img {
799 | height: 16px;
800 | vertical-align: bottom;
801 | }
802 | #nav-button:hover {
803 | opacity: 1;
804 | }
805 | #nav-button.open {
806 | left: 230px;
807 | }
808 | .page-wrapper {
809 | margin-left: 230px;
810 | position: relative;
811 | z-index: 10;
812 | background-color: #eaf2f6;
813 | min-height: 100%;
814 | padding-bottom: 1px;
815 | }
816 | .page-wrapper .dark-box {
817 | width: 50%;
818 | background-color: #393939;
819 | position: absolute;
820 | right: 0;
821 | top: 0;
822 | bottom: 0;
823 | }
824 | .page-wrapper .lang-selector {
825 | position: fixed;
826 | z-index: 50;
827 | border-bottom: 5px solid #393939;
828 | }
829 | .lang-selector {
830 | background-color: #222;
831 | width: 100%;
832 | font-weight: bold;
833 | }
834 | .lang-selector a {
835 | display: block;
836 | float: left;
837 | color: #fff;
838 | text-decoration: none;
839 | padding: 0 10px;
840 | line-height: 30px;
841 | outline: 0;
842 | }
843 | .lang-selector a:active,
844 | .lang-selector a:focus {
845 | background-color: #111;
846 | color: #fff;
847 | }
848 | .lang-selector a.active {
849 | background-color: #393939;
850 | color: #fff;
851 | }
852 | .lang-selector:after {
853 | content: '';
854 | clear: both;
855 | display: block;
856 | }
857 | .content {
858 | position: relative;
859 | z-index: 30;
860 | }
861 | .content:after {
862 | content: '';
863 | display: block;
864 | clear: both;
865 | }
866 | .content > h1,
867 | .content > h2,
868 | .content > h3,
869 | .content > h4,
870 | .content > h5,
871 | .content > h6,
872 | .content > p,
873 | .content > table,
874 | .content > ul,
875 | .content > ol,
876 | .content > aside,
877 | .content > dl {
878 | margin-right: 50%;
879 | padding: 0 28px;
880 | box-sizing: border-box;
881 | display: block;
882 | }
883 | .content > ul,
884 | .content > ol {
885 | padding-left: 43px;
886 | }
887 | .content > h1,
888 | .content > h2,
889 | .content > div {
890 | clear: both;
891 | }
892 | .content h1 {
893 | font-size: 30px;
894 | padding-top: 0.5em;
895 | padding-bottom: 0.5em;
896 | border-bottom: 1px solid #ccc;
897 | margin-bottom: 21px;
898 | margin-top: 2em;
899 | border-top: 1px solid #ddd;
900 | background-image: linear-gradient(to bottom, #fff, #f9f9f9);
901 | }
902 | .content h1:first-child,
903 | .content div:first-child + h1 {
904 | border-top-width: 0;
905 | margin-top: 0;
906 | }
907 | .content h2 {
908 | font-size: 20px;
909 | margin-top: 4em;
910 | margin-bottom: 0;
911 | border-top: 1px solid #ccc;
912 | padding-top: 1.2em;
913 | padding-bottom: 1.2em;
914 | background-image: linear-gradient(to bottom, rgba(255,255,255,0.4), rgba(255,255,255,0));
915 | }
916 | .content h1 + h2,
917 | .content h1 + div + h2 {
918 | margin-top: -21px;
919 | border-top: none;
920 | }
921 | .content h3,
922 | .content h4,
923 | .content h5,
924 | .content h6 {
925 | font-size: 15px;
926 | margin-top: 2.5em;
927 | margin-bottom: 0.8em;
928 | }
929 | .content h4,
930 | .content h5,
931 | .content h6 {
932 | font-size: 10px;
933 | }
934 | .content hr {
935 | margin: 2em 0;
936 | border-top: 2px solid #393939;
937 | border-bottom: 2px solid #eaf2f6;
938 | }
939 | .content table {
940 | margin-bottom: 1em;
941 | overflow: auto;
942 | }
943 | .content table th,
944 | .content table td {
945 | text-align: left;
946 | vertical-align: top;
947 | line-height: 1.6;
948 | }
949 | .content table th {
950 | padding: 5px 10px;
951 | border-bottom: 1px solid #ccc;
952 | vertical-align: bottom;
953 | }
954 | .content table td {
955 | padding: 10px;
956 | }
957 | .content table tr:last-child {
958 | border-bottom: 1px solid #ccc;
959 | }
960 | .content table tr:nth-child(odd) > td {
961 | background-color: #ebf3f6;
962 | }
963 | .content table tr:nth-child(even) > td {
964 | background-color: #ebf2f6;
965 | }
966 | .content dt {
967 | font-weight: bold;
968 | }
969 | .content dd {
970 | margin-left: 15px;
971 | }
972 | .content p,
973 | .content li,
974 | .content dt,
975 | .content dd {
976 | line-height: 1.6;
977 | margin-top: 0;
978 | }
979 | .content img {
980 | max-width: 100%;
981 | }
982 | .content code {
983 | background-color: rgba(0,0,0,0.05);
984 | padding: 3px;
985 | border-radius: 3px;
986 | }
987 | .content pre>code {
988 | background-color: transparent;
989 | padding: 0;
990 | }
991 | .content aside {
992 | padding-top: 1em;
993 | padding-bottom: 1em;
994 | #a0c6da
995 | margin-top: 1.5em;
996 | margin-bottom: 1.5em;
997 | background: #8fbcd4;
998 | line-height: 1.6;
999 | }
1000 | .content aside.warning {
1001 | background-color: #c97a7e;
1002 | #d18e91
1003 | }
1004 | .content aside.success {
1005 | background-color: #6ac174;
1006 | #80ca89
1007 | }
1008 | .content aside:before {
1009 | vertical-align: middle;
1010 | padding-right: 0.5em;
1011 | font-size: 14px;
1012 | }
1013 | .content .search-highlight {
1014 | padding: 2px;
1015 | margin: -2px;
1016 | border-radius: 4px;
1017 | border: 1px solid #f7e633;
1018 | background: linear-gradient(to top left, #f7e633 0%, #f1d32f 100%);
1019 | }
1020 | .content pre,
1021 | .content blockquote {
1022 | background-color: #292929;
1023 | color: #fff;
1024 | padding: 2em 28px;
1025 | margin: 0;
1026 | width: 50%;
1027 | float: right;
1028 | clear: right;
1029 | box-sizing: border-box;
1030 | rgba(0,0,0,0.4)
1031 | }
1032 | .content pre > p,
1033 | .content blockquote > p {
1034 | margin: 0;
1035 | }
1036 | .content pre a,
1037 | .content blockquote a {
1038 | color: #fff;
1039 | text-decoration: none;
1040 | border-bottom: dashed 1px #ccc;
1041 | }
1042 | .content blockquote > p {
1043 | background-color: #1c1c1c;
1044 | border-radius: 5px;
1045 | padding: 13px;
1046 | color: #ccc;
1047 | border-top: 1px solid #000;
1048 | border-bottom: 1px solid #404040;
1049 | }
1050 | @media (max-width: 930px) {
1051 | .tocify-wrapper {
1052 | left: -230px;
1053 | }
1054 | .tocify-wrapper.open {
1055 | left: 0;
1056 | }
1057 | .page-wrapper {
1058 | margin-left: 0;
1059 | }
1060 | #nav-button {
1061 | display: block;
1062 | }
1063 | .tocify-wrapper .tocify-item > a {
1064 | padding-top: 0.3em;
1065 | padding-bottom: 0.3em;
1066 | }
1067 | }
1068 | @media (max-width: 700px) {
1069 | .dark-box {
1070 | display: none;
1071 | }
1072 | .tocify-wrapper .lang-selector {
1073 | display: block;
1074 | }
1075 | .page-wrapper .lang-selector {
1076 | display: none;
1077 | }
1078 | .content h1,
1079 | .content h2,
1080 | .content h3,
1081 | .content h4,
1082 | .content h5,
1083 | .content h6,
1084 | .content p,
1085 | .content table,
1086 | .content ul,
1087 | .content ol,
1088 | .content aside,
1089 | .content dl {
1090 | margin-right: 0;
1091 | }
1092 | .content pre,
1093 | .content blockquote {
1094 | float: none;
1095 | width: auto;
1096 | }
1097 | }
1098 |
--------------------------------------------------------------------------------
/db.json:
--------------------------------------------------------------------------------
1 | {
2 | "characters": [
3 | {
4 | "id": 1,
5 | "name": "Bigby Wolf",
6 | "species": "Wolf",
7 | "gender": "Male",
8 | "occupation": "Fabletown Sherrif",
9 | "hairColour": "Mahogany",
10 | "eyeColour": "Brown",
11 | "description": "The renowned Big Bad Wolf. He's known for tormenting pigs and girls in red hoods, but is trying to put those dark days behind him. Bigby now acts as Fabletown's sheriff and remains in his human form, mostly. However, due to his rough past, the citizens of Fabletown are slow to trust him. Bigby is determined to show that he's truly changed, but some instincts are just too hard to control",
12 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/2/2b/CW_Bigby_Nerissa_Convo.png/revision/latest?cb=20140710174447"
13 | },
14 | {
15 | "id": 2,
16 | "name": "Winter Wolf",
17 | "species": "Wolf",
18 | "gender": "Female",
19 | "occupation": "Unknown",
20 | "hairColour": "White",
21 | "eyeColour": "Unknown",
22 | "description": "Winter was the mother of Bigby Wolf and his brothers. Winter was a good and caring mother and Bigby regards her very highly and was displeased that his father, Mr. North, left her. Despite that, Winter loved and was devoted to him even to the moment of her death.",
23 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/e/e2/Winter_Wolf.png/revision/latest?cb=20140808191333"
24 | },
25 | {
26 | "id": 3,
27 | "name": "Snow White",
28 | "species": "Human",
29 | "gender": "Female",
30 | "occupation": "Fabletown Deputy Mayor",
31 | "hairColour": "Black",
32 | "eyeColour": "Blue",
33 | "description": "Snow White may seem cold, but this stems from her life of mistreatment and abuse back in the Homelands. After escaping assault and imprisonment, not to mention an attempt on her life, she married Prince Charming. It wasn't long before Snow discovered that Charming cheated on her with her estranged sister, Rose Red, and she divorced him. After the Exodus, Snow focused her attention on setting up a safe haven for Fables in the New World. She now serves as Assistant to the Deputy Mayor of Fabletown.",
34 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/7/74/WD_E32013_PORTRAITS_snow_5uupawjwjx_.jpg/revision/latest?cb=20180528022235"
35 | },
36 | {
37 | "id": 4,
38 | "name": "Ichabod Crane",
39 | "species": "Human",
40 | "gender": "Male",
41 | "occupation": "Fabletown Former Deputy Mayor",
42 | "hairColour": "Grey",
43 | "eyeColour": "Green",
44 | "description": "Hailing from the haunted town of Sleepy Hollow, Ichabod Crane has been Deputy Mayor of Fabletown for nearly 115 years. Crane is a bundle of nerves and takes his job very seriously. Though that doesn't mean he always does it well. As one of Fabletown's elite, Crane is often blind to the troubles of the less well-off citizens. Overall, Crane is authoritarian, cowardly, and always hiding something.",
45 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/5/58/SAM_Crane_Driving.png/revision/latest/scale-to-width-down/250?cb=20140407201736"
46 | },
47 | {
48 | "id": 5,
49 | "name": "Bufkin",
50 | "species": "Monkey",
51 | "gender": "Male",
52 | "occupation": "Librarian",
53 | "hairColour": "Green",
54 | "eyeColour": "Black",
55 | "description": "Bufkin is the talking, winged monkey from the land of Oz. Now, as Fabletown's librarian, he spends his time reading and stealing the deputy mayor's booze. He's prone to mischief, so when something goes wrong he assumes he'll receive the lion's share of the blame. He's helpful when he wants to be, but most of the time he'd rather be drinking. Someone would have fired him a long time ago, but he's the only one who can make sense of the filing system.",
56 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/d/dc/ISC_Bufkin_Mirror.png/revision/latest/scale-to-width-down/250?cb=20140527224740"
57 | },
58 | {
59 | "id": 6,
60 | "name": "Magic Mirror",
61 | "species": "Unknown",
62 | "gender": "Male",
63 | "occupation": "Unknown",
64 | "hairColour": "Bald",
65 | "eyeColour": "Black",
66 | "description": "The Magic Mirror speaks mostly in rhyme and demands that others do the same. He also requires the name of whatever object or person you wish to find. If you follow these rules, the mirror will show you a glimpse of whatever you want to see, but nothing more.",
67 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/e/ec/FTH_Mirror.png/revision/latest/scale-to-width-down/250?cb=20140331004413"
68 | },
69 | {
70 | "id": 7,
71 | "name": "The Woodsman",
72 | "species": "Human",
73 | "gender": "Male",
74 | "occupation": "Unknown",
75 | "hairColour": "Brown",
76 | "eyeColour": "Green",
77 | "description": "The Woodsman is one of the few men who went toe to toe with Bigby in his Black Forest days and lived to tell the tale. In an attempt to save Little Red Riding Hood, he split the great wolf's belly open with his axe, filled him full of rocks, and threw the beast into a river. To his dismay his popularity faded. Even his name is forgotten, and he is only known as The Woodsman.",
78 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/4/47/ISC_Woody_Angry.png/revision/latest/scale-to-width-down/250?cb=20140528030339"
79 | },
80 | {
81 | "id": 8,
82 | "name": "Mr. Toad",
83 | "species": "Toad",
84 | "gender": "Male",
85 | "occupation": "Tenement Superintendent",
86 | "hairColour": "None",
87 | "eyeColour": "Green",
88 | "description": "Mr. Toad is the superintendent for a defunct tenement on the edge of Fabletown proper. Because he's a three and a half foot talking amphibian, Toad is required by Fabletown law to keep his family and himself magically glamoured to appear human. The problem is Toad isn't too concerned with what the law is, and has to be reminded often.",
89 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/7/7c/CW_Toad_Truck.png/revision/latest/scale-to-width-down/250?cb=20140709085854"
90 | },
91 | {
92 | "id": 9,
93 | "name": "Toad Jr.",
94 | "species": "Toad",
95 | "gender": "Male",
96 | "occupation": "Unknown",
97 | "hairColour": "None",
98 | "eyeColour": "Green",
99 | "description": "Not much is known about Toad Jr. before the events of in Fabletown, other than that after leaving the Homelands, he lives in an apartment with his father, Mr. Toad.",
100 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/5/51/CW_TJ_Leaving.png/revision/latest/scale-to-width-down/250?cb=20140708203540"
101 | },
102 | {
103 | "id": 10,
104 | "name": "Lawrence",
105 | "species": "Human",
106 | "gender": "Male",
107 | "occupation": "Former Prince",
108 | "hairColour": "Black",
109 | "eyeColour": "Blue",
110 | "description": "After escaping the Homelands, Prince Lawrence and his wife, Faith, immediately fell victim to the harsh realities of the mundane world. They moved to New York hoping to find aid in a community of fellow Fables, but without enough money to live in Fabletown they had to settle on an apartment on the outskirts of the neighborhood. Unfortunately that meant they were out of sight and out of mind when it came to government assistance. Their prospects dwindling, Faith left Lawrence to try to make it on her own. Now, without his wife for support, Lawrence struggles to motivate himself and quickly sinks into depression.",
111 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/5/51/CW_Lawrence_Debate.png/revision/latest/scale-to-width-down/250?cb=20140709085922"
112 | },
113 | {
114 | "id": 11,
115 | "name": "Faith",
116 | "species": "Human",
117 | "gender": "Female",
118 | "occupation": "Prostitute",
119 | "hairColour": "Black",
120 | "eyeColour": "Green",
121 | "description": "Faith, otherwise known as Donkeyskin girl, made it through the Exodus from the Homelands with the clothes on her back, her husband, and nothing else. She was once a beautiful princess, happily married to Prince Lawrence of a neighboring kingdom. Her life should have had a happy ending, but the mundane city of New York wasn't kind to her, or her marriage. With no money, Faith found herself turning tricks to make the rent for a cheap apartment on the outskirts of Fabletown. She had a difficult life, but she did what she could to survive in an unfamiliar world.",
122 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/5/59/FTH_Faith_Street.png/revision/latest/scale-to-width-down/250?cb=20140211211628"
123 | },
124 | {
125 | "id": 12,
126 | "name": "Edward",
127 | "species": "Human",
128 | "gender": "Male",
129 | "occupation": "King",
130 | "hairColour": "Unknown",
131 | "eyeColour": "Grey",
132 | "description": "Father of Faith. Edward does not make a physical appearance in The Wolf Among Us. However, during a talk between Bigby and the Magic Mirror, Bigby asks him to show Faith's father, the Magic Mirror proceeds to show Bigby Edward's skeletal remains.",
133 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/2/23/I5_FTWAU_Edward.PNG/revision/latest/scale-to-width-down/250?cb=20150124195615"
134 | },
135 | {
136 | "id": 13,
137 | "name": "Grimble",
138 | "species": "Troll",
139 | "gender": "Male",
140 | "occupation": "Security Guard",
141 | "hairColour": "Black",
142 | "eyeColour": "Green",
143 | "description": "Grimble is a Fable troll who works as a security guard at the Woodland building in Fabletown. Grimble uses glamour to appear human, and is often seen sleeping behind his desk.",
144 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/6/67/FTH_Grimble_Sleeping.png/revision/latest/scale-to-width-down/250?cb=20140504024554"
145 | },
146 | {
147 | "id": 14,
148 | "name": "Cryer",
149 | "species": "Human",
150 | "gender": "Male",
151 | "occupation": "Unknown",
152 | "hairColour": "Ginger",
153 | "eyeColour": "Green",
154 | "description": "Cryer is a civilian in Fabletown",
155 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/5/5d/FTH_Cryer.png/revision/latest/scale-to-width-down/250?cb=20140223003727"
156 | },
157 | {
158 | "id": 15,
159 | "name": "Bluebeard",
160 | "species": "Human",
161 | "gender": "Male",
162 | "occupation": "Former Serial Killer",
163 | "hairColour": "Blue",
164 | "eyeColour": "Tawny",
165 | "description": "Bluebeard managed to escape the Homelands with his riches intact, and continues to be one of the wealthiest Fables in New York. The Fabletown government depends on his generous contributions, and he often uses this influence for his own benefit. As a former serial killer, he claims his days of decapitating his brides are over. But even if he was able to leave his violent ways in the Homelands, that hasn't stopped him from making the occasional trip down Crooked Lane.",
166 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/c/cc/CW_Bluebeard_Debate.png/revision/latest/scale-to-width-down/250?cb=20140709090025"
167 | },
168 | {
169 | "id": 16,
170 | "name": "Dr. Swineheart",
171 | "species": "Human",
172 | "gender": "Male",
173 | "occupation": "Doctor",
174 | "hairColour": "Grey",
175 | "eyeColour": "Blue",
176 | "description": "Dr. Swineheart is the resident Fabletown physician. So skilled in the art of instrumental surgery that he can safely operate on himself, he served as an army medic for years, sometimes using his talents to impress the locals. He currently runs the \"Special Research Section\" of the Knights of Malta Hospital, so named to discourage people for investigating what is actually a reserved, Fables-focused health facility.",
177 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/4/4d/ISC_Swineheart_Stern.png/revision/latest/scale-to-width-down/250?cb=20140601214447"
178 | },
179 | {
180 | "id": 17,
181 | "name": "Beauty",
182 | "species": "Human",
183 | "gender": "Female",
184 | "occupation": "Hotel Clerk",
185 | "hairColour": "Blonde",
186 | "eyeColour": "Blue",
187 | "description": "Beauty and her husband, Beast, once lived in an enchanted castle, but they were forced to flee the Homelands during the Exodus, leaving all of their wealth behind. Now they live in a modest studio in Fabletown, New York. Though times are hard, with Beast working multiple jobs to pay the bills, the couple have the longest lasting relationship of all the Fables.",
188 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/c/c8/CW_Beauty_Trial.png/revision/latest/scale-to-width-down/250?cb=20140708203823"
189 | },
190 | {
191 | "id": 18,
192 | "name": "Beast",
193 | "species": "Beast",
194 | "gender": "Male",
195 | "occupation": "Unknown",
196 | "hairColour": "Brown",
197 | "eyeColour": "Brown",
198 | "description": "Beast and his wife, Beauty, left everything behind when they escaped the Homelands in the Exodus. Without his former wealth, Beast must pick up extra work to make ends meet. He is able to get around Fabletown without a glamour most of the time, but if Beauty gets too angry with him he becomes more beastly by the minute, growing horns and large teeth. Despite the occasional bickering, the two are truly in love and have the longest lasting relationship of anyone in Fabletown.",
199 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/2/2a/CW_Beast_Angry.png/revision/latest/scale-to-width-down/240?cb=20140709085946"
200 | },
201 | {
202 | "id": 19,
203 | "name": "Colin",
204 | "species": "Pig",
205 | "gender": "Male",
206 | "occupation": "None",
207 | "hairColour": "Black",
208 | "eyeColour": "Unknown",
209 | "description": "Colin is better known as one of the Three Little Pigs. Back in the Homelands they were harassed by The Big Bad Wolf, who blew down Colin's house of straw. After the Exodus, Colin and the other Fables who couldn't pass for human were sent to live at The Farm in upstate New York. Unable to stand such a boring life, Colin constantly makes trips down to Fabletown to bother Bigby. He is always caught and sent back to The Farm, but he doesn't let that stop him.",
210 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/9/99/CW_Colin_Beer.png/revision/latest/scale-to-width-down/250?cb=20140708203958"
211 | },
212 | {
213 | "id": 20,
214 | "name": "Flycatcher",
215 | "species": "Human",
216 | "gender": "Male",
217 | "occupation": "Janitor",
218 | "hairColour": "Brown",
219 | "eyeColour": "Green",
220 | "description": "A former prince turned to a frog by a witch, the friendly, genial Flycatcher now carries the nickname as an unsubtle reference to his propensity for catching and eating flies. His wife and their children were brutally murdered back in the Homelands, a fact that he attempts to deny himself by committing to a series of endless tasks and janitorial duties.",
221 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/c/c1/CW_Flycatcher_Hall.png/revision/latest/scale-to-width-down/250?cb=20140709090111"
222 | },
223 | {
224 | "id": 21,
225 | "name": "Tweedle Dee",
226 | "species": "Human",
227 | "gender": "Male",
228 | "occupation": "Private Investigator",
229 | "hairColour": "Brown",
230 | "eyeColour": "Hazel",
231 | "description": "Tweedle Dee is a Fable and the twin brother of Tweedle Dum.",
232 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/5/53/CW_Dee_Lair.png/revision/latest/scale-to-width-down/250?cb=20140709090225"
233 | },
234 | {
235 | "id": 22,
236 | "name": "Tweedle Dum",
237 | "species": "Human",
238 | "gender": "Male",
239 | "occupation": "Private Investigator",
240 | "hairColour": "Brown",
241 | "eyeColour": "Brown",
242 | "description": "Tweedle Dum is a Fable and the twin brother of Tweedle Dee.",
243 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/6/6e/CW_Dum_Lair.png/revision/latest/scale-to-width-down/250?cb=20140824201033"
244 | },
245 | {
246 | "id": 23,
247 | "name": "Grendal",
248 | "species": "Unknown",
249 | "gender": "Male",
250 | "occupation": "Unknown",
251 | "hairColour": "Black",
252 | "eyeColour": "Green",
253 | "description": "Grendel just wants to be left alone. In the old days he terrorized Norse mead halls, but lately he can be found occupying a stool in various quiet, dumpy bars around New York. He hates the noise of the city, but must work there to afford his glamour. Despite his gruff bearing, he's fiercely loyal to those who've learned to offer him the space and silence he deserves. Talking to him is like watching a time bomb tick down, it's only a matter of time.",
254 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/1/19/CW_Gren_Neutral.png/revision/latest/scale-to-width-down/240?cb=20140709191400"
255 | },
256 | {
257 | "id": 24,
258 | "name": "Jack Horner",
259 | "species": "Human",
260 | "gender": "Male",
261 | "occupation": "Unknown",
262 | "hairColour": "Blonde",
263 | "eyeColour": "Blue",
264 | "description": "Jack is always up to something, but he's not nearly as smart as he thinks he is. His plans to get rich quick often backfire, but his confidence never wavers. He thinks he's the most important person in Fabletown, but everyone knows him as a mostly harmless smart ass.",
265 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/d/da/ISC_Jack_Pawn_Shop.png/revision/latest/scale-to-width-down/250?cb=20140607011400"
266 | },
267 | {
268 | "id": 25,
269 | "name": "Holly",
270 | "species": "Troll",
271 | "gender": "Female",
272 | "occupation": "Bar Owner",
273 | "hairColour": "White",
274 | "eyeColour": "Green",
275 | "description": "Holly is a no-nonsense kind of troll, and the owner of the Trip Trap Bar. She's glamoured to appear human, but her patrons know better. Holly takes good care of her regulars, often the downtrodden Fables with little to spare, but she has no patience for the Fabletown government that has done nothing to locate her missing sister.",
276 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/5/58/CW_Holly_Trial.png/revision/latest/scale-to-width-down/240?cb=20140708204204"
277 | },
278 | {
279 | "id": 26,
280 | "name": "Lily",
281 | "species": "Troll",
282 | "gender": "Female",
283 | "occupation": "Stripper, Prostitute",
284 | "hairColour": "Black",
285 | "eyeColour": "Unknown",
286 | "description": "Lily and her sister Holly grew up in the Homelands together, but had a falling out shortly after moving to the mundane world. Aimless and increasingly destitute, Lily turned to prostitution, and now she's the second victim in an ongoing murder investigation.",
287 | "imagePath": "No-image-available"
288 | },
289 | {
290 | "id": 27,
291 | "name": "Kelsey Brannigan",
292 | "species": "Human",
293 | "gender": "Female",
294 | "occupation": "Detective",
295 | "hairColour": "Brown",
296 | "eyeColour": "Green",
297 | "description": "Kelsey Brannigan is a Mundy (normal human) detective working for the New York City Police Department.",
298 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/1/1d/SAM_Brannigan_Questioning.png/revision/latest/scale-to-width-down/250?cb=20140214064105"
299 | },
300 | {
301 | "id": 28,
302 | "name": "Nerissa",
303 | "species": "Human",
304 | "gender": "Female",
305 | "occupation": "Stripper, Prostitute",
306 | "hairColour": "Mahogany",
307 | "eyeColour": "Amber",
308 | "description": "Nerissa's story never had a happy ending. She's known as the Little Mermaid, the young girl who gave up her tail for a pair of legs in the hopes of winning the heart of a handsome prince. When he married a princess instead, Nerissa was left heartbroken. She made the journey to the mundane world hoping for a better life. Now she dances at the Pudding and Pie, but each step she takes feels like walking on shards of glass. She has very little left, but finds some comfort in the company of her fellow dancers.",
309 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/7/7b/CW_Nerissa_Worried.png/revision/latest/scale-to-width-down/250?cb=20140713054418"
310 | },
311 | {
312 | "id": 29,
313 | "name": "Clever Hans",
314 | "species": "Human",
315 | "gender": "Male",
316 | "occupation": "Bouncer, Janitor, Handyman",
317 | "hairColour": "Blonde",
318 | "eyeColour": "Blue",
319 | "description": "Clever Hans always does exactly as he's told. However, he often misunderstands his instructions and ends up hurting himself or behaving oddly, as in the case of his noted fable, where he threw sheep's eyes at his wife. Unsurprisingly, she left him, and now Hans works as a bouncer at Georgie's club. He hopes to dance on stage one day, but for now he's content sweeping up and making sure the crowd doesn't get out of hand.",
320 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/0/03/FTH_Hans_Line.png/revision/latest/scale-to-width-down/250?cb=20140806132301"
321 | },
322 | {
323 | "id": 30,
324 | "name": "Gwen",
325 | "species": "Human",
326 | "gender": "Female",
327 | "occupation": "Prostitute",
328 | "hairColour": "Brown",
329 | "eyeColour": "Amber",
330 | "description": "Gwen is a prostitute at the Pudding & Pie working under Georgie Porgie",
331 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/5/53/Gwen.png/revision/latest/scale-to-width-down/250?cb=20180316035623"
332 | },
333 | {
334 | "id": 31,
335 | "name": "Georgie Porgie",
336 | "species": "Human",
337 | "gender": "Male",
338 | "occupation": "Pimp",
339 | "hairColour": "Black",
340 | "eyeColour": "Grey",
341 | "description": "Georgie runs the Pudding and Pie, a strip club that also caters to the unmentionable desires of Fabletown's citizens. He has tried just about everything there is to try in pursuit of worldly pleasures, but none of it satisfies him for long. He does seem to enjoy pushing people's buttons. He takes pride in his nightclub, and doesn't react well to anyone meddling in his affairs.",
342 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/e/ef/CW_Georgie_Betrayed.png/revision/latest/scale-to-width-down/250?cb=20140710023226"
343 | },
344 | {
345 | "id": 32,
346 | "name": "Vivian",
347 | "species": "Human",
348 | "gender": "Female",
349 | "occupation": "Hostess",
350 | "hairColour": "Blonde",
351 | "eyeColour": "Green",
352 | "description": "Much of Vivian's past is unknown since she prefers not to talk about her life back in the Homelands. She wanted to start fresh in Fabletown, but she finds herself working for Georgie at the Pudding and Pie. It's not a terrible life. Georgie took a liking to Vivian, so he doesn't make her take jobs at the Open Arms. Instead she plays hostess and helps Georgie ensure complete customer satisfaction.",
353 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/1/1b/CW_Vivian_Deciding.png/revision/latest/scale-to-width-down/250?cb=20140708191031"
354 | },
355 | {
356 | "id": 33,
357 | "name": "Aunty Greenleaf",
358 | "species": "Human",
359 | "gender": "Female",
360 | "occupation": "Witch",
361 | "hairColour": "Grey",
362 | "eyeColour": "Brown",
363 | "description": "Horticulturist, alchemist, and lover of animals, Auntie Greenleaf is one of the few rogue witches still living outside of the Thirteenth Floor, unsupervised and unrestricted. Rumored to have lost a daughter in the Homelands, she suffers paranoia and depressive mood swings, and will only venture outside at irregular hours under the guise of an ethereal, white deer, an oft-whispered specter of Brookhaven natives.",
364 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/f/f9/CW_Greenleaf_Woodlands.png/revision/latest/scale-to-width-down/240?cb=20140708205015"
365 | },
366 | {
367 | "id": 34,
368 | "name": "Tiny Tim",
369 | "species": "Human",
370 | "gender": "Male",
371 | "occupation": "Taxi Driver, Doorman",
372 | "hairColour": "Brown",
373 | "eyeColour": "Green",
374 | "description": "While most Fables theorize that their longevity and overall wellbeing is improved by the Mundy world's knowledge of them, for a select few, that does not seem to apply. When a malady or injury is an integral part of a Fable's story, that noteriety can make recovery nearly impossible. That's what Tiny Tim thinks, at least. And no medical care or magic - rather, none that he can afford - can heal his leg.",
375 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/5/50/CW_Tim_Waiting.png/revision/latest/scale-to-width-down/250?cb=20140708205146"
376 | },
377 | {
378 | "id": 35,
379 | "name": "Crooked Man",
380 | "species": "Human",
381 | "gender": "Male",
382 | "occupation": "Crime Lord",
383 | "hairColour": "Brown",
384 | "eyeColour": "Green",
385 | "description": "The Crooked Man has slowly built himself into one of the most powerful figures in Fabletown. His operation started with a crooked sixpence and a crooked house - two things he cared about more than his wife or children, whom he killed rather than let them stand in his way. In his rise, The Crooked Man has ensnared many Fables in his criminal web, providing them with what they need, but always at a high cost. He is cunning, persuasive, and ruthless.",
386 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/b/b8/CW_Crooked_Man_Office.png/revision/latest/scale-to-width-down/240?cb=20140720022122"
387 | },
388 | {
389 | "id": 36,
390 | "name": "Bloody Mary",
391 | "species": "Unknwon",
392 | "gender": "Female",
393 | "occupation": "Crooked Man's Chief Lieutenant",
394 | "hairColour": "Black with Red Streaks",
395 | "eyeColour": "Amber",
396 | "description": "The true history of the person known as \"Bloody Mary\" is almost completely unknown, even to Fables most acquainted with its members. Her name Mary, at least, is not up for contention, nor is her penchant for shocking violence, an inlaid resistance to magic and spells, and a strange ability to use any reflective surface as a portal, effectively short-cutting space and time. Thought by Mundies to be the wailing apparition of a childless ghost, though any evidence of that is as yet unseen.",
397 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/0/06/CW_Mary_Workshop.png/revision/latest/scale-to-width-down/240?cb=20140708205330"
398 | },
399 | {
400 | "id": 37,
401 | "name": "Jersey Devil",
402 | "species": "Cryptid",
403 | "gender": "Male",
404 | "occupation": "Shop Owner",
405 | "hairColour": "Mahogany",
406 | "eyeColour": "Unknown",
407 | "description": "Not all of the Fables who came to this world landed in Fabletown. There are those who scattered across the farthest corners of the Earth. And there are those who simply prefer the Garden State to the Empire State. Such is the Jersey Devil. Reports of its appearance have varied... although most accounts make mention of leathery wings. But an encounter with a certain axe of legend some years ago has temporarily rendered that feature absent.",
408 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/8/85/CW_Jersey_Lair.png/revision/latest/scale-to-width-down/240?cb=20140708205051"
409 | },
410 | {
411 | "id": 38,
412 | "name": "Johann",
413 | "species": "Human",
414 | "gender": "Male",
415 | "occupation": "Butcher",
416 | "hairColour": "Grey",
417 | "eyeColour": "Brown",
418 | "description": "His name is often said in the same breath as that of the Baker and Candlestick Maker of Fabletown. And like those other tradesmen, Johann the Butcher's storefront has served Fabletown for ages: fresh cuts, exotic meats, and even full sides of beef for the vigorous appetites of ogres and trolls. But Johann's business has fallen in with the wrong crowd. As the quality of his products declined, and his business turned into a front operation for the Crooked Man, some have started to wonder if they ever really knew Johann.",
419 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/1/18/CW_Johann_Debate.png/revision/latest/scale-to-width-down/250?cb=20140709090409"
420 | }
421 | ],
422 | "locations": [
423 | {
424 | "id": 1,
425 | "locationName": "New York City",
426 | "description": "Fabletown is situated in New York City",
427 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/4/43/FTH_Manhattan_Skyline.png/revision/latest/scale-to-width-down/1300?cb=20140826020954"
428 | },
429 | {
430 | "id": 2,
431 | "locationName": "Toad's Tenement",
432 | "description": "A tenement building owned by Mr. Toad. The Woodsman owns an apartment in it.",
433 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/b/b5/FTH_Toad%27s_Tenement.png/revision/latest/scale-to-width-down/1300?cb=20140614235651"
434 | },
435 | {
436 | "id": 3,
437 | "locationName": "Woodman's Apartment",
438 | "description": "An apartment in Toad's tenement owned by the Woodsman.",
439 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/4/42/FTH_Woody%27s_Apartment_Daytime.png/revision/latest/scale-to-width-down/1300?cb=20140826021035"
440 | },
441 | {
442 | "id": 4,
443 | "locationName": "Woodland Luxury Apartments",
444 | "description": "The Woodland Luxury Apartments, most often referred to as the Woodlands, is the principal workplace, as well as the residence, of some of the authority figures of Fabletown on the Upper West Side in Manhattan, New York City.",
445 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/c/c6/FTH_Woodlands_Exterior.png/revision/latest/scale-to-width-down/1300?cb=20140614235831"
446 | },
447 | {
448 | "id": 5,
449 | "locationName": "Bigby's Apartment",
450 | "description": "An apartment in the Woodlands, apparently the smallest in Fabletown. Bigby calls it home during the precious little downtime he has.",
451 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/0/09/ISC_Bigby%27s_Apartment.png/revision/latest/scale-to-width-down/1300?cb=20140614235911"
452 | },
453 | {
454 | "id": 6,
455 | "locationName": "Business Office",
456 | "description": "A cavernous room in the Woodlands that is home to the Fabletown government and archives.",
457 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/2/2b/ISC_Back_to_the_Office.png/revision/latest/scale-to-width-down/1300?cb=20140531214254"
458 | },
459 | {
460 | "id": 7,
461 | "locationName": "Lawrence's Apartment",
462 | "description": "An apartment in The Bronx that is home to Lawrence and Faith.",
463 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/7/7a/FTH_Lawrence%27s_Apartment_Exterior.png/revision/latest/scale-to-width-down/1300?cb=20140615000003"
464 | },
465 | {
466 | "id": 8,
467 | "locationName": "Trip Trap",
468 | "description": "A bar that generally serves Fabletown's less fortunate residents. It is owned by Holly and frequented by Grendel and the Woodman.",
469 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/c/c6/FTH_Trip_Trap_Exterior.png/revision/latest/scale-to-width-down/1300?cb=20140405000415"
470 | },
471 | {
472 | "id": 9,
473 | "locationName": "Upper West Side Precinct",
474 | "description": "A NYPD precinct in the Upper West Side that Bigby is brought to after being arrested for entering the crime scene at the Woodlands.",
475 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/6/65/SAM_Questioning_Bigby.png/revision/latest/scale-to-width-down/1300?cb=20140511191712"
476 | },
477 | {
478 | "id": 10,
479 | "locationName": "Woodland's Basement",
480 | "description": "A chamber in the Woodland Luxury Apartments",
481 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/e/eb/SAM_Bluebeard_Dee.png/revision/latest/scale-to-width-down/1300?cb=20140604234346"
482 | },
483 | {
484 | "id": 11,
485 | "locationName": "Witching Well Chamber",
486 | "description": "The Witching Well is a magical well that acts as a portal to the lands of the deceased and was used by Fabletown to dispose of Fable bodies on the assumption that they couldn't return.",
487 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/3/38/SAM_Witching_Well_Chamber.png/revision/latest/scale-to-width-down/1300?cb=20140511195950"
488 | },
489 | {
490 | "id": 12,
491 | "locationName": "Pudding & Pie",
492 | "description": "The Pudding & Pie, sometimes stylized as the Puddin' & Pie or Pudding N' Pie, is a strip club in Fabletown owned by Georgie Porgie and Vivian.",
493 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/8/80/ACM_Pudding_%26_Pie_Exterior.png/revision/latest/scale-to-width-down/1300?cb=20140610212324"
494 | },
495 | {
496 | "id": 13,
497 | "locationName": "Open Arms Hotel",
498 | "description": "A rundown establishment down the road from the Pudding & Pie, it is used by its girls for prostitution appointments. It also also Beauty's current place of employment.",
499 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/0/07/SAM_Open_Arms_Exterior.png/revision/latest/scale-to-width-down/1300?cb=20140506000416"
500 | },
501 | {
502 | "id": 14,
503 | "locationName": "Buckingham Bridge",
504 | "description": "A bridge connecting Manhattan and Brooklyn. Lily's funeral is held beneath it.",
505 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/3/31/ACM_Manhattan_Bridge.png/revision/latest/scale-to-width-down/1300?cb=20140416210818"
506 | },
507 | {
508 | "id": 15,
509 | "locationName": "Crane's Apartment",
510 | "description": "A penthouse in the Woodlands that was home to Ichabod Crane.",
511 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/4/46/ACM_Crane%27s_Apartment_Ransacked.png/revision/latest/scale-to-width-down/1300?cb=20140416214218"
512 | },
513 | {
514 | "id": 16,
515 | "locationName": "Tweedles' Office",
516 | "description": "An office that the Tweedle brothers operate their private investigation business out of. It is maintained by Flycatcher.",
517 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/8/83/ACM_Tweedle_Office.png/revision/latest/scale-to-width-down/1300?cb=20140416214409"
518 | },
519 | {
520 | "id": 17,
521 | "locationName": "Aunty Greenleaf's Apartment",
522 | "description": "An apartment in the outskirts of Fabletown that Aunty Greenleaf lives in.",
523 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/7/7f/ACM_Greenleaf%27s_Apartment.png/revision/latest/scale-to-width-down/1300?cb=20140416215442"
524 | },
525 | {
526 | "id": 18,
527 | "locationName": "Bigby's Office",
528 | "description": "The Woodlands' security office, and Bigby's base of operations as sheriff.",
529 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/6/6f/ISC_Comfort.png/revision/latest/scale-to-width-down/1300?cb=20140531211054"
530 | },
531 | {
532 | "id": 19,
533 | "locationName": "Beauty and Beast's Apartment",
534 | "description": "A lavish apartment in the Woodlands that Beauty and Beast live in.",
535 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/2/2d/ISC_Fishing_for_Sympathy.png/revision/latest/scale-to-width-down/1300?cb=20140531212737"
536 | },
537 | {
538 | "id": 20,
539 | "locationName": "Lucky Pawn",
540 | "description": "Jersey Devil's pawn shop next door to the Trip Trap. It is a front for the Crooked Man's criminal enterprise.",
541 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/f/f8/ISC_Lucky_Pawn_Exterior.png/revision/latest/scale-to-width-down/1300?cb=20140531212738"
542 | },
543 | {
544 | "id": 21,
545 | "locationName": "The Cut Above",
546 | "description": "Johann's butcher shop. It was taken over by the Crooked Man's crew and is now used to create and ship black market magic and glamours.",
547 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/9/94/ISC_Cut_Above_Interior.png/revision/latest/scale-to-width-down/1300?cb=20140531213818"
548 | },
549 | {
550 | "id": 22,
551 | "locationName": "Central Park",
552 | "description": "A large urban park in the center of Manhattan. Bigby finds the door to the Crooked Man's hideout under the Gothic Bridge within the park.",
553 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/c/c2/FTH_Central_Park.png/revision/latest/scale-to-width-down/1300?cb=20140826021233"
554 | },
555 | {
556 | "id": 23,
557 | "locationName": "The Crooked Lair",
558 | "description": "The Crooked Lair is the headquarters for the Crooked Man and his criminal enterprise. Located inside a desanctified church, the only way to enter it is through a magic door that constantly moves around Fabletown. The entrance is manned by Tiny Tim.",
559 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/7/78/ISC_Crooked_Lair.png/revision/latest/scale-to-width-down/1300?cb=20140703051430"
560 | },
561 | {
562 | "id": 24,
563 | "locationName": "Sheppard Metalworks",
564 | "description": "Shepard Metalworks is an old foundry located in the outskirts of Fabletown. It is used by the Crooked Man as a storage location for information about the various Fables he has observed over the years.",
565 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/f/ff/CW_Sheppard_Metalworks.png/revision/latest/scale-to-width-down/1300?cb=20140709053211"
566 | }
567 | ],
568 | "episodes": [
569 | {
570 | "id": 1,
571 | "episodeName": "Faith",
572 | "episodeNum": 1,
573 | "season": 1,
574 | "description": "Bigby Wolf, Sheriff of Fabletown, must work a murder case when a Fable is killed and he must work with Snow White to find out who the murderer is. The two of them work together and the investigation goes deep, with a shocking ending.",
575 | "directedBy": "Nick Herman & Dennis Lenart",
576 | "writtenBy": "Pierre Shorette & Ryan Kaufman",
577 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/b/b6/FTH_Cover.png/revision/latest?cb=20140214011436"
578 | },
579 | {
580 | "id": 2,
581 | "episodeName": "Smoke & Mirrors",
582 | "episodeNum": 2,
583 | "season": 1,
584 | "description": "The episode starts with Bigby being questioned by a female detective named Kelsey Brannigan. Bigby is reluctant to answer her questions. Suddenly, the detective and police officers in the room pass out, and it is shown that Crane had used a memory-wipe spell to let Bigby out.",
585 | "directedBy": "Jason Latino",
586 | "writtenBy": "Dave Grossman & Joe Pinney",
587 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/a/a2/SAM_Cover.png/revision/latest?cb=20140214011612"
588 | },
589 | {
590 | "id": 3,
591 | "episodeName": "A Crooked Mile",
592 | "episodeNum": 3,
593 | "season": 1,
594 | "description": "Bigby has discovered that Crane is the murderer; a photo of him in bed with a murdered woman glamoured to look like Snow was found. He sees Beauty and Beast off to arrive at Lily's funeral, where Holly, Grendel, Prince Lawrence, Vivian and Nerissa are all attending. Eventually, Snow returns to the funeral and asks Bigby to wait at a distance.",
595 | "directedBy": " Martin Montgomery",
596 | "writtenBy": "Adam Hines & Ryan Kaufman",
597 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/0/03/ACM_Cover.png/revision/latest/?cb=20140214012342"
598 | },
599 | {
600 | "id": 4,
601 | "episodeName": "In Sheep's Clothing",
602 | "episodeNum": 4,
603 | "season": 1,
604 | "description": "In a nightmare sequence, Bigby is about to be axed to death by Bloody Mary; he violently awakes to find himself being operated on by Doctor Swineheart, with Colin and Snow watching. The Silver Bullet Mary shot Bigby with in the previous episode is Bigby's key weakness requiring an important healing practice.",
605 | "directedBy": "Kent Mudle",
606 | "writtenBy": "Dane Martin & Aaron Casillas",
607 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/1/18/ISC_Cover.png/revision/latest?cb=20140311212241"
608 | },
609 | {
610 | "id": 5,
611 | "episodeName": "Cry Wolf",
612 | "episodeNum": 5,
613 | "season": 1,
614 | "description": "Bigby stands before the Crooked Man and his crew consisting of Dum (if he was spared) and Dee, Georgie Porgie and Vivian, and the Jersey Devil. Bigby can either be rude and threatening to The Crooked Man or accept his hospitality. The henchman are nervous about Bigby's presence, and Bigby observes the Crooked Man's use of scare tactics to enforce his rule. When Bigby brings up the murders, he can either ask The Crooked Man outright who did it, or take a guess at the culprit himself.",
615 | "directedBy": "Vahram Antonian",
616 | "writtenBy": "Nicole Martinez & Joe Pinney",
617 | "imagePath": "https://vignette.wikia.nocookie.net/fables/images/5/5e/CW_Cover.png/revision/latest?cb=20140403210906"
618 | }
619 | ]
620 | }
--------------------------------------------------------------------------------
/public/js/lib/jquery.tocify.js:
--------------------------------------------------------------------------------
1 | /* jquery Tocify - v1.8.0 - 2013-09-16
2 | * http://www.gregfranko.com/jquery.tocify.js/
3 | * Copyright (c) 2013 Greg Franko; Licensed MIT
4 | * Modified lightly by Robert Lord to fix a bug I found,
5 | * and also so it adds ids to headers
6 | * also because I want height caching, since the
7 | * height lookup for h1s and h2s was causing serious
8 | * lag spikes below 30 fps */
9 |
10 | // Immediately-Invoked Function Expression (IIFE) [Ben Alman Blog Post](http://benalman.com/news/2010/11/immediately-invoked-function-expression/) that calls another IIFE that contains all of the plugin logic. I used this pattern so that anyone viewing this code would not have to scroll to the bottom of the page to view the local parameters that were passed to the main IIFE.
11 | (function(tocify) {
12 |
13 | // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
14 | "use strict";
15 |
16 | // Calls the second IIFE and locally passes in the global jQuery, window, and document objects
17 | tocify(window.jQuery, window, document);
18 |
19 | }
20 |
21 | // Locally passes in `jQuery`, the `window` object, the `document` object, and an `undefined` variable. The `jQuery`, `window` and `document` objects are passed in locally, to improve performance, since javascript first searches for a variable match within the local variables set before searching the global variables set. All of the global variables are also passed in locally to be minifier friendly. `undefined` can be passed in locally, because it is not a reserved word in JavaScript.
22 | (function($, window, document, undefined) {
23 |
24 | // ECMAScript 5 Strict Mode: [John Resig Blog Post](http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/)
25 | "use strict";
26 |
27 | var tocClassName = "tocify",
28 | tocClass = "." + tocClassName,
29 | tocFocusClassName = "tocify-focus",
30 | tocHoverClassName = "tocify-hover",
31 | hideTocClassName = "tocify-hide",
32 | hideTocClass = "." + hideTocClassName,
33 | headerClassName = "tocify-header",
34 | headerClass = "." + headerClassName,
35 | subheaderClassName = "tocify-subheader",
36 | subheaderClass = "." + subheaderClassName,
37 | itemClassName = "tocify-item",
38 | itemClass = "." + itemClassName,
39 | extendPageClassName = "tocify-extend-page",
40 | extendPageClass = "." + extendPageClassName;
41 |
42 | // Calling the jQueryUI Widget Factory Method
43 | $.widget("toc.tocify", {
44 |
45 | //Plugin version
46 | version: "1.8.0",
47 |
48 | // These options will be used as defaults
49 | options: {
50 |
51 | // **context**: Accepts String: Any jQuery selector
52 | // The container element that holds all of the elements used to generate the table of contents
53 | context: "body",
54 |
55 | // **ignoreSelector**: Accepts String: Any jQuery selector
56 | // A selector to any element that would be matched by selectors that you wish to be ignored
57 | ignoreSelector: null,
58 |
59 | // **selectors**: Accepts an Array of Strings: Any jQuery selectors
60 | // The element's used to generate the table of contents. The order is very important since it will determine the table of content's nesting structure
61 | selectors: "h1, h2, h3",
62 |
63 | // **showAndHide**: Accepts a boolean: true or false
64 | // Used to determine if elements should be shown and hidden
65 | showAndHide: true,
66 |
67 | // **showEffect**: Accepts String: "none", "fadeIn", "show", or "slideDown"
68 | // Used to display any of the table of contents nested items
69 | showEffect: "slideDown",
70 |
71 | // **showEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
72 | // The time duration of the show animation
73 | showEffectSpeed: "medium",
74 |
75 | // **hideEffect**: Accepts String: "none", "fadeOut", "hide", or "slideUp"
76 | // Used to hide any of the table of contents nested items
77 | hideEffect: "slideUp",
78 |
79 | // **hideEffectSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
80 | // The time duration of the hide animation
81 | hideEffectSpeed: "medium",
82 |
83 | // **smoothScroll**: Accepts a boolean: true or false
84 | // Determines if a jQuery animation should be used to scroll to specific table of contents items on the page
85 | smoothScroll: true,
86 |
87 | // **smoothScrollSpeed**: Accepts Number (milliseconds) or String: "slow", "medium", or "fast"
88 | // The time duration of the smoothScroll animation
89 | smoothScrollSpeed: "medium",
90 |
91 | // **scrollTo**: Accepts Number (pixels)
92 | // The amount of space between the top of page and the selected table of contents item after the page has been scrolled
93 | scrollTo: 0,
94 |
95 | // **showAndHideOnScroll**: Accepts a boolean: true or false
96 | // Determines if table of contents nested items should be shown and hidden while scrolling
97 | showAndHideOnScroll: true,
98 |
99 | // **highlightOnScroll**: Accepts a boolean: true or false
100 | // Determines if table of contents nested items should be highlighted (set to a different color) while scrolling
101 | highlightOnScroll: true,
102 |
103 | // **highlightOffset**: Accepts a number
104 | // The offset distance in pixels to trigger the next active table of contents item
105 | highlightOffset: 40,
106 |
107 | // **theme**: Accepts a string: "bootstrap", "jqueryui", or "none"
108 | // Determines if Twitter Bootstrap, jQueryUI, or Tocify classes should be added to the table of contents
109 | theme: "bootstrap",
110 |
111 | // **extendPage**: Accepts a boolean: true or false
112 | // If a user scrolls to the bottom of the page and the page is not tall enough to scroll to the last table of contents item, then the page height is increased
113 | extendPage: true,
114 |
115 | // **extendPageOffset**: Accepts a number: pixels
116 | // How close to the bottom of the page a user must scroll before the page is extended
117 | extendPageOffset: 100,
118 |
119 | // **history**: Accepts a boolean: true or false
120 | // Adds a hash to the page url to maintain history
121 | history: true,
122 |
123 | // **scrollHistory**: Accepts a boolean: true or false
124 | // Adds a hash to the page url, to maintain history, when scrolling to a TOC item
125 | scrollHistory: false,
126 |
127 | // **hashGenerator**: How the hash value (the anchor segment of the URL, following the
128 | // # character) will be generated.
129 | //
130 | // "compact" (default) - #CompressesEverythingTogether
131 | // "pretty" - #looks-like-a-nice-url-and-is-easily-readable
132 | // function(text, element){} - Your own hash generation function that accepts the text as an
133 | // argument, and returns the hash value.
134 | hashGenerator: "compact",
135 |
136 | // **highlightDefault**: Accepts a boolean: true or false
137 | // Set's the first TOC item as active if no other TOC item is active.
138 | highlightDefault: true
139 |
140 | },
141 |
142 | // _Create
143 | // -------
144 | // Constructs the plugin. Only called once.
145 | _create: function() {
146 |
147 | var self = this;
148 |
149 | self.tocifyWrapper = $('.tocify-wrapper');
150 | self.extendPageScroll = true;
151 |
152 | // Internal array that keeps track of all TOC items (Helps to recognize if there are duplicate TOC item strings)
153 | self.items = [];
154 |
155 | // Generates the HTML for the dynamic table of contents
156 | self._generateToc();
157 |
158 | // Caches heights and anchors
159 | self.cachedHeights = [],
160 | self.cachedAnchors = [];
161 |
162 | // Adds CSS classes to the newly generated table of contents HTML
163 | self._addCSSClasses();
164 |
165 | self.webkit = (function() {
166 |
167 | for(var prop in window) {
168 |
169 | if(prop) {
170 |
171 | if(prop.toLowerCase().indexOf("webkit") !== -1) {
172 |
173 | return true;
174 |
175 | }
176 |
177 | }
178 |
179 | }
180 |
181 | return false;
182 |
183 | }());
184 |
185 | // Adds jQuery event handlers to the newly generated table of contents
186 | self._setEventHandlers();
187 |
188 | // Binding to the Window load event to make sure the correct scrollTop is calculated
189 | $(window).load(function() {
190 |
191 | // Sets the active TOC item
192 | self._setActiveElement(true);
193 |
194 | // Once all animations on the page are complete, this callback function will be called
195 | $("html, body").promise().done(function() {
196 |
197 | setTimeout(function() {
198 |
199 | self.extendPageScroll = false;
200 |
201 | },0);
202 |
203 | });
204 |
205 | });
206 |
207 | },
208 |
209 | // _generateToc
210 | // ------------
211 | // Generates the HTML for the dynamic table of contents
212 | _generateToc: function() {
213 |
214 | // _Local variables_
215 |
216 | // Stores the plugin context in the self variable
217 | var self = this,
218 |
219 | // All of the HTML tags found within the context provided (i.e. body) that match the top level jQuery selector above
220 | firstElem,
221 |
222 | // Instantiated variable that will store the top level newly created unordered list DOM element
223 | ul,
224 | ignoreSelector = self.options.ignoreSelector;
225 |
226 | // If the selectors option has a comma within the string
227 | if(this.options.selectors.indexOf(",") !== -1) {
228 |
229 | // Grabs the first selector from the string
230 | firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,"").substr(0, this.options.selectors.indexOf(",")));
231 |
232 | }
233 |
234 | // If the selectors option does not have a comman within the string
235 | else {
236 |
237 | // Grabs the first selector from the string and makes sure there are no spaces
238 | firstElem = $(this.options.context).find(this.options.selectors.replace(/ /g,""));
239 |
240 | }
241 |
242 | if(!firstElem.length) {
243 |
244 | self.element.addClass(hideTocClassName);
245 |
246 | return;
247 |
248 | }
249 |
250 | self.element.addClass(tocClassName);
251 |
252 | // Loops through each top level selector
253 | firstElem.each(function(index) {
254 |
255 | //If the element matches the ignoreSelector then we skip it
256 | if($(this).is(ignoreSelector)) {
257 | return;
258 | }
259 |
260 | // Creates an unordered list HTML element and adds a dynamic ID and standard class name
261 | ul = $("
", {
262 | "id": headerClassName + index,
263 | "class": headerClassName
264 | }).
265 |
266 | // Appends a top level list item HTML element to the previously created HTML header
267 | append(self._nestElements($(this), index));
268 |
269 | // Add the created unordered list element to the HTML element calling the plugin
270 | self.element.append(ul);
271 |
272 | // Finds all of the HTML tags between the header and subheader elements
273 | $(this).nextUntil(this.nodeName.toLowerCase()).each(function() {
274 |
275 | // If there are no nested subheader elemements
276 | if($(this).find(self.options.selectors).length === 0) {
277 |
278 | // Loops through all of the subheader elements
279 | $(this).filter(self.options.selectors).each(function() {
280 |
281 | //If the element matches the ignoreSelector then we skip it
282 | if($(this).is(ignoreSelector)) {
283 | return;
284 | }
285 |
286 | self._appendSubheaders.call(this, self, ul);
287 |
288 | });
289 |
290 | }
291 |
292 | // If there are nested subheader elements
293 | else {
294 |
295 | // Loops through all of the subheader elements
296 | $(this).find(self.options.selectors).each(function() {
297 |
298 | //If the element matches the ignoreSelector then we skip it
299 | if($(this).is(ignoreSelector)) {
300 | return;
301 | }
302 |
303 | self._appendSubheaders.call(this, self, ul);
304 |
305 | });
306 |
307 | }
308 |
309 | });
310 |
311 | });
312 |
313 | },
314 |
315 | _setActiveElement: function(pageload) {
316 |
317 | var self = this,
318 |
319 | hash = window.location.hash.substring(1),
320 |
321 | elem = self.element.find("li[data-unique='" + hash + "']");
322 |
323 | if(hash.length) {
324 |
325 | // Removes highlighting from all of the list item's
326 | self.element.find("." + self.focusClass).removeClass(self.focusClass);
327 |
328 | // Highlights the current list item that was clicked
329 | elem.addClass(self.focusClass);
330 |
331 | // If the showAndHide option is true
332 | if(self.options.showAndHide) {
333 |
334 | // Triggers the click event on the currently focused TOC item
335 | elem.click();
336 |
337 | }
338 |
339 | }
340 |
341 | else {
342 |
343 | // Removes highlighting from all of the list item's
344 | self.element.find("." + self.focusClass).removeClass(self.focusClass);
345 |
346 | if(!hash.length && pageload && self.options.highlightDefault) {
347 |
348 | // Highlights the first TOC item if no other items are highlighted
349 | self.element.find(itemClass).first().addClass(self.focusClass);
350 |
351 | }
352 |
353 | }
354 |
355 | return self;
356 |
357 | },
358 |
359 | // _nestElements
360 | // -------------
361 | // Helps create the table of contents list by appending nested list items
362 | _nestElements: function(self, index) {
363 |
364 | var arr, item, hashValue;
365 |
366 | arr = $.grep(this.items, function (item) {
367 |
368 | return item === self.text();
369 |
370 | });
371 |
372 | // If there is already a duplicate TOC item
373 | if(arr.length) {
374 |
375 | // Adds the current TOC item text and index (for slight randomization) to the internal array
376 | this.items.push(self.text() + index);
377 |
378 | }
379 |
380 | // If there not a duplicate TOC item
381 | else {
382 |
383 | // Adds the current TOC item text to the internal array
384 | this.items.push(self.text());
385 |
386 | }
387 |
388 | hashValue = this._generateHashValue(arr, self, index);
389 |
390 | // ADDED BY ROBERT
391 | // actually add the hash value to the element's id
392 | // self.attr("id", "link-" + hashValue);
393 |
394 | // Appends a list item HTML element to the last unordered list HTML element found within the HTML element calling the plugin
395 | item = $("
", {
396 |
397 | // Sets a common class name to the list item
398 | "class": itemClassName,
399 |
400 | "data-unique": hashValue
401 |
402 | }).append($("
", {
403 |
404 | "text": self.text()
405 |
406 | }));
407 |
408 | // Adds an HTML anchor tag before the currently traversed HTML element
409 | self.before($("
", {
410 |
411 | // Sets a name attribute on the anchor tag to the text of the currently traversed HTML element (also making sure that all whitespace is replaced with an underscore)
412 | "name": hashValue,
413 |
414 | "data-unique": hashValue
415 |
416 | }));
417 |
418 | return item;
419 |
420 | },
421 |
422 | // _generateHashValue
423 | // ------------------
424 | // Generates the hash value that will be used to refer to each item.
425 | _generateHashValue: function(arr, self, index) {
426 |
427 | var hashValue = "",
428 | hashGeneratorOption = this.options.hashGenerator;
429 |
430 | if (hashGeneratorOption === "pretty") {
431 | // remove weird characters
432 |
433 |
434 | // prettify the text
435 | hashValue = self.text().toLowerCase().replace(/\s/g, "-");
436 |
437 | // ADDED BY ROBERT
438 | // remove weird characters
439 | hashValue = hashValue.replace(/[^\x00-\x7F]/g, "");
440 |
441 | // fix double hyphens
442 | while (hashValue.indexOf("--") > -1) {
443 | hashValue = hashValue.replace(/--/g, "-");
444 | }
445 |
446 | // fix colon-space instances
447 | while (hashValue.indexOf(":-") > -1) {
448 | hashValue = hashValue.replace(/:-/g, "-");
449 | }
450 |
451 | } else if (typeof hashGeneratorOption === "function") {
452 |
453 | // call the function
454 | hashValue = hashGeneratorOption(self.text(), self);
455 |
456 | } else {
457 |
458 | // compact - the default
459 | hashValue = self.text().replace(/\s/g, "");
460 |
461 | }
462 |
463 | // add the index if we need to
464 | if (arr.length) { hashValue += ""+index; }
465 |
466 | // return the value
467 | return hashValue;
468 |
469 | },
470 |
471 | // _appendElements
472 | // ---------------
473 | // Helps create the table of contents list by appending subheader elements
474 |
475 | _appendSubheaders: function(self, ul) {
476 |
477 | // The current element index
478 | var index = $(this).index(self.options.selectors),
479 |
480 | // Finds the previous header DOM element
481 | previousHeader = $(self.options.selectors).eq(index - 1),
482 |
483 | currentTagName = +$(this).prop("tagName").charAt(1),
484 |
485 | previousTagName = +previousHeader.prop("tagName").charAt(1),
486 |
487 | lastSubheader;
488 |
489 | // If the current header DOM element is smaller than the previous header DOM element or the first subheader
490 | if(currentTagName < previousTagName) {
491 |
492 | // Selects the last unordered list HTML found within the HTML element calling the plugin
493 | self.element.find(subheaderClass + "[data-tag=" + currentTagName + "]").last().append(self._nestElements($(this), index));
494 |
495 | }
496 |
497 | // If the current header DOM element is the same type of header(eg. h4) as the previous header DOM element
498 | else if(currentTagName === previousTagName) {
499 |
500 | ul.find(itemClass).last().after(self._nestElements($(this), index));
501 |
502 | }
503 |
504 | else {
505 |
506 | // Selects the last unordered list HTML found within the HTML element calling the plugin
507 | ul.find(itemClass).last().
508 |
509 | // Appends an unorderedList HTML element to the dynamic `unorderedList` variable and sets a common class name
510 | after($("
", {
511 |
512 | "class": subheaderClassName,
513 |
514 | "data-tag": currentTagName
515 |
516 | })).next(subheaderClass).
517 |
518 | // Appends a list item HTML element to the last unordered list HTML element found within the HTML element calling the plugin
519 | append(self._nestElements($(this), index));
520 | }
521 |
522 | },
523 |
524 | // _setEventHandlers
525 | // ----------------
526 | // Adds jQuery event handlers to the newly generated table of contents
527 | _setEventHandlers: function() {
528 |
529 | // _Local variables_
530 |
531 | // Stores the plugin context in the self variable
532 | var self = this,
533 |
534 | // Instantiates a new variable that will be used to hold a specific element's context
535 | $self,
536 |
537 | // Instantiates a new variable that will be used to determine the smoothScroll animation time duration
538 | duration;
539 |
540 | // Event delegation that looks for any clicks on list item elements inside of the HTML element calling the plugin
541 | this.element.on("click.tocify", "li", function(event) {
542 |
543 | if(self.options.history) {
544 |
545 | window.location.hash = $(this).attr("data-unique");
546 |
547 | }
548 |
549 | // Removes highlighting from all of the list item's
550 | self.element.find("." + self.focusClass).removeClass(self.focusClass);
551 |
552 | // Highlights the current list item that was clicked
553 | $(this).addClass(self.focusClass);
554 |
555 | // If the showAndHide option is true
556 | if(self.options.showAndHide) {
557 |
558 | var elem = $('li[data-unique="' + $(this).attr("data-unique") + '"]');
559 |
560 | self._triggerShow(elem);
561 |
562 | }
563 |
564 | self._scrollTo($(this));
565 |
566 | });
567 |
568 | // Mouseenter and Mouseleave event handlers for the list item's within the HTML element calling the plugin
569 | this.element.find("li").on({
570 |
571 | // Mouseenter event handler
572 | "mouseenter.tocify": function() {
573 |
574 | // Adds a hover CSS class to the current list item
575 | $(this).addClass(self.hoverClass);
576 |
577 | // Makes sure the cursor is set to the pointer icon
578 | $(this).css("cursor", "pointer");
579 |
580 | },
581 |
582 | // Mouseleave event handler
583 | "mouseleave.tocify": function() {
584 |
585 | if(self.options.theme !== "bootstrap") {
586 |
587 | // Removes the hover CSS class from the current list item
588 | $(this).removeClass(self.hoverClass);
589 |
590 | }
591 |
592 | }
593 | });
594 |
595 | // Reset height cache on scroll
596 |
597 | $(window).on('resize', function() {
598 | self.calculateHeights();
599 | });
600 |
601 | // Window scroll event handler
602 | $(window).on("scroll.tocify", function() {
603 |
604 | // Once all animations on the page are complete, this callback function will be called
605 | $("html, body").promise().done(function() {
606 |
607 | // Local variables
608 |
609 | // Stores how far the user has scrolled
610 | var winScrollTop = $(window).scrollTop(),
611 |
612 | // Stores the height of the window
613 | winHeight = $(window).height(),
614 |
615 | // Stores the height of the document
616 | docHeight = $(document).height(),
617 |
618 | scrollHeight = $("body")[0].scrollHeight,
619 |
620 | // Instantiates a variable that will be used to hold a selected HTML element
621 | elem,
622 |
623 | lastElem,
624 |
625 | lastElemOffset,
626 |
627 | currentElem;
628 |
629 | if(self.options.extendPage) {
630 |
631 | // If the user has scrolled to the bottom of the page and the last toc item is not focused
632 | if((self.webkit && winScrollTop >= scrollHeight - winHeight - self.options.extendPageOffset) || (!self.webkit && winHeight + winScrollTop > docHeight - self.options.extendPageOffset)) {
633 |
634 | if(!$(extendPageClass).length) {
635 |
636 | lastElem = $('div[data-unique="' + $(itemClass).last().attr("data-unique") + '"]');
637 |
638 | if(!lastElem.length) return;
639 |
640 | // Gets the top offset of the page header that is linked to the last toc item
641 | lastElemOffset = lastElem.offset().top;
642 |
643 | // Appends a div to the bottom of the page and sets the height to the difference of the window scrollTop and the last element's position top offset
644 | $(self.options.context).append($("
", {
645 |
646 | "class": extendPageClassName,
647 |
648 | "height": Math.abs(lastElemOffset - winScrollTop) + "px",
649 |
650 | "data-unique": extendPageClassName
651 |
652 | }));
653 |
654 | if(self.extendPageScroll) {
655 |
656 | currentElem = self.element.find('li.active');
657 |
658 | self._scrollTo($("div[data-unique=" + currentElem.attr("data-unique") + "]"));
659 |
660 | }
661 |
662 | }
663 |
664 | }
665 |
666 | }
667 |
668 | // The zero timeout ensures the following code is run after the scroll events
669 | setTimeout(function() {
670 |
671 | // _Local variables_
672 |
673 | // Stores the distance to the closest anchor
674 | var // Stores the index of the closest anchor
675 | closestAnchorIdx = null,
676 | anchorText;
677 |
678 | // if never calculated before, calculate and cache the heights
679 | if (self.cachedHeights.length == 0) {
680 | self.calculateHeights();
681 | }
682 |
683 | var scrollTop = $(window).scrollTop();
684 |
685 | // Determines the index of the closest anchor
686 | self.cachedAnchors.each(function(idx) {
687 | if (self.cachedHeights[idx] - scrollTop < 0) {
688 | closestAnchorIdx = idx;
689 | } else {
690 | return false;
691 | }
692 | });
693 |
694 | anchorText = $(self.cachedAnchors[closestAnchorIdx]).attr("data-unique");
695 |
696 | // Stores the list item HTML element that corresponds to the currently traversed anchor tag
697 | elem = $('li[data-unique="' + anchorText + '"]');
698 |
699 | // If the `highlightOnScroll` option is true and a next element is found
700 | if(self.options.highlightOnScroll && elem.length && !elem.hasClass(self.focusClass)) {
701 |
702 | // Removes highlighting from all of the list item's
703 | self.element.find("." + self.focusClass).removeClass(self.focusClass);
704 |
705 | // Highlights the corresponding list item
706 | elem.addClass(self.focusClass);
707 |
708 | // Scroll to highlighted element's header
709 | var tocifyWrapper = self.tocifyWrapper;
710 | var scrollToElem = $(elem).closest('.tocify-header');
711 |
712 | var elementOffset = scrollToElem.offset().top,
713 | wrapperOffset = tocifyWrapper.offset().top;
714 | var offset = elementOffset - wrapperOffset;
715 |
716 | if (offset >= $(window).height()) {
717 | var scrollPosition = offset + tocifyWrapper.scrollTop();
718 | tocifyWrapper.scrollTop(scrollPosition);
719 | } else if (offset < 0) {
720 | tocifyWrapper.scrollTop(0);
721 | }
722 | }
723 |
724 | if(self.options.scrollHistory) {
725 |
726 | // IF STATEMENT ADDED BY ROBERT
727 |
728 | if(window.location.hash !== "#" + anchorText && anchorText !== undefined) {
729 |
730 | if(history.replaceState) {
731 | history.replaceState({}, "", "#" + anchorText);
732 | // provide a fallback
733 | } else {
734 | scrollV = document.body.scrollTop;
735 | scrollH = document.body.scrollLeft;
736 | location.hash = "#" + anchorText;
737 | document.body.scrollTop = scrollV;
738 | document.body.scrollLeft = scrollH;
739 | }
740 |
741 | }
742 |
743 | }
744 |
745 | // If the `showAndHideOnScroll` option is true
746 | if(self.options.showAndHideOnScroll && self.options.showAndHide) {
747 |
748 | self._triggerShow(elem, true);
749 |
750 | }
751 |
752 | }, 0);
753 |
754 | });
755 |
756 | });
757 |
758 | },
759 |
760 | // calculateHeights
761 | // ----
762 | // ADDED BY ROBERT
763 | calculateHeights: function() {
764 | var self = this;
765 | self.cachedHeights = [];
766 | self.cachedAnchors = [];
767 | var anchors = $(self.options.context).find("div[data-unique]");
768 | anchors.each(function(idx) {
769 | var distance = (($(this).next().length ? $(this).next() : $(this)).offset().top - self.options.highlightOffset);
770 | self.cachedHeights[idx] = distance;
771 | });
772 | self.cachedAnchors = anchors;
773 | },
774 |
775 | // Show
776 | // ----
777 | // Opens the current sub-header
778 | show: function(elem, scroll) {
779 |
780 | // Stores the plugin context in the `self` variable
781 | var self = this,
782 | element = elem;
783 |
784 | // If the sub-header is not already visible
785 | if (!elem.is(":visible")) {
786 |
787 | // If the current element does not have any nested subheaders, is not a header, and its parent is not visible
788 | if(!elem.find(subheaderClass).length && !elem.parent().is(headerClass) && !elem.parent().is(":visible")) {
789 |
790 | // Sets the current element to all of the subheaders within the current header
791 | elem = elem.parents(subheaderClass).add(elem);
792 |
793 | }
794 |
795 | // If the current element does not have any nested subheaders and is not a header
796 | else if(!elem.children(subheaderClass).length && !elem.parent().is(headerClass)) {
797 |
798 | // Sets the current element to the closest subheader
799 | elem = elem.closest(subheaderClass);
800 |
801 | }
802 |
803 | //Determines what jQuery effect to use
804 | switch (self.options.showEffect) {
805 |
806 | //Uses `no effect`
807 | case "none":
808 |
809 | elem.show();
810 |
811 | break;
812 |
813 | //Uses the jQuery `show` special effect
814 | case "show":
815 |
816 | elem.show(self.options.showEffectSpeed);
817 |
818 | break;
819 |
820 | //Uses the jQuery `slideDown` special effect
821 | case "slideDown":
822 |
823 | elem.slideDown(self.options.showEffectSpeed);
824 |
825 | break;
826 |
827 | //Uses the jQuery `fadeIn` special effect
828 | case "fadeIn":
829 |
830 | elem.fadeIn(self.options.showEffectSpeed);
831 |
832 | break;
833 |
834 | //If none of the above options were passed, then a `jQueryUI show effect` is expected
835 | default:
836 |
837 | elem.show();
838 |
839 | break;
840 |
841 | }
842 |
843 | }
844 |
845 | // If the current subheader parent element is a header
846 | if(elem.parent().is(headerClass)) {
847 |
848 | // Hides all non-active sub-headers
849 | self.hide($(subheaderClass).not(elem));
850 |
851 | }
852 |
853 | // If the current subheader parent element is not a header
854 | else {
855 |
856 | // Hides all non-active sub-headers
857 | self.hide($(subheaderClass).not(elem.closest(headerClass).find(subheaderClass).not(elem.siblings())));
858 |
859 | }
860 |
861 | // Maintains chainablity
862 | return self;
863 |
864 | },
865 |
866 | // Hide
867 | // ----
868 | // Closes the current sub-header
869 | hide: function(elem) {
870 |
871 | // Stores the plugin context in the `self` variable
872 | var self = this;
873 |
874 | //Determines what jQuery effect to use
875 | switch (self.options.hideEffect) {
876 |
877 | // Uses `no effect`
878 | case "none":
879 |
880 | elem.hide();
881 |
882 | break;
883 |
884 | // Uses the jQuery `hide` special effect
885 | case "hide":
886 |
887 | elem.hide(self.options.hideEffectSpeed);
888 |
889 | break;
890 |
891 | // Uses the jQuery `slideUp` special effect
892 | case "slideUp":
893 |
894 | elem.slideUp(self.options.hideEffectSpeed);
895 |
896 | break;
897 |
898 | // Uses the jQuery `fadeOut` special effect
899 | case "fadeOut":
900 |
901 | elem.fadeOut(self.options.hideEffectSpeed);
902 |
903 | break;
904 |
905 | // If none of the above options were passed, then a `jqueryUI hide effect` is expected
906 | default:
907 |
908 | elem.hide();
909 |
910 | break;
911 |
912 | }
913 |
914 | // Maintains chainablity
915 | return self;
916 | },
917 |
918 | // _triggerShow
919 | // ------------
920 | // Determines what elements get shown on scroll and click
921 | _triggerShow: function(elem, scroll) {
922 |
923 | var self = this;
924 |
925 | // If the current element's parent is a header element or the next element is a nested subheader element
926 | if(elem.parent().is(headerClass) || elem.next().is(subheaderClass)) {
927 |
928 | // Shows the next sub-header element
929 | self.show(elem.next(subheaderClass), scroll);
930 |
931 | }
932 |
933 | // If the current element's parent is a subheader element
934 | else if(elem.parent().is(subheaderClass)) {
935 |
936 | // Shows the parent sub-header element
937 | self.show(elem.parent(), scroll);
938 |
939 | }
940 |
941 | // Maintains chainability
942 | return self;
943 |
944 | },
945 |
946 | // _addCSSClasses
947 | // --------------
948 | // Adds CSS classes to the newly generated table of contents HTML
949 | _addCSSClasses: function() {
950 |
951 | // If the user wants a jqueryUI theme
952 | if(this.options.theme === "jqueryui") {
953 |
954 | this.focusClass = "ui-state-default";
955 |
956 | this.hoverClass = "ui-state-hover";
957 |
958 | //Adds the default styling to the dropdown list
959 | this.element.addClass("ui-widget").find(".toc-title").addClass("ui-widget-header").end().find("li").addClass("ui-widget-content");
960 |
961 | }
962 |
963 | // If the user wants a twitterBootstrap theme
964 | else if(this.options.theme === "bootstrap") {
965 |
966 | this.element.find(headerClass + "," + subheaderClass).addClass("nav nav-list");
967 |
968 | this.focusClass = "active";
969 |
970 | }
971 |
972 | // If a user does not want a prebuilt theme
973 | else {
974 |
975 | // Adds more neutral classes (instead of jqueryui)
976 |
977 | this.focusClass = tocFocusClassName;
978 |
979 | this.hoverClass = tocHoverClassName;
980 |
981 | }
982 |
983 | //Maintains chainability
984 | return this;
985 |
986 | },
987 |
988 | // setOption
989 | // ---------
990 | // Sets a single Tocify option after the plugin is invoked
991 | setOption: function() {
992 |
993 | // Calls the jQueryUI Widget Factory setOption method
994 | $.Widget.prototype._setOption.apply(this, arguments);
995 |
996 | },
997 |
998 | // setOptions
999 | // ----------
1000 | // Sets a single or multiple Tocify options after the plugin is invoked
1001 | setOptions: function() {
1002 |
1003 | // Calls the jQueryUI Widget Factory setOptions method
1004 | $.Widget.prototype._setOptions.apply(this, arguments);
1005 |
1006 | },
1007 |
1008 | // _scrollTo
1009 | // ---------
1010 | // Scrolls to a specific element
1011 | _scrollTo: function(elem) {
1012 |
1013 | var self = this,
1014 | duration = self.options.smoothScroll || 0,
1015 | scrollTo = self.options.scrollTo;
1016 |
1017 | // Once all animations on the page are complete, this callback function will be called
1018 | $("html, body").promise().done(function() {
1019 |
1020 | // Animates the html and body element scrolltops
1021 | $("html, body").animate({
1022 |
1023 | // Sets the jQuery `scrollTop` to the top offset of the HTML div tag that matches the current list item's `data-unique` tag
1024 | "scrollTop": $('div[data-unique="' + elem.attr("data-unique") + '"]').next().offset().top - ($.isFunction(scrollTo) ? scrollTo.call() : scrollTo) + "px"
1025 |
1026 | }, {
1027 |
1028 | // Sets the smoothScroll animation time duration to the smoothScrollSpeed option
1029 | "duration": duration
1030 |
1031 | });
1032 |
1033 | });
1034 |
1035 | // Maintains chainability
1036 | return self;
1037 |
1038 | }
1039 |
1040 | });
1041 |
1042 | })); //end of plugin
1043 |
--------------------------------------------------------------------------------