├── .gitignore
├── .npmrc
├── Dockerfile
├── LICENSE
├── NOTICE
├── README.md
├── config.fuseki.js
├── config.js
├── config.sparql.js
├── data
├── public
│ ├── css
│ │ ├── bootstrap.css
│ │ └── custom.css
│ ├── img
│ │ ├── synapta-logo.svg
│ │ └── zazuko-logo.png
│ ├── js
│ │ ├── bootstrap.js
│ │ ├── jquery.js
│ │ ├── jsonld.js
│ │ ├── react.js
│ │ ├── render-ld.js
│ │ ├── search.js
│ │ └── sticky.js
│ ├── rdf2h
│ │ ├── matchers.ttl
│ │ └── site-matchers.ttl
│ ├── search
│ │ └── index.html
│ └── sparql
│ │ └── index.html
├── scripts
│ ├── fuseki-config.ttl
│ └── start-fuseki
├── sparql
│ └── search.sparql
└── templates
│ ├── 404.html
│ └── graph.html
├── index.js
├── lib
├── abstract-store.js
├── file-store.js
├── graph-split.js
├── handler-middleware.js
├── ldp-module-handler.js
├── patch-headers-middleware.js
├── render-html-middleware.js
├── sparql-handler.js
├── sparql-proxy.js
└── sparql-search.js
├── logo.svg
├── package.json
└── server.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 | npm-debug.log
4 | /nbproject/private/
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | tag-version-prefix=""
2 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:6.7-onbuild
2 | EXPOSE 8080
3 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Trifid-LD - Lightweight Linked Data Server and Proxy
2 | Copyright 2015 Zazuko GmbH
3 | Author: Thomas Bergwinkl, Adrian Gschwend
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | With the release of version 1.0 Trifid-LD was renamed to Trifid. Please switch your Trifid-LD installations to Trifid, you can find the latest release in its new repository at
2 | https://github.com/zazuko/trifid
3 |
--------------------------------------------------------------------------------
/config.fuseki.js:
--------------------------------------------------------------------------------
1 | /* global rdf:false */
2 |
3 | 'use strict';
4 |
5 | var
6 | fs = require('fs'),
7 | path = require('path');
8 |
9 |
10 | var buildQuery = function (iri) {
11 | return 'DESCRIBE <' + iri + '>';
12 | };
13 |
14 | var buildExistsQuery = function (iri) {
15 | return 'ASK { <' + iri + '> ?p ?o }';
16 | };
17 |
18 | var patchResponseHeaders = function (res, headers) {
19 | if (res.statusCode === 200) {
20 | // clean existings values
21 | var fieldList = [
22 | 'Access-Control-Allow-Origin',
23 | 'Cache-Control',
24 | 'Fuseki-Request-ID',
25 | 'Server',
26 | 'Vary'];
27 |
28 | if (res._headers) {
29 | fieldList.forEach(function (field) {
30 | if (field in res._headers) {
31 | delete res._headers[field];
32 | }
33 |
34 | if (field.toLowerCase() in res._headers) {
35 | delete res._headers[field.toLowerCase()];
36 | }
37 | });
38 | }
39 |
40 | // cors header
41 | headers['Access-Control-Allow-Origin'] = '*';
42 |
43 | // cache header
44 | headers['Cache-Control'] = 'public, max-age=120';
45 |
46 | // vary header
47 | headers['Vary'] = 'Accept';
48 | }
49 |
50 | return headers;
51 | };
52 |
53 | module.exports = {
54 | app: 'trifid-ld',
55 | logger: {
56 | level: 'debug'
57 | },
58 | listener: {
59 | port: 8080
60 | },
61 | expressSettings: {
62 | 'trust proxy': 'loopback',
63 | 'x-powered-by': null
64 | },
65 | patchHeaders: {
66 | patchResponse: patchResponseHeaders
67 | },
68 | sparqlProxy: {
69 | path: '/sparql',
70 | options: {
71 | /*authentication: {
72 | user: 'user',
73 | password: 'password'
74 | },*/
75 | endpointUrl:'http://localhost:3030/tbbt/sparql'
76 | }
77 | },
78 | sparqlSearch: {
79 | path: '/query',
80 | options: {
81 | endpointUrl:'http://localhost:3030/tbbt/sparql',
82 | resultsPerPage: 5,
83 | queryTemplate: fs.readFileSync(path.join(__dirname, 'data/sparql/search.sparql')).toString(),
84 | variables: {
85 | 'q': {
86 | variable: '%searchstring%',
87 | required: true
88 | }
89 | }
90 | }
91 | },
92 | HandlerClass: require('./lib/sparql-handler'),
93 | handlerOptions: {
94 | endpointUrl: 'http://localhost:3030/tbbt/sparql',
95 | buildQuery: buildQuery,
96 | buildExistsQuery: buildExistsQuery
97 | }
98 | };
99 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | /* global rdf:false */
2 |
3 | 'use strict';
4 |
5 |
6 | global.rdf = require('rdf-ext')(rdf);
7 |
8 |
9 | var
10 | fs = require('fs'),
11 | graphSplit = require('./lib/graph-split')(rdf),
12 | url = require('url');
13 |
14 |
15 | var init = function () {
16 | var config = this;
17 |
18 | var importGraph = function (filename) {
19 | return new Promise(function (resolve) {
20 | rdf.parseTurtle(fs.readFileSync(filename).toString(), function (graph) {
21 | resolve(graph);
22 | });
23 | });
24 | };
25 |
26 | return Promise.all([
27 | importGraph('./node_modules/tbbt-ld/dist/tbbt.nt')
28 | ]).then(function (graphs) {
29 | var
30 | mergedGraph = rdf.createGraph(),
31 | searchNs = 'http://localhost:8080',
32 | replaceNs = url.format({
33 | protocol: 'http:',
34 | hostname: config.hostname,
35 | port: config.port || '',
36 | pathname: config.path || ''
37 | });
38 |
39 | graphs.forEach(function (graph) {
40 | // map namespace to listener config
41 | graph = rdf.utils.mapNamespaceGraph(graph, searchNs, replaceNs);
42 |
43 | mergedGraph.addAll(graph);
44 | });
45 |
46 | config.handlerOptions.storeOptions = {
47 | graph: mergedGraph,
48 | split: rdf.utils.splitGraphByNamedNodeSubject
49 | };
50 |
51 | return Promise.resolve();
52 | });
53 | };
54 |
55 | var patchResponseHeaders = function (res, headers) {
56 | if (res.statusCode === 200) {
57 | // clean existings values
58 | var fieldList = [
59 | 'Access-Control-Allow-Origin',
60 | 'Cache-Control',
61 | 'Fuseki-Request-ID',
62 | 'Server',
63 | 'Vary'];
64 |
65 | if (res._headers) {
66 | fieldList.forEach(function (field) {
67 | if (field in res._headers) {
68 | delete res._headers[field];
69 | }
70 |
71 | if (field.toLowerCase() in res._headers) {
72 | delete res._headers[field.toLowerCase()];
73 | }
74 | });
75 | }
76 |
77 | // cors header
78 | headers['Access-Control-Allow-Origin'] = '*';
79 |
80 | // cache header
81 | headers['Cache-Control'] = 'public, max-age=120';
82 |
83 | // vary header
84 | headers['Vary'] = 'Accept';
85 | }
86 |
87 | return headers;
88 | };
89 |
90 | module.exports = {
91 | app: 'trifid-ld',
92 | logger: {
93 | level: 'debug'
94 | },
95 | // public interface visible after any reverse proxies
96 | hostname: 'localhost',
97 | port: 8080,
98 | path: '',
99 | // listener
100 | listener: {
101 | hostname: '',
102 | port: 8080
103 | },
104 | expressSettings: {
105 | 'trust proxy': 'loopback',
106 | 'x-powered-by': null
107 | },
108 | patchHeaders: {
109 | patchResponse: patchResponseHeaders
110 | },
111 | init: init,
112 | HandlerClass: require('./lib/ldp-module-handler'),
113 | handlerOptions: {
114 | rdf: rdf,
115 | StoreClass: graphSplit.SplitStore
116 | }
117 | };
118 |
--------------------------------------------------------------------------------
/config.sparql.js:
--------------------------------------------------------------------------------
1 | var baseConfig = require('./config.fuseki');
2 | var defaultsDeep = require('lodash/defaultsDeep');
3 |
4 | var config = {
5 | listener: {
6 | port: 8080
7 | },
8 | handlerOptions: {
9 | endpointUrl: 'http://localhost:3030/tbbt/sparql'
10 | }
11 | };
12 |
13 | module.exports = defaultsDeep(config, baseConfig);
14 |
--------------------------------------------------------------------------------
/data/public/css/custom.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-size: 1em;
3 | }
4 | #header {
5 | background-color: #53BBF4;
6 | color: #fff;
7 | width: 100%;
8 | font-size: 2em;
9 | padding: 1em 3em 1em 1em;
10 | }
11 | #footer {
12 | background-color: #4298B5;
13 | color: #fff;
14 | font-size: 0.9em;
15 | width: 100%;
16 | text-align: center;
17 | padding: 2em 3em 2em 1em;
18 | }
19 | hr.dashed {
20 | border-top: 1px dashed #8c8b8b;
21 | width: 5%;
22 | margin-right:100%;
23 | }
24 | hr.baffone {
25 | height: 30px;
26 | border-style: solid;
27 | border-color: #8c8b8b;
28 | border-width: 1px 0 0 0;
29 | border-radius: 20px;
30 | }
31 | hr.baffone:before {
32 | display: block;
33 | content: "";
34 | height: 30px;
35 | margin-top: -31px;
36 | border-style: solid;
37 | border-color: #8c8b8b;
38 | border-width: 0 0 1px 0;
39 | border-radius: 20px;
40 | }
41 | .moving {
42 | padding-top: 100px;
43 | margin-top: -100px;
44 | }
45 |
46 | /* STICKY */
47 | #sticky.stick {
48 | margin-top: 0 !important;
49 | position: fixed;
50 | top: 0;
51 | z-index: 10000;
52 | }
53 |
54 |
55 | /* CONTAINER */
56 | .container {
57 | width: 100%;
58 | padding: 2% 2% 2% 2%;
59 | }
60 |
61 | .LOD {
62 | background-color: #28abe3;
63 | color: #FFF;
64 | font-size: 2em;
65 | width: 100%;
66 | padding: 1em 3em 1em 2em;
67 | }
68 |
--------------------------------------------------------------------------------
/data/public/img/synapta-logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
29 |
--------------------------------------------------------------------------------
/data/public/img/zazuko-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zazukoians/trifid-ld/cd9f5d261a8beadd9195466ea11df0ed6c0d6bae/data/public/img/zazuko-logo.png
--------------------------------------------------------------------------------
/data/public/js/bootstrap.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v3.3.2 (http://getbootstrap.com)
3 | * Copyright 2011-2015 Twitter, Inc.
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 |
7 | if (typeof jQuery === 'undefined') {
8 | throw new Error('Bootstrap\'s JavaScript requires jQuery')
9 | }
10 |
11 | +function ($) {
12 | 'use strict';
13 | var version = $.fn.jquery.split(' ')[0].split('.')
14 | if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) {
15 | throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher')
16 | }
17 | }(jQuery);
18 |
19 | /* ========================================================================
20 | * Bootstrap: transition.js v3.3.2
21 | * http://getbootstrap.com/javascript/#transitions
22 | * ========================================================================
23 | * Copyright 2011-2015 Twitter, Inc.
24 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
25 | * ======================================================================== */
26 |
27 |
28 | +function ($) {
29 | 'use strict';
30 |
31 | // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
32 | // ============================================================
33 |
34 | function transitionEnd() {
35 | var el = document.createElement('bootstrap')
36 |
37 | var transEndEventNames = {
38 | WebkitTransition : 'webkitTransitionEnd',
39 | MozTransition : 'transitionend',
40 | OTransition : 'oTransitionEnd otransitionend',
41 | transition : 'transitionend'
42 | }
43 |
44 | for (var name in transEndEventNames) {
45 | if (el.style[name] !== undefined) {
46 | return { end: transEndEventNames[name] }
47 | }
48 | }
49 |
50 | return false // explicit for ie8 ( ._.)
51 | }
52 |
53 | // http://blog.alexmaccaw.com/css-transitions
54 | $.fn.emulateTransitionEnd = function (duration) {
55 | var called = false
56 | var $el = this
57 | $(this).one('bsTransitionEnd', function () { called = true })
58 | var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
59 | setTimeout(callback, duration)
60 | return this
61 | }
62 |
63 | $(function () {
64 | $.support.transition = transitionEnd()
65 |
66 | if (!$.support.transition) return
67 |
68 | $.event.special.bsTransitionEnd = {
69 | bindType: $.support.transition.end,
70 | delegateType: $.support.transition.end,
71 | handle: function (e) {
72 | if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
73 | }
74 | }
75 | })
76 |
77 | }(jQuery);
78 |
79 | /* ========================================================================
80 | * Bootstrap: alert.js v3.3.2
81 | * http://getbootstrap.com/javascript/#alerts
82 | * ========================================================================
83 | * Copyright 2011-2015 Twitter, Inc.
84 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
85 | * ======================================================================== */
86 |
87 |
88 | +function ($) {
89 | 'use strict';
90 |
91 | // ALERT CLASS DEFINITION
92 | // ======================
93 |
94 | var dismiss = '[data-dismiss="alert"]'
95 | var Alert = function (el) {
96 | $(el).on('click', dismiss, this.close)
97 | }
98 |
99 | Alert.VERSION = '3.3.2'
100 |
101 | Alert.TRANSITION_DURATION = 150
102 |
103 | Alert.prototype.close = function (e) {
104 | var $this = $(this)
105 | var selector = $this.attr('data-target')
106 |
107 | if (!selector) {
108 | selector = $this.attr('href')
109 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
110 | }
111 |
112 | var $parent = $(selector)
113 |
114 | if (e) e.preventDefault()
115 |
116 | if (!$parent.length) {
117 | $parent = $this.closest('.alert')
118 | }
119 |
120 | $parent.trigger(e = $.Event('close.bs.alert'))
121 |
122 | if (e.isDefaultPrevented()) return
123 |
124 | $parent.removeClass('in')
125 |
126 | function removeElement() {
127 | // detach from parent, fire event then clean up data
128 | $parent.detach().trigger('closed.bs.alert').remove()
129 | }
130 |
131 | $.support.transition && $parent.hasClass('fade') ?
132 | $parent
133 | .one('bsTransitionEnd', removeElement)
134 | .emulateTransitionEnd(Alert.TRANSITION_DURATION) :
135 | removeElement()
136 | }
137 |
138 |
139 | // ALERT PLUGIN DEFINITION
140 | // =======================
141 |
142 | function Plugin(option) {
143 | return this.each(function () {
144 | var $this = $(this)
145 | var data = $this.data('bs.alert')
146 |
147 | if (!data) $this.data('bs.alert', (data = new Alert(this)))
148 | if (typeof option == 'string') data[option].call($this)
149 | })
150 | }
151 |
152 | var old = $.fn.alert
153 |
154 | $.fn.alert = Plugin
155 | $.fn.alert.Constructor = Alert
156 |
157 |
158 | // ALERT NO CONFLICT
159 | // =================
160 |
161 | $.fn.alert.noConflict = function () {
162 | $.fn.alert = old
163 | return this
164 | }
165 |
166 |
167 | // ALERT DATA-API
168 | // ==============
169 |
170 | $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
171 |
172 | }(jQuery);
173 |
174 | /* ========================================================================
175 | * Bootstrap: button.js v3.3.2
176 | * http://getbootstrap.com/javascript/#buttons
177 | * ========================================================================
178 | * Copyright 2011-2015 Twitter, Inc.
179 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
180 | * ======================================================================== */
181 |
182 |
183 | +function ($) {
184 | 'use strict';
185 |
186 | // BUTTON PUBLIC CLASS DEFINITION
187 | // ==============================
188 |
189 | var Button = function (element, options) {
190 | this.$element = $(element)
191 | this.options = $.extend({}, Button.DEFAULTS, options)
192 | this.isLoading = false
193 | }
194 |
195 | Button.VERSION = '3.3.2'
196 |
197 | Button.DEFAULTS = {
198 | loadingText: 'loading...'
199 | }
200 |
201 | Button.prototype.setState = function (state) {
202 | var d = 'disabled'
203 | var $el = this.$element
204 | var val = $el.is('input') ? 'val' : 'html'
205 | var data = $el.data()
206 |
207 | state = state + 'Text'
208 |
209 | if (data.resetText == null) $el.data('resetText', $el[val]())
210 |
211 | // push to event loop to allow forms to submit
212 | setTimeout($.proxy(function () {
213 | $el[val](data[state] == null ? this.options[state] : data[state])
214 |
215 | if (state == 'loadingText') {
216 | this.isLoading = true
217 | $el.addClass(d).attr(d, d)
218 | } else if (this.isLoading) {
219 | this.isLoading = false
220 | $el.removeClass(d).removeAttr(d)
221 | }
222 | }, this), 0)
223 | }
224 |
225 | Button.prototype.toggle = function () {
226 | var changed = true
227 | var $parent = this.$element.closest('[data-toggle="buttons"]')
228 |
229 | if ($parent.length) {
230 | var $input = this.$element.find('input')
231 | if ($input.prop('type') == 'radio') {
232 | if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
233 | else $parent.find('.active').removeClass('active')
234 | }
235 | if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
236 | } else {
237 | this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
238 | }
239 |
240 | if (changed) this.$element.toggleClass('active')
241 | }
242 |
243 |
244 | // BUTTON PLUGIN DEFINITION
245 | // ========================
246 |
247 | function Plugin(option) {
248 | return this.each(function () {
249 | var $this = $(this)
250 | var data = $this.data('bs.button')
251 | var options = typeof option == 'object' && option
252 |
253 | if (!data) $this.data('bs.button', (data = new Button(this, options)))
254 |
255 | if (option == 'toggle') data.toggle()
256 | else if (option) data.setState(option)
257 | })
258 | }
259 |
260 | var old = $.fn.button
261 |
262 | $.fn.button = Plugin
263 | $.fn.button.Constructor = Button
264 |
265 |
266 | // BUTTON NO CONFLICT
267 | // ==================
268 |
269 | $.fn.button.noConflict = function () {
270 | $.fn.button = old
271 | return this
272 | }
273 |
274 |
275 | // BUTTON DATA-API
276 | // ===============
277 |
278 | $(document)
279 | .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
280 | var $btn = $(e.target)
281 | if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
282 | Plugin.call($btn, 'toggle')
283 | e.preventDefault()
284 | })
285 | .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
286 | $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
287 | })
288 |
289 | }(jQuery);
290 |
291 | /* ========================================================================
292 | * Bootstrap: carousel.js v3.3.2
293 | * http://getbootstrap.com/javascript/#carousel
294 | * ========================================================================
295 | * Copyright 2011-2015 Twitter, Inc.
296 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
297 | * ======================================================================== */
298 |
299 |
300 | +function ($) {
301 | 'use strict';
302 |
303 | // CAROUSEL CLASS DEFINITION
304 | // =========================
305 |
306 | var Carousel = function (element, options) {
307 | this.$element = $(element)
308 | this.$indicators = this.$element.find('.carousel-indicators')
309 | this.options = options
310 | this.paused =
311 | this.sliding =
312 | this.interval =
313 | this.$active =
314 | this.$items = null
315 |
316 | this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))
317 |
318 | this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
319 | .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
320 | .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
321 | }
322 |
323 | Carousel.VERSION = '3.3.2'
324 |
325 | Carousel.TRANSITION_DURATION = 600
326 |
327 | Carousel.DEFAULTS = {
328 | interval: 5000,
329 | pause: 'hover',
330 | wrap: true,
331 | keyboard: true
332 | }
333 |
334 | Carousel.prototype.keydown = function (e) {
335 | if (/input|textarea/i.test(e.target.tagName)) return
336 | switch (e.which) {
337 | case 37: this.prev(); break
338 | case 39: this.next(); break
339 | default: return
340 | }
341 |
342 | e.preventDefault()
343 | }
344 |
345 | Carousel.prototype.cycle = function (e) {
346 | e || (this.paused = false)
347 |
348 | this.interval && clearInterval(this.interval)
349 |
350 | this.options.interval
351 | && !this.paused
352 | && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
353 |
354 | return this
355 | }
356 |
357 | Carousel.prototype.getItemIndex = function (item) {
358 | this.$items = item.parent().children('.item')
359 | return this.$items.index(item || this.$active)
360 | }
361 |
362 | Carousel.prototype.getItemForDirection = function (direction, active) {
363 | var activeIndex = this.getItemIndex(active)
364 | var willWrap = (direction == 'prev' && activeIndex === 0)
365 | || (direction == 'next' && activeIndex == (this.$items.length - 1))
366 | if (willWrap && !this.options.wrap) return active
367 | var delta = direction == 'prev' ? -1 : 1
368 | var itemIndex = (activeIndex + delta) % this.$items.length
369 | return this.$items.eq(itemIndex)
370 | }
371 |
372 | Carousel.prototype.to = function (pos) {
373 | var that = this
374 | var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))
375 |
376 | if (pos > (this.$items.length - 1) || pos < 0) return
377 |
378 | if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
379 | if (activeIndex == pos) return this.pause().cycle()
380 |
381 | return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
382 | }
383 |
384 | Carousel.prototype.pause = function (e) {
385 | e || (this.paused = true)
386 |
387 | if (this.$element.find('.next, .prev').length && $.support.transition) {
388 | this.$element.trigger($.support.transition.end)
389 | this.cycle(true)
390 | }
391 |
392 | this.interval = clearInterval(this.interval)
393 |
394 | return this
395 | }
396 |
397 | Carousel.prototype.next = function () {
398 | if (this.sliding) return
399 | return this.slide('next')
400 | }
401 |
402 | Carousel.prototype.prev = function () {
403 | if (this.sliding) return
404 | return this.slide('prev')
405 | }
406 |
407 | Carousel.prototype.slide = function (type, next) {
408 | var $active = this.$element.find('.item.active')
409 | var $next = next || this.getItemForDirection(type, $active)
410 | var isCycling = this.interval
411 | var direction = type == 'next' ? 'left' : 'right'
412 | var that = this
413 |
414 | if ($next.hasClass('active')) return (this.sliding = false)
415 |
416 | var relatedTarget = $next[0]
417 | var slideEvent = $.Event('slide.bs.carousel', {
418 | relatedTarget: relatedTarget,
419 | direction: direction
420 | })
421 | this.$element.trigger(slideEvent)
422 | if (slideEvent.isDefaultPrevented()) return
423 |
424 | this.sliding = true
425 |
426 | isCycling && this.pause()
427 |
428 | if (this.$indicators.length) {
429 | this.$indicators.find('.active').removeClass('active')
430 | var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
431 | $nextIndicator && $nextIndicator.addClass('active')
432 | }
433 |
434 | var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
435 | if ($.support.transition && this.$element.hasClass('slide')) {
436 | $next.addClass(type)
437 | $next[0].offsetWidth // force reflow
438 | $active.addClass(direction)
439 | $next.addClass(direction)
440 | $active
441 | .one('bsTransitionEnd', function () {
442 | $next.removeClass([type, direction].join(' ')).addClass('active')
443 | $active.removeClass(['active', direction].join(' '))
444 | that.sliding = false
445 | setTimeout(function () {
446 | that.$element.trigger(slidEvent)
447 | }, 0)
448 | })
449 | .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
450 | } else {
451 | $active.removeClass('active')
452 | $next.addClass('active')
453 | this.sliding = false
454 | this.$element.trigger(slidEvent)
455 | }
456 |
457 | isCycling && this.cycle()
458 |
459 | return this
460 | }
461 |
462 |
463 | // CAROUSEL PLUGIN DEFINITION
464 | // ==========================
465 |
466 | function Plugin(option) {
467 | return this.each(function () {
468 | var $this = $(this)
469 | var data = $this.data('bs.carousel')
470 | var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
471 | var action = typeof option == 'string' ? option : options.slide
472 |
473 | if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
474 | if (typeof option == 'number') data.to(option)
475 | else if (action) data[action]()
476 | else if (options.interval) data.pause().cycle()
477 | })
478 | }
479 |
480 | var old = $.fn.carousel
481 |
482 | $.fn.carousel = Plugin
483 | $.fn.carousel.Constructor = Carousel
484 |
485 |
486 | // CAROUSEL NO CONFLICT
487 | // ====================
488 |
489 | $.fn.carousel.noConflict = function () {
490 | $.fn.carousel = old
491 | return this
492 | }
493 |
494 |
495 | // CAROUSEL DATA-API
496 | // =================
497 |
498 | var clickHandler = function (e) {
499 | var href
500 | var $this = $(this)
501 | var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7
502 | if (!$target.hasClass('carousel')) return
503 | var options = $.extend({}, $target.data(), $this.data())
504 | var slideIndex = $this.attr('data-slide-to')
505 | if (slideIndex) options.interval = false
506 |
507 | Plugin.call($target, options)
508 |
509 | if (slideIndex) {
510 | $target.data('bs.carousel').to(slideIndex)
511 | }
512 |
513 | e.preventDefault()
514 | }
515 |
516 | $(document)
517 | .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
518 | .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)
519 |
520 | $(window).on('load', function () {
521 | $('[data-ride="carousel"]').each(function () {
522 | var $carousel = $(this)
523 | Plugin.call($carousel, $carousel.data())
524 | })
525 | })
526 |
527 | }(jQuery);
528 |
529 | /* ========================================================================
530 | * Bootstrap: collapse.js v3.3.2
531 | * http://getbootstrap.com/javascript/#collapse
532 | * ========================================================================
533 | * Copyright 2011-2015 Twitter, Inc.
534 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
535 | * ======================================================================== */
536 |
537 |
538 | +function ($) {
539 | 'use strict';
540 |
541 | // COLLAPSE PUBLIC CLASS DEFINITION
542 | // ================================
543 |
544 | var Collapse = function (element, options) {
545 | this.$element = $(element)
546 | this.options = $.extend({}, Collapse.DEFAULTS, options)
547 | this.$trigger = $(this.options.trigger).filter('[href="#' + element.id + '"], [data-target="#' + element.id + '"]')
548 | this.transitioning = null
549 |
550 | if (this.options.parent) {
551 | this.$parent = this.getParent()
552 | } else {
553 | this.addAriaAndCollapsedClass(this.$element, this.$trigger)
554 | }
555 |
556 | if (this.options.toggle) this.toggle()
557 | }
558 |
559 | Collapse.VERSION = '3.3.2'
560 |
561 | Collapse.TRANSITION_DURATION = 350
562 |
563 | Collapse.DEFAULTS = {
564 | toggle: true,
565 | trigger: '[data-toggle="collapse"]'
566 | }
567 |
568 | Collapse.prototype.dimension = function () {
569 | var hasWidth = this.$element.hasClass('width')
570 | return hasWidth ? 'width' : 'height'
571 | }
572 |
573 | Collapse.prototype.show = function () {
574 | if (this.transitioning || this.$element.hasClass('in')) return
575 |
576 | var activesData
577 | var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')
578 |
579 | if (actives && actives.length) {
580 | activesData = actives.data('bs.collapse')
581 | if (activesData && activesData.transitioning) return
582 | }
583 |
584 | var startEvent = $.Event('show.bs.collapse')
585 | this.$element.trigger(startEvent)
586 | if (startEvent.isDefaultPrevented()) return
587 |
588 | if (actives && actives.length) {
589 | Plugin.call(actives, 'hide')
590 | activesData || actives.data('bs.collapse', null)
591 | }
592 |
593 | var dimension = this.dimension()
594 |
595 | this.$element
596 | .removeClass('collapse')
597 | .addClass('collapsing')[dimension](0)
598 | .attr('aria-expanded', true)
599 |
600 | this.$trigger
601 | .removeClass('collapsed')
602 | .attr('aria-expanded', true)
603 |
604 | this.transitioning = 1
605 |
606 | var complete = function () {
607 | this.$element
608 | .removeClass('collapsing')
609 | .addClass('collapse in')[dimension]('')
610 | this.transitioning = 0
611 | this.$element
612 | .trigger('shown.bs.collapse')
613 | }
614 |
615 | if (!$.support.transition) return complete.call(this)
616 |
617 | var scrollSize = $.camelCase(['scroll', dimension].join('-'))
618 |
619 | this.$element
620 | .one('bsTransitionEnd', $.proxy(complete, this))
621 | .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
622 | }
623 |
624 | Collapse.prototype.hide = function () {
625 | if (this.transitioning || !this.$element.hasClass('in')) return
626 |
627 | var startEvent = $.Event('hide.bs.collapse')
628 | this.$element.trigger(startEvent)
629 | if (startEvent.isDefaultPrevented()) return
630 |
631 | var dimension = this.dimension()
632 |
633 | this.$element[dimension](this.$element[dimension]())[0].offsetHeight
634 |
635 | this.$element
636 | .addClass('collapsing')
637 | .removeClass('collapse in')
638 | .attr('aria-expanded', false)
639 |
640 | this.$trigger
641 | .addClass('collapsed')
642 | .attr('aria-expanded', false)
643 |
644 | this.transitioning = 1
645 |
646 | var complete = function () {
647 | this.transitioning = 0
648 | this.$element
649 | .removeClass('collapsing')
650 | .addClass('collapse')
651 | .trigger('hidden.bs.collapse')
652 | }
653 |
654 | if (!$.support.transition) return complete.call(this)
655 |
656 | this.$element
657 | [dimension](0)
658 | .one('bsTransitionEnd', $.proxy(complete, this))
659 | .emulateTransitionEnd(Collapse.TRANSITION_DURATION)
660 | }
661 |
662 | Collapse.prototype.toggle = function () {
663 | this[this.$element.hasClass('in') ? 'hide' : 'show']()
664 | }
665 |
666 | Collapse.prototype.getParent = function () {
667 | return $(this.options.parent)
668 | .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
669 | .each($.proxy(function (i, element) {
670 | var $element = $(element)
671 | this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
672 | }, this))
673 | .end()
674 | }
675 |
676 | Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
677 | var isOpen = $element.hasClass('in')
678 |
679 | $element.attr('aria-expanded', isOpen)
680 | $trigger
681 | .toggleClass('collapsed', !isOpen)
682 | .attr('aria-expanded', isOpen)
683 | }
684 |
685 | function getTargetFromTrigger($trigger) {
686 | var href
687 | var target = $trigger.attr('data-target')
688 | || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
689 |
690 | return $(target)
691 | }
692 |
693 |
694 | // COLLAPSE PLUGIN DEFINITION
695 | // ==========================
696 |
697 | function Plugin(option) {
698 | return this.each(function () {
699 | var $this = $(this)
700 | var data = $this.data('bs.collapse')
701 | var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
702 |
703 | if (!data && options.toggle && option == 'show') options.toggle = false
704 | if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
705 | if (typeof option == 'string') data[option]()
706 | })
707 | }
708 |
709 | var old = $.fn.collapse
710 |
711 | $.fn.collapse = Plugin
712 | $.fn.collapse.Constructor = Collapse
713 |
714 |
715 | // COLLAPSE NO CONFLICT
716 | // ====================
717 |
718 | $.fn.collapse.noConflict = function () {
719 | $.fn.collapse = old
720 | return this
721 | }
722 |
723 |
724 | // COLLAPSE DATA-API
725 | // =================
726 |
727 | $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
728 | var $this = $(this)
729 |
730 | if (!$this.attr('data-target')) e.preventDefault()
731 |
732 | var $target = getTargetFromTrigger($this)
733 | var data = $target.data('bs.collapse')
734 | var option = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this })
735 |
736 | Plugin.call($target, option)
737 | })
738 |
739 | }(jQuery);
740 |
741 | /* ========================================================================
742 | * Bootstrap: dropdown.js v3.3.2
743 | * http://getbootstrap.com/javascript/#dropdowns
744 | * ========================================================================
745 | * Copyright 2011-2015 Twitter, Inc.
746 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
747 | * ======================================================================== */
748 |
749 |
750 | +function ($) {
751 | 'use strict';
752 |
753 | // DROPDOWN CLASS DEFINITION
754 | // =========================
755 |
756 | var backdrop = '.dropdown-backdrop'
757 | var toggle = '[data-toggle="dropdown"]'
758 | var Dropdown = function (element) {
759 | $(element).on('click.bs.dropdown', this.toggle)
760 | }
761 |
762 | Dropdown.VERSION = '3.3.2'
763 |
764 | Dropdown.prototype.toggle = function (e) {
765 | var $this = $(this)
766 |
767 | if ($this.is('.disabled, :disabled')) return
768 |
769 | var $parent = getParent($this)
770 | var isActive = $parent.hasClass('open')
771 |
772 | clearMenus()
773 |
774 | if (!isActive) {
775 | if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
776 | // if mobile we use a backdrop because click events don't delegate
777 | $('
').insertAfter($(this)).on('click', clearMenus)
778 | }
779 |
780 | var relatedTarget = { relatedTarget: this }
781 | $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))
782 |
783 | if (e.isDefaultPrevented()) return
784 |
785 | $this
786 | .trigger('focus')
787 | .attr('aria-expanded', 'true')
788 |
789 | $parent
790 | .toggleClass('open')
791 | .trigger('shown.bs.dropdown', relatedTarget)
792 | }
793 |
794 | return false
795 | }
796 |
797 | Dropdown.prototype.keydown = function (e) {
798 | if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return
799 |
800 | var $this = $(this)
801 |
802 | e.preventDefault()
803 | e.stopPropagation()
804 |
805 | if ($this.is('.disabled, :disabled')) return
806 |
807 | var $parent = getParent($this)
808 | var isActive = $parent.hasClass('open')
809 |
810 | if ((!isActive && e.which != 27) || (isActive && e.which == 27)) {
811 | if (e.which == 27) $parent.find(toggle).trigger('focus')
812 | return $this.trigger('click')
813 | }
814 |
815 | var desc = ' li:not(.divider):visible a'
816 | var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc)
817 |
818 | if (!$items.length) return
819 |
820 | var index = $items.index(e.target)
821 |
822 | if (e.which == 38 && index > 0) index-- // up
823 | if (e.which == 40 && index < $items.length - 1) index++ // down
824 | if (!~index) index = 0
825 |
826 | $items.eq(index).trigger('focus')
827 | }
828 |
829 | function clearMenus(e) {
830 | if (e && e.which === 3) return
831 | $(backdrop).remove()
832 | $(toggle).each(function () {
833 | var $this = $(this)
834 | var $parent = getParent($this)
835 | var relatedTarget = { relatedTarget: this }
836 |
837 | if (!$parent.hasClass('open')) return
838 |
839 | $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))
840 |
841 | if (e.isDefaultPrevented()) return
842 |
843 | $this.attr('aria-expanded', 'false')
844 | $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget)
845 | })
846 | }
847 |
848 | function getParent($this) {
849 | var selector = $this.attr('data-target')
850 |
851 | if (!selector) {
852 | selector = $this.attr('href')
853 | selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
854 | }
855 |
856 | var $parent = selector && $(selector)
857 |
858 | return $parent && $parent.length ? $parent : $this.parent()
859 | }
860 |
861 |
862 | // DROPDOWN PLUGIN DEFINITION
863 | // ==========================
864 |
865 | function Plugin(option) {
866 | return this.each(function () {
867 | var $this = $(this)
868 | var data = $this.data('bs.dropdown')
869 |
870 | if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
871 | if (typeof option == 'string') data[option].call($this)
872 | })
873 | }
874 |
875 | var old = $.fn.dropdown
876 |
877 | $.fn.dropdown = Plugin
878 | $.fn.dropdown.Constructor = Dropdown
879 |
880 |
881 | // DROPDOWN NO CONFLICT
882 | // ====================
883 |
884 | $.fn.dropdown.noConflict = function () {
885 | $.fn.dropdown = old
886 | return this
887 | }
888 |
889 |
890 | // APPLY TO STANDARD DROPDOWN ELEMENTS
891 | // ===================================
892 |
893 | $(document)
894 | .on('click.bs.dropdown.data-api', clearMenus)
895 | .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
896 | .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
897 | .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
898 | .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown)
899 | .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown)
900 |
901 | }(jQuery);
902 |
903 | /* ========================================================================
904 | * Bootstrap: modal.js v3.3.2
905 | * http://getbootstrap.com/javascript/#modals
906 | * ========================================================================
907 | * Copyright 2011-2015 Twitter, Inc.
908 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
909 | * ======================================================================== */
910 |
911 |
912 | +function ($) {
913 | 'use strict';
914 |
915 | // MODAL CLASS DEFINITION
916 | // ======================
917 |
918 | var Modal = function (element, options) {
919 | this.options = options
920 | this.$body = $(document.body)
921 | this.$element = $(element)
922 | this.$backdrop =
923 | this.isShown = null
924 | this.scrollbarWidth = 0
925 |
926 | if (this.options.remote) {
927 | this.$element
928 | .find('.modal-content')
929 | .load(this.options.remote, $.proxy(function () {
930 | this.$element.trigger('loaded.bs.modal')
931 | }, this))
932 | }
933 | }
934 |
935 | Modal.VERSION = '3.3.2'
936 |
937 | Modal.TRANSITION_DURATION = 300
938 | Modal.BACKDROP_TRANSITION_DURATION = 150
939 |
940 | Modal.DEFAULTS = {
941 | backdrop: true,
942 | keyboard: true,
943 | show: true
944 | }
945 |
946 | Modal.prototype.toggle = function (_relatedTarget) {
947 | return this.isShown ? this.hide() : this.show(_relatedTarget)
948 | }
949 |
950 | Modal.prototype.show = function (_relatedTarget) {
951 | var that = this
952 | var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
953 |
954 | this.$element.trigger(e)
955 |
956 | if (this.isShown || e.isDefaultPrevented()) return
957 |
958 | this.isShown = true
959 |
960 | this.checkScrollbar()
961 | this.setScrollbar()
962 | this.$body.addClass('modal-open')
963 |
964 | this.escape()
965 | this.resize()
966 |
967 | this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
968 |
969 | this.backdrop(function () {
970 | var transition = $.support.transition && that.$element.hasClass('fade')
971 |
972 | if (!that.$element.parent().length) {
973 | that.$element.appendTo(that.$body) // don't move modals dom position
974 | }
975 |
976 | that.$element
977 | .show()
978 | .scrollTop(0)
979 |
980 | if (that.options.backdrop) that.adjustBackdrop()
981 | that.adjustDialog()
982 |
983 | if (transition) {
984 | that.$element[0].offsetWidth // force reflow
985 | }
986 |
987 | that.$element
988 | .addClass('in')
989 | .attr('aria-hidden', false)
990 |
991 | that.enforceFocus()
992 |
993 | var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
994 |
995 | transition ?
996 | that.$element.find('.modal-dialog') // wait for modal to slide in
997 | .one('bsTransitionEnd', function () {
998 | that.$element.trigger('focus').trigger(e)
999 | })
1000 | .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
1001 | that.$element.trigger('focus').trigger(e)
1002 | })
1003 | }
1004 |
1005 | Modal.prototype.hide = function (e) {
1006 | if (e) e.preventDefault()
1007 |
1008 | e = $.Event('hide.bs.modal')
1009 |
1010 | this.$element.trigger(e)
1011 |
1012 | if (!this.isShown || e.isDefaultPrevented()) return
1013 |
1014 | this.isShown = false
1015 |
1016 | this.escape()
1017 | this.resize()
1018 |
1019 | $(document).off('focusin.bs.modal')
1020 |
1021 | this.$element
1022 | .removeClass('in')
1023 | .attr('aria-hidden', true)
1024 | .off('click.dismiss.bs.modal')
1025 |
1026 | $.support.transition && this.$element.hasClass('fade') ?
1027 | this.$element
1028 | .one('bsTransitionEnd', $.proxy(this.hideModal, this))
1029 | .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
1030 | this.hideModal()
1031 | }
1032 |
1033 | Modal.prototype.enforceFocus = function () {
1034 | $(document)
1035 | .off('focusin.bs.modal') // guard against infinite focus loop
1036 | .on('focusin.bs.modal', $.proxy(function (e) {
1037 | if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
1038 | this.$element.trigger('focus')
1039 | }
1040 | }, this))
1041 | }
1042 |
1043 | Modal.prototype.escape = function () {
1044 | if (this.isShown && this.options.keyboard) {
1045 | this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
1046 | e.which == 27 && this.hide()
1047 | }, this))
1048 | } else if (!this.isShown) {
1049 | this.$element.off('keydown.dismiss.bs.modal')
1050 | }
1051 | }
1052 |
1053 | Modal.prototype.resize = function () {
1054 | if (this.isShown) {
1055 | $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
1056 | } else {
1057 | $(window).off('resize.bs.modal')
1058 | }
1059 | }
1060 |
1061 | Modal.prototype.hideModal = function () {
1062 | var that = this
1063 | this.$element.hide()
1064 | this.backdrop(function () {
1065 | that.$body.removeClass('modal-open')
1066 | that.resetAdjustments()
1067 | that.resetScrollbar()
1068 | that.$element.trigger('hidden.bs.modal')
1069 | })
1070 | }
1071 |
1072 | Modal.prototype.removeBackdrop = function () {
1073 | this.$backdrop && this.$backdrop.remove()
1074 | this.$backdrop = null
1075 | }
1076 |
1077 | Modal.prototype.backdrop = function (callback) {
1078 | var that = this
1079 | var animate = this.$element.hasClass('fade') ? 'fade' : ''
1080 |
1081 | if (this.isShown && this.options.backdrop) {
1082 | var doAnimate = $.support.transition && animate
1083 |
1084 | this.$backdrop = $('')
1085 | .prependTo(this.$element)
1086 | .on('click.dismiss.bs.modal', $.proxy(function (e) {
1087 | if (e.target !== e.currentTarget) return
1088 | this.options.backdrop == 'static'
1089 | ? this.$element[0].focus.call(this.$element[0])
1090 | : this.hide.call(this)
1091 | }, this))
1092 |
1093 | if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
1094 |
1095 | this.$backdrop.addClass('in')
1096 |
1097 | if (!callback) return
1098 |
1099 | doAnimate ?
1100 | this.$backdrop
1101 | .one('bsTransitionEnd', callback)
1102 | .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
1103 | callback()
1104 |
1105 | } else if (!this.isShown && this.$backdrop) {
1106 | this.$backdrop.removeClass('in')
1107 |
1108 | var callbackRemove = function () {
1109 | that.removeBackdrop()
1110 | callback && callback()
1111 | }
1112 | $.support.transition && this.$element.hasClass('fade') ?
1113 | this.$backdrop
1114 | .one('bsTransitionEnd', callbackRemove)
1115 | .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
1116 | callbackRemove()
1117 |
1118 | } else if (callback) {
1119 | callback()
1120 | }
1121 | }
1122 |
1123 | // these following methods are used to handle overflowing modals
1124 |
1125 | Modal.prototype.handleUpdate = function () {
1126 | if (this.options.backdrop) this.adjustBackdrop()
1127 | this.adjustDialog()
1128 | }
1129 |
1130 | Modal.prototype.adjustBackdrop = function () {
1131 | this.$backdrop
1132 | .css('height', 0)
1133 | .css('height', this.$element[0].scrollHeight)
1134 | }
1135 |
1136 | Modal.prototype.adjustDialog = function () {
1137 | var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight
1138 |
1139 | this.$element.css({
1140 | paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
1141 | paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
1142 | })
1143 | }
1144 |
1145 | Modal.prototype.resetAdjustments = function () {
1146 | this.$element.css({
1147 | paddingLeft: '',
1148 | paddingRight: ''
1149 | })
1150 | }
1151 |
1152 | Modal.prototype.checkScrollbar = function () {
1153 | this.bodyIsOverflowing = document.body.scrollHeight > document.documentElement.clientHeight
1154 | this.scrollbarWidth = this.measureScrollbar()
1155 | }
1156 |
1157 | Modal.prototype.setScrollbar = function () {
1158 | var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
1159 | if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
1160 | }
1161 |
1162 | Modal.prototype.resetScrollbar = function () {
1163 | this.$body.css('padding-right', '')
1164 | }
1165 |
1166 | Modal.prototype.measureScrollbar = function () { // thx walsh
1167 | var scrollDiv = document.createElement('div')
1168 | scrollDiv.className = 'modal-scrollbar-measure'
1169 | this.$body.append(scrollDiv)
1170 | var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
1171 | this.$body[0].removeChild(scrollDiv)
1172 | return scrollbarWidth
1173 | }
1174 |
1175 |
1176 | // MODAL PLUGIN DEFINITION
1177 | // =======================
1178 |
1179 | function Plugin(option, _relatedTarget) {
1180 | return this.each(function () {
1181 | var $this = $(this)
1182 | var data = $this.data('bs.modal')
1183 | var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
1184 |
1185 | if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
1186 | if (typeof option == 'string') data[option](_relatedTarget)
1187 | else if (options.show) data.show(_relatedTarget)
1188 | })
1189 | }
1190 |
1191 | var old = $.fn.modal
1192 |
1193 | $.fn.modal = Plugin
1194 | $.fn.modal.Constructor = Modal
1195 |
1196 |
1197 | // MODAL NO CONFLICT
1198 | // =================
1199 |
1200 | $.fn.modal.noConflict = function () {
1201 | $.fn.modal = old
1202 | return this
1203 | }
1204 |
1205 |
1206 | // MODAL DATA-API
1207 | // ==============
1208 |
1209 | $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
1210 | var $this = $(this)
1211 | var href = $this.attr('href')
1212 | var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
1213 | var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
1214 |
1215 | if ($this.is('a')) e.preventDefault()
1216 |
1217 | $target.one('show.bs.modal', function (showEvent) {
1218 | if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
1219 | $target.one('hidden.bs.modal', function () {
1220 | $this.is(':visible') && $this.trigger('focus')
1221 | })
1222 | })
1223 | Plugin.call($target, option, this)
1224 | })
1225 |
1226 | }(jQuery);
1227 |
1228 | /* ========================================================================
1229 | * Bootstrap: tooltip.js v3.3.2
1230 | * http://getbootstrap.com/javascript/#tooltip
1231 | * Inspired by the original jQuery.tipsy by Jason Frame
1232 | * ========================================================================
1233 | * Copyright 2011-2015 Twitter, Inc.
1234 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
1235 | * ======================================================================== */
1236 |
1237 |
1238 | +function ($) {
1239 | 'use strict';
1240 |
1241 | // TOOLTIP PUBLIC CLASS DEFINITION
1242 | // ===============================
1243 |
1244 | var Tooltip = function (element, options) {
1245 | this.type =
1246 | this.options =
1247 | this.enabled =
1248 | this.timeout =
1249 | this.hoverState =
1250 | this.$element = null
1251 |
1252 | this.init('tooltip', element, options)
1253 | }
1254 |
1255 | Tooltip.VERSION = '3.3.2'
1256 |
1257 | Tooltip.TRANSITION_DURATION = 150
1258 |
1259 | Tooltip.DEFAULTS = {
1260 | animation: true,
1261 | placement: 'top',
1262 | selector: false,
1263 | template: '',
1264 | trigger: 'hover focus',
1265 | title: '',
1266 | delay: 0,
1267 | html: false,
1268 | container: false,
1269 | viewport: {
1270 | selector: 'body',
1271 | padding: 0
1272 | }
1273 | }
1274 |
1275 | Tooltip.prototype.init = function (type, element, options) {
1276 | this.enabled = true
1277 | this.type = type
1278 | this.$element = $(element)
1279 | this.options = this.getOptions(options)
1280 | this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport)
1281 |
1282 | var triggers = this.options.trigger.split(' ')
1283 |
1284 | for (var i = triggers.length; i--;) {
1285 | var trigger = triggers[i]
1286 |
1287 | if (trigger == 'click') {
1288 | this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
1289 | } else if (trigger != 'manual') {
1290 | var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin'
1291 | var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'
1292 |
1293 | this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
1294 | this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
1295 | }
1296 | }
1297 |
1298 | this.options.selector ?
1299 | (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
1300 | this.fixTitle()
1301 | }
1302 |
1303 | Tooltip.prototype.getDefaults = function () {
1304 | return Tooltip.DEFAULTS
1305 | }
1306 |
1307 | Tooltip.prototype.getOptions = function (options) {
1308 | options = $.extend({}, this.getDefaults(), this.$element.data(), options)
1309 |
1310 | if (options.delay && typeof options.delay == 'number') {
1311 | options.delay = {
1312 | show: options.delay,
1313 | hide: options.delay
1314 | }
1315 | }
1316 |
1317 | return options
1318 | }
1319 |
1320 | Tooltip.prototype.getDelegateOptions = function () {
1321 | var options = {}
1322 | var defaults = this.getDefaults()
1323 |
1324 | this._options && $.each(this._options, function (key, value) {
1325 | if (defaults[key] != value) options[key] = value
1326 | })
1327 |
1328 | return options
1329 | }
1330 |
1331 | Tooltip.prototype.enter = function (obj) {
1332 | var self = obj instanceof this.constructor ?
1333 | obj : $(obj.currentTarget).data('bs.' + this.type)
1334 |
1335 | if (self && self.$tip && self.$tip.is(':visible')) {
1336 | self.hoverState = 'in'
1337 | return
1338 | }
1339 |
1340 | if (!self) {
1341 | self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
1342 | $(obj.currentTarget).data('bs.' + this.type, self)
1343 | }
1344 |
1345 | clearTimeout(self.timeout)
1346 |
1347 | self.hoverState = 'in'
1348 |
1349 | if (!self.options.delay || !self.options.delay.show) return self.show()
1350 |
1351 | self.timeout = setTimeout(function () {
1352 | if (self.hoverState == 'in') self.show()
1353 | }, self.options.delay.show)
1354 | }
1355 |
1356 | Tooltip.prototype.leave = function (obj) {
1357 | var self = obj instanceof this.constructor ?
1358 | obj : $(obj.currentTarget).data('bs.' + this.type)
1359 |
1360 | if (!self) {
1361 | self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
1362 | $(obj.currentTarget).data('bs.' + this.type, self)
1363 | }
1364 |
1365 | clearTimeout(self.timeout)
1366 |
1367 | self.hoverState = 'out'
1368 |
1369 | if (!self.options.delay || !self.options.delay.hide) return self.hide()
1370 |
1371 | self.timeout = setTimeout(function () {
1372 | if (self.hoverState == 'out') self.hide()
1373 | }, self.options.delay.hide)
1374 | }
1375 |
1376 | Tooltip.prototype.show = function () {
1377 | var e = $.Event('show.bs.' + this.type)
1378 |
1379 | if (this.hasContent() && this.enabled) {
1380 | this.$element.trigger(e)
1381 |
1382 | var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
1383 | if (e.isDefaultPrevented() || !inDom) return
1384 | var that = this
1385 |
1386 | var $tip = this.tip()
1387 |
1388 | var tipId = this.getUID(this.type)
1389 |
1390 | this.setContent()
1391 | $tip.attr('id', tipId)
1392 | this.$element.attr('aria-describedby', tipId)
1393 |
1394 | if (this.options.animation) $tip.addClass('fade')
1395 |
1396 | var placement = typeof this.options.placement == 'function' ?
1397 | this.options.placement.call(this, $tip[0], this.$element[0]) :
1398 | this.options.placement
1399 |
1400 | var autoToken = /\s?auto?\s?/i
1401 | var autoPlace = autoToken.test(placement)
1402 | if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
1403 |
1404 | $tip
1405 | .detach()
1406 | .css({ top: 0, left: 0, display: 'block' })
1407 | .addClass(placement)
1408 | .data('bs.' + this.type, this)
1409 |
1410 | this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
1411 |
1412 | var pos = this.getPosition()
1413 | var actualWidth = $tip[0].offsetWidth
1414 | var actualHeight = $tip[0].offsetHeight
1415 |
1416 | if (autoPlace) {
1417 | var orgPlacement = placement
1418 | var $container = this.options.container ? $(this.options.container) : this.$element.parent()
1419 | var containerDim = this.getPosition($container)
1420 |
1421 | placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' :
1422 | placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' :
1423 | placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' :
1424 | placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' :
1425 | placement
1426 |
1427 | $tip
1428 | .removeClass(orgPlacement)
1429 | .addClass(placement)
1430 | }
1431 |
1432 | var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
1433 |
1434 | this.applyPlacement(calculatedOffset, placement)
1435 |
1436 | var complete = function () {
1437 | var prevHoverState = that.hoverState
1438 | that.$element.trigger('shown.bs.' + that.type)
1439 | that.hoverState = null
1440 |
1441 | if (prevHoverState == 'out') that.leave(that)
1442 | }
1443 |
1444 | $.support.transition && this.$tip.hasClass('fade') ?
1445 | $tip
1446 | .one('bsTransitionEnd', complete)
1447 | .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
1448 | complete()
1449 | }
1450 | }
1451 |
1452 | Tooltip.prototype.applyPlacement = function (offset, placement) {
1453 | var $tip = this.tip()
1454 | var width = $tip[0].offsetWidth
1455 | var height = $tip[0].offsetHeight
1456 |
1457 | // manually read margins because getBoundingClientRect includes difference
1458 | var marginTop = parseInt($tip.css('margin-top'), 10)
1459 | var marginLeft = parseInt($tip.css('margin-left'), 10)
1460 |
1461 | // we must check for NaN for ie 8/9
1462 | if (isNaN(marginTop)) marginTop = 0
1463 | if (isNaN(marginLeft)) marginLeft = 0
1464 |
1465 | offset.top = offset.top + marginTop
1466 | offset.left = offset.left + marginLeft
1467 |
1468 | // $.fn.offset doesn't round pixel values
1469 | // so we use setOffset directly with our own function B-0
1470 | $.offset.setOffset($tip[0], $.extend({
1471 | using: function (props) {
1472 | $tip.css({
1473 | top: Math.round(props.top),
1474 | left: Math.round(props.left)
1475 | })
1476 | }
1477 | }, offset), 0)
1478 |
1479 | $tip.addClass('in')
1480 |
1481 | // check to see if placing tip in new offset caused the tip to resize itself
1482 | var actualWidth = $tip[0].offsetWidth
1483 | var actualHeight = $tip[0].offsetHeight
1484 |
1485 | if (placement == 'top' && actualHeight != height) {
1486 | offset.top = offset.top + height - actualHeight
1487 | }
1488 |
1489 | var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
1490 |
1491 | if (delta.left) offset.left += delta.left
1492 | else offset.top += delta.top
1493 |
1494 | var isVertical = /top|bottom/.test(placement)
1495 | var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
1496 | var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
1497 |
1498 | $tip.offset(offset)
1499 | this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
1500 | }
1501 |
1502 | Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) {
1503 | this.arrow()
1504 | .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
1505 | .css(isHorizontal ? 'top' : 'left', '')
1506 | }
1507 |
1508 | Tooltip.prototype.setContent = function () {
1509 | var $tip = this.tip()
1510 | var title = this.getTitle()
1511 |
1512 | $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
1513 | $tip.removeClass('fade in top bottom left right')
1514 | }
1515 |
1516 | Tooltip.prototype.hide = function (callback) {
1517 | var that = this
1518 | var $tip = this.tip()
1519 | var e = $.Event('hide.bs.' + this.type)
1520 |
1521 | function complete() {
1522 | if (that.hoverState != 'in') $tip.detach()
1523 | that.$element
1524 | .removeAttr('aria-describedby')
1525 | .trigger('hidden.bs.' + that.type)
1526 | callback && callback()
1527 | }
1528 |
1529 | this.$element.trigger(e)
1530 |
1531 | if (e.isDefaultPrevented()) return
1532 |
1533 | $tip.removeClass('in')
1534 |
1535 | $.support.transition && this.$tip.hasClass('fade') ?
1536 | $tip
1537 | .one('bsTransitionEnd', complete)
1538 | .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
1539 | complete()
1540 |
1541 | this.hoverState = null
1542 |
1543 | return this
1544 | }
1545 |
1546 | Tooltip.prototype.fixTitle = function () {
1547 | var $e = this.$element
1548 | if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') {
1549 | $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
1550 | }
1551 | }
1552 |
1553 | Tooltip.prototype.hasContent = function () {
1554 | return this.getTitle()
1555 | }
1556 |
1557 | Tooltip.prototype.getPosition = function ($element) {
1558 | $element = $element || this.$element
1559 |
1560 | var el = $element[0]
1561 | var isBody = el.tagName == 'BODY'
1562 |
1563 | var elRect = el.getBoundingClientRect()
1564 | if (elRect.width == null) {
1565 | // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
1566 | elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
1567 | }
1568 | var elOffset = isBody ? { top: 0, left: 0 } : $element.offset()
1569 | var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
1570 | var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null
1571 |
1572 | return $.extend({}, elRect, scroll, outerDims, elOffset)
1573 | }
1574 |
1575 | Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
1576 | return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
1577 | placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
1578 | placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
1579 | /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
1580 |
1581 | }
1582 |
1583 | Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
1584 | var delta = { top: 0, left: 0 }
1585 | if (!this.$viewport) return delta
1586 |
1587 | var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
1588 | var viewportDimensions = this.getPosition(this.$viewport)
1589 |
1590 | if (/right|left/.test(placement)) {
1591 | var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
1592 | var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
1593 | if (topEdgeOffset < viewportDimensions.top) { // top overflow
1594 | delta.top = viewportDimensions.top - topEdgeOffset
1595 | } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
1596 | delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
1597 | }
1598 | } else {
1599 | var leftEdgeOffset = pos.left - viewportPadding
1600 | var rightEdgeOffset = pos.left + viewportPadding + actualWidth
1601 | if (leftEdgeOffset < viewportDimensions.left) { // left overflow
1602 | delta.left = viewportDimensions.left - leftEdgeOffset
1603 | } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow
1604 | delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
1605 | }
1606 | }
1607 |
1608 | return delta
1609 | }
1610 |
1611 | Tooltip.prototype.getTitle = function () {
1612 | var title
1613 | var $e = this.$element
1614 | var o = this.options
1615 |
1616 | title = $e.attr('data-original-title')
1617 | || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
1618 |
1619 | return title
1620 | }
1621 |
1622 | Tooltip.prototype.getUID = function (prefix) {
1623 | do prefix += ~~(Math.random() * 1000000)
1624 | while (document.getElementById(prefix))
1625 | return prefix
1626 | }
1627 |
1628 | Tooltip.prototype.tip = function () {
1629 | return (this.$tip = this.$tip || $(this.options.template))
1630 | }
1631 |
1632 | Tooltip.prototype.arrow = function () {
1633 | return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
1634 | }
1635 |
1636 | Tooltip.prototype.enable = function () {
1637 | this.enabled = true
1638 | }
1639 |
1640 | Tooltip.prototype.disable = function () {
1641 | this.enabled = false
1642 | }
1643 |
1644 | Tooltip.prototype.toggleEnabled = function () {
1645 | this.enabled = !this.enabled
1646 | }
1647 |
1648 | Tooltip.prototype.toggle = function (e) {
1649 | var self = this
1650 | if (e) {
1651 | self = $(e.currentTarget).data('bs.' + this.type)
1652 | if (!self) {
1653 | self = new this.constructor(e.currentTarget, this.getDelegateOptions())
1654 | $(e.currentTarget).data('bs.' + this.type, self)
1655 | }
1656 | }
1657 |
1658 | self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
1659 | }
1660 |
1661 | Tooltip.prototype.destroy = function () {
1662 | var that = this
1663 | clearTimeout(this.timeout)
1664 | this.hide(function () {
1665 | that.$element.off('.' + that.type).removeData('bs.' + that.type)
1666 | })
1667 | }
1668 |
1669 |
1670 | // TOOLTIP PLUGIN DEFINITION
1671 | // =========================
1672 |
1673 | function Plugin(option) {
1674 | return this.each(function () {
1675 | var $this = $(this)
1676 | var data = $this.data('bs.tooltip')
1677 | var options = typeof option == 'object' && option
1678 |
1679 | if (!data && option == 'destroy') return
1680 | if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
1681 | if (typeof option == 'string') data[option]()
1682 | })
1683 | }
1684 |
1685 | var old = $.fn.tooltip
1686 |
1687 | $.fn.tooltip = Plugin
1688 | $.fn.tooltip.Constructor = Tooltip
1689 |
1690 |
1691 | // TOOLTIP NO CONFLICT
1692 | // ===================
1693 |
1694 | $.fn.tooltip.noConflict = function () {
1695 | $.fn.tooltip = old
1696 | return this
1697 | }
1698 |
1699 | }(jQuery);
1700 |
1701 | /* ========================================================================
1702 | * Bootstrap: popover.js v3.3.2
1703 | * http://getbootstrap.com/javascript/#popovers
1704 | * ========================================================================
1705 | * Copyright 2011-2015 Twitter, Inc.
1706 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
1707 | * ======================================================================== */
1708 |
1709 |
1710 | +function ($) {
1711 | 'use strict';
1712 |
1713 | // POPOVER PUBLIC CLASS DEFINITION
1714 | // ===============================
1715 |
1716 | var Popover = function (element, options) {
1717 | this.init('popover', element, options)
1718 | }
1719 |
1720 | if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
1721 |
1722 | Popover.VERSION = '3.3.2'
1723 |
1724 | Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
1725 | placement: 'right',
1726 | trigger: 'click',
1727 | content: '',
1728 | template: ''
1729 | })
1730 |
1731 |
1732 | // NOTE: POPOVER EXTENDS tooltip.js
1733 | // ================================
1734 |
1735 | Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
1736 |
1737 | Popover.prototype.constructor = Popover
1738 |
1739 | Popover.prototype.getDefaults = function () {
1740 | return Popover.DEFAULTS
1741 | }
1742 |
1743 | Popover.prototype.setContent = function () {
1744 | var $tip = this.tip()
1745 | var title = this.getTitle()
1746 | var content = this.getContent()
1747 |
1748 | $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
1749 | $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events
1750 | this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text'
1751 | ](content)
1752 |
1753 | $tip.removeClass('fade top bottom left right in')
1754 |
1755 | // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
1756 | // this manually by checking the contents.
1757 | if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
1758 | }
1759 |
1760 | Popover.prototype.hasContent = function () {
1761 | return this.getTitle() || this.getContent()
1762 | }
1763 |
1764 | Popover.prototype.getContent = function () {
1765 | var $e = this.$element
1766 | var o = this.options
1767 |
1768 | return $e.attr('data-content')
1769 | || (typeof o.content == 'function' ?
1770 | o.content.call($e[0]) :
1771 | o.content)
1772 | }
1773 |
1774 | Popover.prototype.arrow = function () {
1775 | return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
1776 | }
1777 |
1778 | Popover.prototype.tip = function () {
1779 | if (!this.$tip) this.$tip = $(this.options.template)
1780 | return this.$tip
1781 | }
1782 |
1783 |
1784 | // POPOVER PLUGIN DEFINITION
1785 | // =========================
1786 |
1787 | function Plugin(option) {
1788 | return this.each(function () {
1789 | var $this = $(this)
1790 | var data = $this.data('bs.popover')
1791 | var options = typeof option == 'object' && option
1792 |
1793 | if (!data && option == 'destroy') return
1794 | if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
1795 | if (typeof option == 'string') data[option]()
1796 | })
1797 | }
1798 |
1799 | var old = $.fn.popover
1800 |
1801 | $.fn.popover = Plugin
1802 | $.fn.popover.Constructor = Popover
1803 |
1804 |
1805 | // POPOVER NO CONFLICT
1806 | // ===================
1807 |
1808 | $.fn.popover.noConflict = function () {
1809 | $.fn.popover = old
1810 | return this
1811 | }
1812 |
1813 | }(jQuery);
1814 |
1815 | /* ========================================================================
1816 | * Bootstrap: scrollspy.js v3.3.2
1817 | * http://getbootstrap.com/javascript/#scrollspy
1818 | * ========================================================================
1819 | * Copyright 2011-2015 Twitter, Inc.
1820 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
1821 | * ======================================================================== */
1822 |
1823 |
1824 | +function ($) {
1825 | 'use strict';
1826 |
1827 | // SCROLLSPY CLASS DEFINITION
1828 | // ==========================
1829 |
1830 | function ScrollSpy(element, options) {
1831 | var process = $.proxy(this.process, this)
1832 |
1833 | this.$body = $('body')
1834 | this.$scrollElement = $(element).is('body') ? $(window) : $(element)
1835 | this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
1836 | this.selector = (this.options.target || '') + ' .nav li > a'
1837 | this.offsets = []
1838 | this.targets = []
1839 | this.activeTarget = null
1840 | this.scrollHeight = 0
1841 |
1842 | this.$scrollElement.on('scroll.bs.scrollspy', process)
1843 | this.refresh()
1844 | this.process()
1845 | }
1846 |
1847 | ScrollSpy.VERSION = '3.3.2'
1848 |
1849 | ScrollSpy.DEFAULTS = {
1850 | offset: 10
1851 | }
1852 |
1853 | ScrollSpy.prototype.getScrollHeight = function () {
1854 | return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
1855 | }
1856 |
1857 | ScrollSpy.prototype.refresh = function () {
1858 | var offsetMethod = 'offset'
1859 | var offsetBase = 0
1860 |
1861 | if (!$.isWindow(this.$scrollElement[0])) {
1862 | offsetMethod = 'position'
1863 | offsetBase = this.$scrollElement.scrollTop()
1864 | }
1865 |
1866 | this.offsets = []
1867 | this.targets = []
1868 | this.scrollHeight = this.getScrollHeight()
1869 |
1870 | var self = this
1871 |
1872 | this.$body
1873 | .find(this.selector)
1874 | .map(function () {
1875 | var $el = $(this)
1876 | var href = $el.data('target') || $el.attr('href')
1877 | var $href = /^#./.test(href) && $(href)
1878 |
1879 | return ($href
1880 | && $href.length
1881 | && $href.is(':visible')
1882 | && [[$href[offsetMethod]().top + offsetBase, href]]) || null
1883 | })
1884 | .sort(function (a, b) { return a[0] - b[0] })
1885 | .each(function () {
1886 | self.offsets.push(this[0])
1887 | self.targets.push(this[1])
1888 | })
1889 | }
1890 |
1891 | ScrollSpy.prototype.process = function () {
1892 | var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
1893 | var scrollHeight = this.getScrollHeight()
1894 | var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height()
1895 | var offsets = this.offsets
1896 | var targets = this.targets
1897 | var activeTarget = this.activeTarget
1898 | var i
1899 |
1900 | if (this.scrollHeight != scrollHeight) {
1901 | this.refresh()
1902 | }
1903 |
1904 | if (scrollTop >= maxScroll) {
1905 | return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
1906 | }
1907 |
1908 | if (activeTarget && scrollTop < offsets[0]) {
1909 | this.activeTarget = null
1910 | return this.clear()
1911 | }
1912 |
1913 | for (i = offsets.length; i--;) {
1914 | activeTarget != targets[i]
1915 | && scrollTop >= offsets[i]
1916 | && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
1917 | && this.activate(targets[i])
1918 | }
1919 | }
1920 |
1921 | ScrollSpy.prototype.activate = function (target) {
1922 | this.activeTarget = target
1923 |
1924 | this.clear()
1925 |
1926 | var selector = this.selector +
1927 | '[data-target="' + target + '"],' +
1928 | this.selector + '[href="' + target + '"]'
1929 |
1930 | var active = $(selector)
1931 | .parents('li')
1932 | .addClass('active')
1933 |
1934 | if (active.parent('.dropdown-menu').length) {
1935 | active = active
1936 | .closest('li.dropdown')
1937 | .addClass('active')
1938 | }
1939 |
1940 | active.trigger('activate.bs.scrollspy')
1941 | }
1942 |
1943 | ScrollSpy.prototype.clear = function () {
1944 | $(this.selector)
1945 | .parentsUntil(this.options.target, '.active')
1946 | .removeClass('active')
1947 | }
1948 |
1949 |
1950 | // SCROLLSPY PLUGIN DEFINITION
1951 | // ===========================
1952 |
1953 | function Plugin(option) {
1954 | return this.each(function () {
1955 | var $this = $(this)
1956 | var data = $this.data('bs.scrollspy')
1957 | var options = typeof option == 'object' && option
1958 |
1959 | if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
1960 | if (typeof option == 'string') data[option]()
1961 | })
1962 | }
1963 |
1964 | var old = $.fn.scrollspy
1965 |
1966 | $.fn.scrollspy = Plugin
1967 | $.fn.scrollspy.Constructor = ScrollSpy
1968 |
1969 |
1970 | // SCROLLSPY NO CONFLICT
1971 | // =====================
1972 |
1973 | $.fn.scrollspy.noConflict = function () {
1974 | $.fn.scrollspy = old
1975 | return this
1976 | }
1977 |
1978 |
1979 | // SCROLLSPY DATA-API
1980 | // ==================
1981 |
1982 | $(window).on('load.bs.scrollspy.data-api', function () {
1983 | $('[data-spy="scroll"]').each(function () {
1984 | var $spy = $(this)
1985 | Plugin.call($spy, $spy.data())
1986 | })
1987 | })
1988 |
1989 | }(jQuery);
1990 |
1991 | /* ========================================================================
1992 | * Bootstrap: tab.js v3.3.2
1993 | * http://getbootstrap.com/javascript/#tabs
1994 | * ========================================================================
1995 | * Copyright 2011-2015 Twitter, Inc.
1996 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
1997 | * ======================================================================== */
1998 |
1999 |
2000 | +function ($) {
2001 | 'use strict';
2002 |
2003 | // TAB CLASS DEFINITION
2004 | // ====================
2005 |
2006 | var Tab = function (element) {
2007 | this.element = $(element)
2008 | }
2009 |
2010 | Tab.VERSION = '3.3.2'
2011 |
2012 | Tab.TRANSITION_DURATION = 150
2013 |
2014 | Tab.prototype.show = function () {
2015 | var $this = this.element
2016 | var $ul = $this.closest('ul:not(.dropdown-menu)')
2017 | var selector = $this.data('target')
2018 |
2019 | if (!selector) {
2020 | selector = $this.attr('href')
2021 | selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
2022 | }
2023 |
2024 | if ($this.parent('li').hasClass('active')) return
2025 |
2026 | var $previous = $ul.find('.active:last a')
2027 | var hideEvent = $.Event('hide.bs.tab', {
2028 | relatedTarget: $this[0]
2029 | })
2030 | var showEvent = $.Event('show.bs.tab', {
2031 | relatedTarget: $previous[0]
2032 | })
2033 |
2034 | $previous.trigger(hideEvent)
2035 | $this.trigger(showEvent)
2036 |
2037 | if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return
2038 |
2039 | var $target = $(selector)
2040 |
2041 | this.activate($this.closest('li'), $ul)
2042 | this.activate($target, $target.parent(), function () {
2043 | $previous.trigger({
2044 | type: 'hidden.bs.tab',
2045 | relatedTarget: $this[0]
2046 | })
2047 | $this.trigger({
2048 | type: 'shown.bs.tab',
2049 | relatedTarget: $previous[0]
2050 | })
2051 | })
2052 | }
2053 |
2054 | Tab.prototype.activate = function (element, container, callback) {
2055 | var $active = container.find('> .active')
2056 | var transition = callback
2057 | && $.support.transition
2058 | && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length)
2059 |
2060 | function next() {
2061 | $active
2062 | .removeClass('active')
2063 | .find('> .dropdown-menu > .active')
2064 | .removeClass('active')
2065 | .end()
2066 | .find('[data-toggle="tab"]')
2067 | .attr('aria-expanded', false)
2068 |
2069 | element
2070 | .addClass('active')
2071 | .find('[data-toggle="tab"]')
2072 | .attr('aria-expanded', true)
2073 |
2074 | if (transition) {
2075 | element[0].offsetWidth // reflow for transition
2076 | element.addClass('in')
2077 | } else {
2078 | element.removeClass('fade')
2079 | }
2080 |
2081 | if (element.parent('.dropdown-menu')) {
2082 | element
2083 | .closest('li.dropdown')
2084 | .addClass('active')
2085 | .end()
2086 | .find('[data-toggle="tab"]')
2087 | .attr('aria-expanded', true)
2088 | }
2089 |
2090 | callback && callback()
2091 | }
2092 |
2093 | $active.length && transition ?
2094 | $active
2095 | .one('bsTransitionEnd', next)
2096 | .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
2097 | next()
2098 |
2099 | $active.removeClass('in')
2100 | }
2101 |
2102 |
2103 | // TAB PLUGIN DEFINITION
2104 | // =====================
2105 |
2106 | function Plugin(option) {
2107 | return this.each(function () {
2108 | var $this = $(this)
2109 | var data = $this.data('bs.tab')
2110 |
2111 | if (!data) $this.data('bs.tab', (data = new Tab(this)))
2112 | if (typeof option == 'string') data[option]()
2113 | })
2114 | }
2115 |
2116 | var old = $.fn.tab
2117 |
2118 | $.fn.tab = Plugin
2119 | $.fn.tab.Constructor = Tab
2120 |
2121 |
2122 | // TAB NO CONFLICT
2123 | // ===============
2124 |
2125 | $.fn.tab.noConflict = function () {
2126 | $.fn.tab = old
2127 | return this
2128 | }
2129 |
2130 |
2131 | // TAB DATA-API
2132 | // ============
2133 |
2134 | var clickHandler = function (e) {
2135 | e.preventDefault()
2136 | Plugin.call($(this), 'show')
2137 | }
2138 |
2139 | $(document)
2140 | .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
2141 | .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)
2142 |
2143 | }(jQuery);
2144 |
2145 | /* ========================================================================
2146 | * Bootstrap: affix.js v3.3.2
2147 | * http://getbootstrap.com/javascript/#affix
2148 | * ========================================================================
2149 | * Copyright 2011-2015 Twitter, Inc.
2150 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
2151 | * ======================================================================== */
2152 |
2153 |
2154 | +function ($) {
2155 | 'use strict';
2156 |
2157 | // AFFIX CLASS DEFINITION
2158 | // ======================
2159 |
2160 | var Affix = function (element, options) {
2161 | this.options = $.extend({}, Affix.DEFAULTS, options)
2162 |
2163 | this.$target = $(this.options.target)
2164 | .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
2165 | .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
2166 |
2167 | this.$element = $(element)
2168 | this.affixed =
2169 | this.unpin =
2170 | this.pinnedOffset = null
2171 |
2172 | this.checkPosition()
2173 | }
2174 |
2175 | Affix.VERSION = '3.3.2'
2176 |
2177 | Affix.RESET = 'affix affix-top affix-bottom'
2178 |
2179 | Affix.DEFAULTS = {
2180 | offset: 0,
2181 | target: window
2182 | }
2183 |
2184 | Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
2185 | var scrollTop = this.$target.scrollTop()
2186 | var position = this.$element.offset()
2187 | var targetHeight = this.$target.height()
2188 |
2189 | if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false
2190 |
2191 | if (this.affixed == 'bottom') {
2192 | if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
2193 | return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
2194 | }
2195 |
2196 | var initializing = this.affixed == null
2197 | var colliderTop = initializing ? scrollTop : position.top
2198 | var colliderHeight = initializing ? targetHeight : height
2199 |
2200 | if (offsetTop != null && scrollTop <= offsetTop) return 'top'
2201 | if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'
2202 |
2203 | return false
2204 | }
2205 |
2206 | Affix.prototype.getPinnedOffset = function () {
2207 | if (this.pinnedOffset) return this.pinnedOffset
2208 | this.$element.removeClass(Affix.RESET).addClass('affix')
2209 | var scrollTop = this.$target.scrollTop()
2210 | var position = this.$element.offset()
2211 | return (this.pinnedOffset = position.top - scrollTop)
2212 | }
2213 |
2214 | Affix.prototype.checkPositionWithEventLoop = function () {
2215 | setTimeout($.proxy(this.checkPosition, this), 1)
2216 | }
2217 |
2218 | Affix.prototype.checkPosition = function () {
2219 | if (!this.$element.is(':visible')) return
2220 |
2221 | var height = this.$element.height()
2222 | var offset = this.options.offset
2223 | var offsetTop = offset.top
2224 | var offsetBottom = offset.bottom
2225 | var scrollHeight = $('body').height()
2226 |
2227 | if (typeof offset != 'object') offsetBottom = offsetTop = offset
2228 | if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element)
2229 | if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)
2230 |
2231 | var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)
2232 |
2233 | if (this.affixed != affix) {
2234 | if (this.unpin != null) this.$element.css('top', '')
2235 |
2236 | var affixType = 'affix' + (affix ? '-' + affix : '')
2237 | var e = $.Event(affixType + '.bs.affix')
2238 |
2239 | this.$element.trigger(e)
2240 |
2241 | if (e.isDefaultPrevented()) return
2242 |
2243 | this.affixed = affix
2244 | this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null
2245 |
2246 | this.$element
2247 | .removeClass(Affix.RESET)
2248 | .addClass(affixType)
2249 | .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
2250 | }
2251 |
2252 | if (affix == 'bottom') {
2253 | this.$element.offset({
2254 | top: scrollHeight - height - offsetBottom
2255 | })
2256 | }
2257 | }
2258 |
2259 |
2260 | // AFFIX PLUGIN DEFINITION
2261 | // =======================
2262 |
2263 | function Plugin(option) {
2264 | return this.each(function () {
2265 | var $this = $(this)
2266 | var data = $this.data('bs.affix')
2267 | var options = typeof option == 'object' && option
2268 |
2269 | if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
2270 | if (typeof option == 'string') data[option]()
2271 | })
2272 | }
2273 |
2274 | var old = $.fn.affix
2275 |
2276 | $.fn.affix = Plugin
2277 | $.fn.affix.Constructor = Affix
2278 |
2279 |
2280 | // AFFIX NO CONFLICT
2281 | // =================
2282 |
2283 | $.fn.affix.noConflict = function () {
2284 | $.fn.affix = old
2285 | return this
2286 | }
2287 |
2288 |
2289 | // AFFIX DATA-API
2290 | // ==============
2291 |
2292 | $(window).on('load', function () {
2293 | $('[data-spy="affix"]').each(function () {
2294 | var $spy = $(this)
2295 | var data = $spy.data()
2296 |
2297 | data.offset = data.offset || {}
2298 |
2299 | if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
2300 | if (data.offsetTop != null) data.offset.top = data.offsetTop
2301 |
2302 | Plugin.call($spy, data)
2303 | })
2304 | })
2305 |
2306 | }(jQuery);
2307 |
--------------------------------------------------------------------------------
/data/public/js/render-ld.js:
--------------------------------------------------------------------------------
1 | /* global rdf:false, React:false */
2 | 'use strict';
3 |
4 | var dcVocab = {
5 | "@context": {
6 | "dc": "http://purl.org/dc/elements/1.1/",
7 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
8 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#"
9 | },
10 | "@graph": [
11 | {
12 | "@id": "dc:contributor",
13 | "@type": "rdf:Property",
14 | "rdfs:label": {
15 | "@language": "en",
16 | "@value": "Contributor"
17 | }
18 | },
19 | {
20 | "@id": "dc:source",
21 | "@type": "rdf:Property",
22 | "rdfs:label": {
23 | "@language": "en",
24 | "@value": "Source"
25 | }
26 | },
27 | {
28 | "@id": "dc:language",
29 | "@type": "rdf:Property",
30 | "rdfs:label": {
31 | "@language": "en",
32 | "@value": "Language"
33 | }
34 | },
35 | {
36 | "@id": "dc:creator",
37 | "@type": "rdf:Property",
38 | "rdfs:label": {
39 | "@language": "en",
40 | "@value": "Creator"
41 | }
42 | },
43 | {
44 | "@id": "dc:type",
45 | "@type": "rdf:Property",
46 | "rdfs:label": {
47 | "@language": "en",
48 | "@value": "Type"
49 | }
50 | },
51 | {
52 | "@id": "dc:title",
53 | "@type": "rdf:Property",
54 | "rdfs:label": [{
55 | "@language": "en",
56 | "@value": "Title"
57 | },{
58 | "@language": "de",
59 | "@value": "Titel"
60 | }]
61 | },
62 | {
63 | "@id": "dc:coverage",
64 | "@type": "rdf:Property",
65 | "rdfs:label": {
66 | "@language": "en",
67 | "@value": "Coverage"
68 | }
69 | },
70 | {
71 | "@id": "dc:format",
72 | "@type": "rdf:Property",
73 | "rdfs:label": {
74 | "@language": "en",
75 | "@value": "Format"
76 | }
77 | },
78 | {
79 | "@id": "dc:relation",
80 | "@type": "rdf:Property",
81 | "rdfs:label": {
82 | "@language": "en",
83 | "@value": "Relation"
84 | }
85 | },
86 | {
87 | "@id": "dc:date",
88 | "@type": "rdf:Property",
89 | "rdfs:label": {
90 | "@language": "en",
91 | "@value": "Date"
92 | }
93 | },
94 | {
95 | "@id": "dc:identifier",
96 | "@type": "rdf:Property",
97 | "rdfs:label": {
98 | "@language": "en",
99 | "@value": "Identifier"
100 | }
101 | },
102 | {
103 | "@id": "dc:publisher",
104 | "@type": "rdf:Property",
105 | "rdfs:label": {
106 | "@language": "en",
107 | "@value": "Publisher"
108 | }
109 | },
110 | {
111 | "@id": "dc:subject",
112 | "@type": "rdf:Property",
113 | "rdfs:label": {
114 | "@language": "en",
115 | "@value": "Subject"
116 | }
117 | },
118 | {
119 | "@id": "dc:description",
120 | "@type": "rdf:Property",
121 | "rdfs:label": {
122 | "@language": "en",
123 | "@value": "Description"
124 | }
125 | },
126 | {
127 | "@id": "dc:rights",
128 | "@type": "rdf:Property",
129 | "rdfs:label": {
130 | "@language": "en",
131 | "@value": "Rights"
132 | }
133 | }
134 | ]
135 | };
136 |
137 |
138 | jsonld.promises.flatten = function (json, context) {
139 | return new Promise(function (resolve, reject) {
140 | jsonld.flatten(json, context, function (error, result) {
141 | if (error != null) {
142 | reject(error);
143 | } else {
144 | resolve(result);
145 | }
146 | });
147 | });
148 | };
149 |
150 | jsonld.promises.expand = function (json) {
151 | return new Promise(function (resolve, reject) {
152 | jsonld.expand(json, function (error, result) {
153 | if (error != null) {
154 | reject(error);
155 | } else {
156 | resolve(result);
157 | }
158 | });
159 | });
160 | };
161 |
162 |
163 | var JsonLdSubjectTable = React.createClass({
164 | getInitialState: function () {
165 | var
166 | self = this,
167 | expandVocabs = [];
168 |
169 | if ('vocabs' in this.props) {
170 | this.props.vocabs.forEach(function (vocab) {
171 | expandVocabs.push(jsonld.promises.expand(vocab));
172 | });
173 | }
174 |
175 | Promise.all(expandVocabs)
176 | .then(function (results) {
177 | self.setState({vocab: Array.prototype.concat.apply([], results)});
178 | });
179 |
180 | return {vocab: []};
181 | },
182 | render: function () {
183 | var
184 | self = this,
185 | id,
186 | head,
187 | body,
188 | objects,
189 | rows = [];
190 |
191 | var getIriLabel = function (iri) {
192 | var
193 | getTermRegEx = /(#|\/)([^#\/]*)$/,
194 | parts = getTermRegEx.exec(iri);
195 |
196 | if (parts == null || parts.length === 0) {
197 | return null;
198 | }
199 |
200 | return parts[parts.length-1];
201 | };
202 |
203 | var getPredicateLabel = function (iri) {
204 | var
205 | vocab = self.state.vocab,
206 | subject,
207 | predicate = 'http://www.w3.org/2000/01/rdf-schema#label',
208 | objects,
209 | language = navigator.language || navigator.userLanguage;
210 |
211 | for(var i=0; i= 0) {
44 | self.setPage(self.state.page + direction);
45 | }
46 | },
47 | updateCache: function () {
48 | var self = this;
49 |
50 | if (self.state.page > 0) {
51 | self.loadResults(self.state.page-1);
52 | }
53 |
54 | self.loadResults(self.state.page+1);
55 | },
56 | updateDetails: function () {
57 | var self = this;
58 |
59 | return Promise.all(self.state.data.map(function (row) {
60 | return self.loadDetails(row['@id']);
61 | }))
62 | .then(function (allDetails) {
63 | return Promise.all(allDetails.map(function (details) {
64 | if (!('dc:relation' in details)) {
65 | return Promise.resolve();
66 | }
67 |
68 | return self.loadDetails(details['dc:relation']['@id']);
69 | }));
70 | });
71 | },
72 | loadResults: function(page) {
73 | var
74 | self = this,
75 | searchString = $('#search-string').val();
76 |
77 | var doRequest = function (searchString, page) {
78 | return new Promise(function (resolve, reject) {
79 | var url = '/query?q=' + encodeURIComponent(searchString) + '&page=' + (page + 1);
80 |
81 | $.ajax({
82 | url: url,
83 | headers: {Accept: 'application/ld+json'},
84 | success: function (data) {
85 | jsonld.promises().compact(data, context)
86 | .then(function (data) {
87 | var cache = self.state.cache;
88 |
89 | if ('@graph' in data) {
90 | data = data['@graph'];
91 | } else {
92 | data = [data];
93 | }
94 |
95 | cache[page] = data;
96 |
97 | self.setState({cache: cache});
98 |
99 | resolve(data);
100 | })
101 | },
102 | error: function () {
103 | reject();
104 | }
105 | });
106 | });
107 | };
108 |
109 | if (page in self.state.cache) {
110 | return Promise.resolve(self.state.cache[page]);
111 | } else {
112 | return doRequest(searchString, page);
113 | }
114 | },
115 | loadDetails: function (url) {
116 | var self = this;
117 |
118 | var doRequest = function (url) {
119 | return new Promise(function (resolve, reject) {
120 | $.ajax({
121 | url: url,
122 | headers: {Accept: 'application/ld+json'},
123 | success: function (data) {
124 | jsonld.promises().compact(data, context)
125 | .then(function (data) {
126 | var details = self.state.details;
127 |
128 | details[url] = data;
129 |
130 | self.setState({details: details});
131 |
132 | resolve(data);
133 | })
134 | },
135 | error: function () {
136 | reject();
137 | }
138 | });
139 | });
140 | };
141 |
142 | if (url in self.state.details) {
143 | return Promise.resolve(self.state.details[url]);
144 | } else {
145 | return doRequest(url);
146 | }
147 | },
148 | componentDidMount: function() {
149 | var self = this;
150 |
151 | $('#search').on('click', self.search);
152 | $('#search-string').keypress(function (event) {
153 | if (event.which == 13) {
154 | self.search();
155 | }
156 | });
157 | },
158 | componentWillUnmount: function() {
159 | var self = this;
160 |
161 | $('#search').off('click', self.search);
162 | },
163 | render: function() {
164 | var self = this;
165 |
166 | var value = function (property) {
167 | if (typeof property === 'string') {
168 | return property;
169 | } else if ('@value' in property) {
170 | return property['@value'];
171 | } else if ('@id' in property) {
172 | return property['@id'];
173 | }
174 | };
175 |
176 | var getTerm = function (iri) {
177 | var
178 | getTermRegEx = /(#|\/)([^#\/]*)$/,
179 | parts = getTermRegEx.exec(iri);
180 |
181 | if (parts == null || parts.length === 0) {
182 | return null;
183 | }
184 |
185 | return parts[parts.length-1];
186 | };
187 |
188 | var link = function (ref, label) {
189 | if (ref === '') {
190 | return '';
191 | }
192 |
193 | if (label == null) {
194 | label = getTerm(ref);
195 | }
196 |
197 | return React.DOM.a({href: ref}, label);
198 | };
199 |
200 |
201 | var detail = function (id, property) {
202 | if (!(id in self.state.details)) {
203 | return '';
204 | }
205 |
206 | var details = self.state.details[id];
207 |
208 | if ('@graph' in details) {
209 | details = details['@graph'];
210 | }
211 |
212 | if (Array.isArray(details)) {
213 | details.forEach(function (subject) {
214 | if (subject['@id'].indexOf('_:') !== 0) {
215 | details = subject;
216 | }
217 | });
218 | }
219 |
220 | if (!(property in details)) {
221 | return '';
222 | }
223 |
224 | return details[property];
225 | };
226 |
227 | var rows = self.state.data.map(function (row, index) {
228 | var
229 | name = [];
230 |
231 | if ('givenName' in row) {
232 | name.push(row.givenName);
233 | }
234 |
235 | if ('familyName' in row) {
236 | name.push(row.familyName);
237 | }
238 |
239 | name = name.join(' ');
240 |
241 | return React.DOM.tr({key: row['@id']},
242 | React.DOM.td({}, self.state.page*self.props.resultsPerPage + index + 1),
243 | React.DOM.td({},
244 | React.DOM.a({href: row['@id']}, name)),
245 | React.DOM.td({}, value(detail(row['@id'], 'jobTitle')))
246 | );
247 | });
248 |
249 | var table = React.DOM.table({className: 'table table-bordered', id: 'results-table'},
250 | React.DOM.thead({},
251 | React.DOM.tr({},
252 | React.DOM.th({className: 'col-xs-1'}, 'No.'),
253 | React.DOM.th({className: 'col-xs-4'}, 'Name'),
254 | React.DOM.th({className: 'col-xs-1'}, 'Job')
255 | )),
256 | React.DOM.tbody({}, rows));
257 |
258 | var searchString = $('#search-string').val();
259 |
260 | var noEntries = React.DOM.p({}, 'no hits found');
261 |
262 | var pageIsNotEmpty = function (page) {
263 | if (!(page in self.state.cache)) {
264 | return false;
265 | }
266 |
267 | // there is at least one context entry, so check for a subject
268 | return '@id' in self.state.cache[page][0];
269 | };
270 |
271 | var pager = React.DOM.nav({},
272 | React.DOM.ul({className: 'pager'},
273 | pageIsNotEmpty(self.state.page-1) ? React.DOM.li({className: 'previous'},
274 | React.DOM.a({href: '#', onClick: self.turnPage.bind(self, -1)}, 'Previous')) : null,
275 | pageIsNotEmpty(self.state.page+1) ? React.DOM.li({className: 'next'},
276 | React.DOM.a({href: '#', onClick: self.turnPage.bind(self, +1)}, 'Next')) : null));
277 |
278 | return React.DOM.div({},
279 | self.state.data.length > 0 ? table : searchString != '' ? noEntries : '',
280 | pager);
281 | }
282 | });
283 |
284 |
285 | window.ui = {
286 | createResultTable: React.createFactory(ResultTable)
287 | };
288 |
--------------------------------------------------------------------------------
/data/public/js/sticky.js:
--------------------------------------------------------------------------------
1 | function sticky_relocate() {
2 | var window_top = $(window).scrollTop();
3 | var div_top = $('#sticky-anchor').offset().top;
4 | if (window_top > div_top) {
5 | $('#sticky').addClass('stick');
6 | $('#sticky-anchor').height($('#sticky').outerHeight());
7 | } else {
8 | $('#sticky').removeClass('stick');
9 | $('#sticky-anchor').height(0);
10 | }
11 | }
12 |
13 | $(function() {
14 | $(window).scroll(sticky_relocate);
15 | sticky_relocate();
16 | });
17 |
--------------------------------------------------------------------------------
/data/public/rdf2h/matchers.ttl:
--------------------------------------------------------------------------------
1 | @base .
2 | @prefix rdf: .
3 | @prefix rdfs: .
4 | @prefix r2h: .
5 | @prefix dc: .
6 | @prefix s: .
7 | @prefix foaf: .
8 | @prefix zzm: .
9 | @prefix zz2h: .
10 | @prefix owl: .
11 | @prefix r2hp: .
12 |
13 | a r2h:Matcher ;
14 | r2h:triplePattern [
15 | r2h:subject r2h:this;
16 | r2h:predicate s:headline;
17 | ];
18 | r2h:template [
19 | r2h:context zzm:HtmlTitle;
20 | r2h:mustache '''
21 | {{s:headline}}
22 | '''
23 | ];
24 | r2h:before r2hp:generic.
25 |
26 |
27 | r2hp:generic r2h:before , , , .
28 |
29 | a r2h:Matcher ;
30 | r2h:triplePattern [
31 | r2h:subject r2h:this;
32 | r2h:predicate dc:title;
33 | ];
34 | r2h:template [
35 | r2h:context r2h:Default;
36 | r2h:mustache '''{{dc:title}}
37 | {{{:continue}}}'''
38 | ];
39 | r2h:before .
40 |
41 | a r2h:Matcher ;
42 | r2h:triplePattern [
43 | r2h:subject r2h:this;
44 | r2h:predicate s:headline;
45 | ];
46 | r2h:template [
47 | r2h:context r2h:Default;
48 | r2h:mustache '''{{s:headline}}
49 | {{{:continue}}}'''
50 | ];
51 | r2h:before .
52 |
53 | a r2h:Matcher ;
54 | r2h:triplePattern [
55 | r2h:subject r2h:this;
56 | r2h:predicate s:givenName;
57 | ], [
58 | r2h:subject r2h:this;
59 | r2h:predicate s:familyName;
60 | ];
61 | r2h:template [
62 | r2h:context r2h:Default;
63 | r2h:mustache '''Name: {{s:givenName}} {{s:familyName}}
64 | {{{:continue}}}'''
65 | ];
66 | r2h:before .
67 |
68 | a r2h:Matcher ;
69 | r2h:triplePattern [
70 | r2h:subject r2h:this;
71 | r2h:predicate s:address;
72 | ];
73 | r2h:template [
74 | r2h:context r2h:Default;
75 | r2h:mustache '''Address: {{{:render s:address}}}
76 | {{{:continue}}}'''
77 | ];
78 | r2h:before .
79 |
80 | a r2h:Matcher ;
81 | r2h:triplePattern [
82 | r2h:subject r2h:this;
83 | r2h:predicate rdf:type;
84 | ];
85 | r2h:template [
86 | r2h:context r2h:Default;
87 | r2h:mustache '''
88 |
89 |
90 |
91 | Resource: {{.}}
92 | a {{#rdf:type}}{{.}} {{/rdf:type}}
93 |
94 |
95 | {{{:continue}}}'''
96 | ];
97 | r2h:before r2hp:fallback.
98 |
99 | r2hp:highly-specific r2h:before .
100 |
101 | a r2h:Matcher ;
102 | r2h:triplePattern [
103 | r2h:subject r2h:this;
104 | r2h:predicate owl:sameAs;
105 | ];
106 | r2h:template [
107 | r2h:context r2h:Default;
108 | r2h:mustache '''
109 | {{{:continue}}}
110 | {{#owl:sameAs}}
111 |
112 |
113 |
118 |
119 |
120 | sameAs data should appear here
122 |
123 |
124 |
125 |
126 |
138 | {{/owl:sameAs}}
139 | '''];
140 | r2h:before .
141 |
142 |
143 | a r2h:Matcher ;
144 | rdfs:comment '''Renders the same as resource by using all matchers after r2hp:specific,
145 | avoiding infinite owl:sameAs expansions as well as the header and footer''';
146 | r2h:template [
147 | r2h:context r2h:SameAsBlock;
148 | r2h:mustache '''
149 | {{{:continue r2h:Default}}}
150 | '''
151 | ];
152 | r2h:before r2hp:specific.
153 |
154 | r2hp:fallback r2h:before .
155 |
156 | a r2h:Matcher ;
157 | r2h:template [
158 | rdfs:comment '''Renders a full page, delegating to matchers in the default
159 | context for the body, and to zzm:HtmlHead for things like the html title''';
160 | r2h:context zzm:FullPage;
161 | r2h:mustache '''
162 | {{{@prefix r2h: }}}
163 | {{{@prefix zzm: }}}
164 |
165 | {{{:render . zzm:HtmlHead}}}
166 |
167 |
168 | {{{:render . zzm:HtmlBody}}}
169 |
170 | '''
171 | ];
172 | r2h:template [
173 | r2h:context zzm:HtmlBody;
174 | r2h:mustache '''
175 | {{{:render . r2h:Default}}}'''
188 | ];
189 | r2h:template [
190 | r2h:context zzm:HtmlHead;
191 | r2h:mustache '''
192 |
193 |
194 |
196 | {{{:render . zzm:HtmlTitle}}}
197 | '''
198 | ];
199 | r2h:template [
200 | r2h:context zzm:HtmlTitle;
201 | r2h:mustache '''
202 | Resource {{.}}
203 | '''
204 | ];
205 | r2h:template [
206 | r2h:context r2h:Default;
207 | r2h:javaScript '''function(n) {
208 | //alert(n.graph);
209 | //alert(n.node);
210 |
211 | var prefixes = [{url: "http://schema.org/", shortcut: "s:"},
212 | {url: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", shortcut: "rdf:"},
213 | {url: "https://gont.ch/", shortcut: "gont:"},
214 | {url: "http://www.w3.org/2004/02/skos/core#", shortcut: "skos:"},
215 | {url: "http://www.w3.org/2002/07/owl#", shortcut: "owl:"},
216 | {url: "http://dbpedia.org/resource/", shortcut: "dbr:"},
217 | {url: "http://dbpedia.org/class/yago/", shortcut: "yago:"},
218 | {url: "http://xmlns.com/foaf/0.1/", shortcut: "foaf:"},
219 | {url: "http://www.wikidata.org/entity/", shortcut: "wikidata:"},
220 | {url: "http://dbpedia.org/ontology/", shortcut: "dbo:"},
221 | {url: "http://purl.org/dc/terms/", shortcut: "dct:"},
222 | {url: "http://www.w3.org/2000/01/rdf-schema#", shortcut: "rdfs:"}];
223 | var result = "";
224 | var first = 0;
225 | var firstDash = -1;
226 | var predicatesArray = [];
227 | var blankBack = [];
228 |
229 | var nodesToRender = [];
230 | var renderedNodesToId = new Object();
231 | nodesToRender.push(n.node);
232 | while (nodesToRender.length > 0) {
233 | var node = nodesToRender.pop();
234 | var s = n.graph.filter(function(t) {return t.subject.equals(node)}).toArray();
235 | if (s.length === 0) continue;
236 | if (renderedNodesToId[node.toString()]) continue;
237 | renderedNodesToId[node.toString()] = true;
238 | if (first > 0) {
239 | result += '
';
240 | if (blankBack[first-1] !== undefined) {
241 | result += ''+blankBack[first-1]+'
';
242 | } else {
243 | var newSub = s[0].subject.nominalValue;
244 | result += '';
245 | }
246 | predicatesArray = [];
247 | firstDash = -1;
248 | }
249 | result += '';
250 | first++;
251 | s.forEach(function(t) {
252 | var objectHtml;
253 |
254 | var prefix = "";
255 | var predicate = t.predicate.toString();
256 | var link = predicate;
257 | prefixes.forEach(function(p) {
258 | var predicateHtml = predicate.split(p.url);
259 | if (predicateHtml[0] === "") {
260 | prefix = p.shortcut;
261 | predicate = predicateHtml[1];
262 | }
263 | });
264 |
265 | if ((t.object.interfaceName != "Literal") && (!renderedNodesToId[t.object.toString()])) {
266 | nodesToRender.push(t.object);
267 | if (t.object.interfaceName == "BlankNode") {
268 | objectHtml = ''+t.object+'';
269 | returnObjectLink = ''+t.object+'';
270 | blankBack.push(''+prefix+''+predicate+' | '+returnObjectLink+' |
');
271 | } else {
272 | var objectPrefix = "";
273 | var object = t.object.toString().replace(/&/g,"&");
274 | var objectLink = object;
275 | prefixes.forEach(function(p) {
276 | var objectSplit = object.split(p.url);
277 | if (objectSplit[0] === "") {
278 | objectPrefix = p.shortcut;
279 | object = objectSplit[1];
280 | }
281 | });
282 | if (objectPrefix === "") {
283 | objectHtml = ''+object+'';
284 | } else {
285 | objectHtml = ''+objectPrefix+''+object+'';
286 | }
287 | }
288 |
289 | } else {
290 | objectHtml = t.object;
291 | }
292 |
293 | var isNew = 1;
294 | for (var i = 0; i < predicatesArray.length; i++) {
295 | if (predicatesArray[i] === predicate) {
296 | isNew = 0;
297 | break;
298 | }
299 | }
300 |
301 | predicatesArray.push(predicate);
302 | if (isNew) {
303 | if (firstDash === 0) {
304 | result = result.substring(0, result.length - 24); //XXX
305 | result += '
'
306 | }
307 | result += ''+prefix+''+predicate+' | '+objectHtml+'
| ';
308 | firstDash = 1;
309 | } else {
310 | if (firstDash === 1) {
311 | result = result.substring(0, result.length - 11); //XXX
312 | result += '
';
313 | firstDash = 0;
314 | }
315 | result += '
| '+objectHtml+'
| ';
316 | }
317 | });
318 | result += '
';
319 | };
320 |
321 | return result;
322 | }'''
323 | ].
324 |
--------------------------------------------------------------------------------
/data/public/rdf2h/site-matchers.ttl:
--------------------------------------------------------------------------------
1 | # This file is typically overridden in site-specific configurations
--------------------------------------------------------------------------------
/data/public/search/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/data/public/sparql/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | YASGUI
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/data/scripts/fuseki-config.ttl:
--------------------------------------------------------------------------------
1 | @prefix : <#> .
2 | @prefix fuseki: .
3 | @prefix rdf: .
4 |
5 | @prefix rdfs: .
6 | @prefix tdb: .
7 | @prefix ja: .
8 |
9 | [] rdf:type fuseki:Server;
10 | fuseki:services (
11 | <#tbbtService>
12 | ).
13 |
14 | <#tbbtService> rdf:type fuseki:Service;
15 | fuseki:name "tbbt";
16 | fuseki:serviceQuery "sparql";
17 | fuseki:serviceQuery "query";
18 | fuseki:serviceUpdate "update";
19 | fuseki:serviceUpload "upload";
20 | fuseki:serviceReadWriteGraphStore "data";
21 | fuseki:serviceReadGraphStore "get";
22 | fuseki:dataset <#tbbtDataset>;
23 | .
24 |
25 | <#tbbtDataset> rdf:type ja:RDFDataset;
26 | ja:namedGraph [
27 | ja:graphName ;
28 | ja:graph [ a ja:MemoryModel;
29 | ja:content [
30 | ja:externalContent
31 | ];
32 | ];
33 | ];
34 | .
35 |
--------------------------------------------------------------------------------
/data/scripts/start-fuseki:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | export FUSEKI_HOME=/opt/jena-fuseki/
4 |
5 | $FUSEKI_HOME/fuseki-server --config=fuseki-config.ttl
--------------------------------------------------------------------------------
/data/sparql/search.sparql:
--------------------------------------------------------------------------------
1 | PREFIX s:
2 |
3 | CONSTRUCT {
4 | ?s s:givenName ?givenName;
5 | s:familyName ?familyName;
6 | } WHERE
7 |
8 | {
9 | GRAPH ?g {
10 | ?s s:givenName ?givenName;
11 | s:familyName ?familyName;
12 | }
13 | FILTER ( CONTAINS(?givenName, %searchstring%) || CONTAINS(?familyName, %searchstring%) )
14 | }
--------------------------------------------------------------------------------
/data/templates/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
404 - Not Found
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/data/templates/graph.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = function (config) {
4 |
5 | global.Promise = require('es6-promise').Promise;
6 |
7 | var
8 | bodyParser = require('body-parser'),
9 | express = require('express'),
10 | expressUtils = require('express-utils'),
11 | handlerMiddleware = require('./lib/handler-middleware'),
12 | patchHeadersMiddleware = require('./lib/patch-headers-middleware'),
13 | morgan = require('morgan'),
14 | path = require('path'),
15 | bunyan = require('bunyan'),
16 | renderHtmlMiddleware = require('./lib/render-html-middleware'),
17 | sparqlProxy = require('./lib/sparql-proxy'),
18 | sparqlSearch = require('./lib/sparql-search');
19 |
20 |
21 | global.log = bunyan.createLogger({
22 | name: config.app,
23 | level: config.logger.level
24 | });
25 |
26 | if (!('init' in config)) {
27 | config.init = function () {
28 | return Promise.resolve();
29 | };
30 | }
31 |
32 | return config.init()
33 | .then(function () {
34 | var
35 | app = express(),
36 | handler = new config.HandlerClass(config.handlerOptions);
37 |
38 | if ('expressSettings' in config) {
39 | for (var key in config.expressSettings) {
40 | app.set(key, config.expressSettings[key]);
41 | }
42 | }
43 |
44 | app.use(morgan('combined'));
45 | app.use(patchHeadersMiddleware(config.patchHeaders));
46 | app.use(bodyParser.text());
47 | app.use(bodyParser.urlencoded({extended: false}));
48 |
49 | // instance files
50 | if (__dirname !== process.cwd()) {
51 | app.use(express.static(path.join(process.cwd(), './data/public/')));
52 | }
53 |
54 | // trifid files
55 | app.use(express.static(path.join(__dirname, './data/public/')));
56 |
57 | // yasgui files
58 | app.use('/sparql/dist/', express.static(path.resolve(require.resolve('yasgui'), '../../dist/')))
59 |
60 | app.use(expressUtils.absoluteUrl());
61 |
62 | if ('sparqlProxy' in config) {
63 | app.use(config.sparqlProxy.path, sparqlProxy(config.sparqlProxy.options));
64 | }
65 |
66 | if ('sparqlSearch' in config) {
67 | app.use(config.sparqlSearch.path, sparqlSearch(config.sparqlSearch.options));
68 | }
69 |
70 | app.use(renderHtmlMiddleware(handler));
71 | app.use(handlerMiddleware(handler));
72 | app.listen(config.listener.port, config.listener.hostname);
73 |
74 | log.info('listening on hostname:port: ' + config.listener.hostname + ':' + config.listener.port);
75 | })
76 | .catch(function (error) {
77 | console.error(error.stack);
78 | });
79 |
80 | }
--------------------------------------------------------------------------------
/lib/abstract-store.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | var AbstractStore = function () {
5 | var self = this;
6 |
7 | this.match = function (iri, subject, predicate, object, callback, limit) {
8 | self.graph(iri, function (graph) {
9 | if (graph == null) {
10 | callback(null);
11 | } else {
12 | callback(graph.match(subject, predicate, object, limit));
13 | }
14 | });
15 | };
16 |
17 | this.merge = function (iri, graph, callback) {
18 | self.graph(iri, function (existing) {
19 | var merged = graph;
20 |
21 | if (existing != null) {
22 | merged = existing.addAll(graph);
23 | }
24 |
25 | self.add(merged, callback);
26 | });
27 | };
28 |
29 | this.remove = function (iri, graph, callback) {
30 | self.graph(iri, function (existing) {
31 | if (existing != null) {
32 | self.add(rdf.Graph.difference(existing, graph), function (added) {
33 | callback(added != null);
34 | });
35 | } else {
36 | callback(true);
37 | }
38 | });
39 | };
40 |
41 | this.removeMatches = function (iri, subject, predicate, object, callback) {
42 | self.graph(iri, function (existing) {
43 | if (existing != null) {
44 | self.add(iri, existing.removeMatches(subject, predicate, object), function (added) {
45 | callback(added != null);
46 | });
47 | } else {
48 | callback(true);
49 | }
50 | });
51 | };
52 | };
53 |
54 | module.exports = AbstractStore;
--------------------------------------------------------------------------------
/lib/file-store.js:
--------------------------------------------------------------------------------
1 | /* global rdf:true */
2 | 'use strict';
3 |
4 | var
5 | fs = require('fs'),
6 | path = require('path'),
7 | url = require('url'),
8 | AbstractStore = require('./abstract-store');
9 |
10 |
11 | var FileStore = function (rdf, options) {
12 | if (options == null) {
13 | options = {};
14 | }
15 |
16 | var self = this;
17 |
18 | this.parse = 'parser' in options ? options.parse : rdf.parseTurtle;
19 | this.serialize = 'serialize' in options ? options.serialize : rdf.serializeNTriples;
20 | this.path = 'path' in options ? options.path : '.';
21 | this.graphFile = 'graphFile' in options ? options.graphFile : function (p) {
22 | return p.pathname.split('/').slice(1).join('_') + '.ttl';
23 | };
24 |
25 | var graphPath = function (iri) {
26 | var parsed = url.parse(iri);
27 |
28 | return path.join(self.path, self.graphFile(parsed));
29 | };
30 |
31 | var graphExists = function (iri) {
32 | return fs.existsSync(graphPath(iri));
33 | };
34 |
35 | this.graph = function (iri, callback) {
36 | if (!graphExists(iri)) {
37 | return callback(null);
38 | }
39 |
40 | self.parse(
41 | fs.readFileSync(graphPath(iri)).toString(),
42 | callback,
43 | iri);
44 | };
45 |
46 | this.add = function (iri, graph, callback) {
47 | self.serialize(
48 | graph,
49 | function (serialized) {
50 | fs.writeFileSync(graphPath(iri), serialized);
51 |
52 | callback(graph);
53 | }, iri);
54 | };
55 |
56 | this.delete = function (iri, callback) {
57 | if (graphExists(iri)) {
58 | fs.unlink(graphPath(iri));
59 | }
60 |
61 | callback(true);
62 | };
63 | };
64 |
65 | FileStore.prototype = new AbstractStore();
66 | FileStore.prototype.constructor = FileStore;
67 |
68 |
69 | module.exports = function (rdf) {
70 | rdf.FileStore = FileStore.bind(null, rdf);
71 | };
72 |
73 | module.exports.store = FileStore;
--------------------------------------------------------------------------------
/lib/graph-split.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | var
5 | url = require('url'),
6 | AbstractStore = require('./abstract-store');
7 |
8 |
9 | var SplitStore = function (rdf, options) {
10 | var
11 | store = new rdf.InMemoryStore();
12 |
13 | if (!('graph' in options)) {
14 | throw 'graph option missing';
15 | }
16 |
17 | if (!('split' in options)) {
18 | throw 'split option missing';
19 | }
20 |
21 | options.split(options.graph, new rdf.promise.Store(store));
22 |
23 | var mapIri = function (iri) {
24 | var parsed = url.parse(iri);
25 |
26 | if ('hostname' in options) {
27 | parsed.hostname = options.hostname;
28 | }
29 |
30 | if ('port' in options) {
31 | parsed.port = options.port;
32 | }
33 |
34 | delete parsed.host;
35 |
36 | return url.format(parsed);
37 | };
38 |
39 | this.graph = function (iri, callback) {
40 | store.graph(mapIri(iri), callback);
41 | };
42 |
43 | this.add = function (iri, graph, callback) {
44 | callback(null, 'read only store');
45 | };
46 |
47 | this.delete = function (iri, callback) {
48 | callback(false, 'read only store');
49 | };
50 | };
51 |
52 |
53 | SplitStore.prototype = new AbstractStore();
54 | SplitStore.constructor = SplitStore;
55 |
56 |
57 | module.exports = function (rdf) {
58 | return {
59 | SplitStore: SplitStore.bind(null, rdf)
60 | };
61 | };
--------------------------------------------------------------------------------
/lib/handler-middleware.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | module.exports = function (handler) {
5 | return function (req, res, next) {
6 | var iri = req.absoluteUrl();
7 |
8 | if (req.method === 'GET') {
9 | handler.get(req, res, next, iri)
10 | } else {
11 | next();
12 | }
13 | };
14 | };
--------------------------------------------------------------------------------
/lib/ldp-module-handler.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var
4 | url = require('url'),
5 | Ldp = require('ldp');
6 |
7 |
8 | module.exports = function (options) {
9 | this.store = new options.StoreClass(options.storeOptions);
10 | this.ldp = new Ldp(options.rdf, this.store);
11 |
12 | var mapIri = function (iri) {
13 | var parsed = url.parse(iri);
14 |
15 | if ('hostname' in options) {
16 | parsed.hostname = options.hostname;
17 | }
18 |
19 | if ('port' in options) {
20 | parsed.port = options.port;
21 | }
22 |
23 | delete parsed.host;
24 |
25 | return url.format(parsed);
26 | };
27 |
28 | this.get = function (req, res, next, iri) {
29 | this.ldp.get(req, res, next, mapIri(iri));
30 | };
31 | };
--------------------------------------------------------------------------------
/lib/patch-headers-middleware.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | module.exports = function (options) {
5 | if (options == null) {
6 | options = {};
7 | }
8 |
9 | if (!('patchResponse' in options)) {
10 | options.patchResponse = function (res, headers) { return headers; };
11 | }
12 |
13 | return function (req, res, next) {
14 | var writeHead = res.writeHead
15 |
16 | res.writeHead = function (statusCode, headers) {
17 | this.statusCode = statusCode;
18 |
19 | if (headers == null) {
20 | headers = {};
21 | }
22 |
23 | headers = options.patchResponse(this, headers);
24 |
25 | writeHead.bind(this)(statusCode, headers);
26 | };
27 |
28 | next();
29 | };
30 | };
--------------------------------------------------------------------------------
/lib/render-html-middleware.js:
--------------------------------------------------------------------------------
1 | /* global log:false */
2 |
3 | 'use strict';
4 |
5 | var
6 | fs = require('fs'),
7 | path = require('path'),
8 | streamBuffers = require("stream-buffers");
9 |
10 |
11 | require('express-negotiate');
12 |
13 | function fileContent (filename) {
14 | try {
15 | return fs.readFileSync(filename).toString()
16 | } catch (err) {
17 | return null
18 | }
19 | }
20 |
21 | module.exports = function (handler) {
22 | var template = fileContent(path.join(process.cwd(), 'data/templates/graph.html')) ||
23 | fileContent(path.join(__dirname, '../data/templates/graph.html'))
24 |
25 | var notFoundPage = fileContent(path.join(process.cwd(), 'data/templates/404.html')) ||
26 | fileContent(path.join(__dirname, '../data/templates/404.html'))
27 |
28 | var handlerGetRequest = function (iri, mimetype) {
29 | return new Promise(function (resolve, reject) {
30 | var contentBuffer = new streamBuffers.WritableStreamBuffer();
31 |
32 | contentBuffer.setHeader = function () {};
33 | contentBuffer.writeHead = function (statusCode) { this.statusCode = statusCode; };
34 |
35 | contentBuffer.on('close', function () {
36 | if (contentBuffer.statusCode === 404) {
37 | resolve(null);
38 | } else {
39 | resolve(contentBuffer.getContents().toString());
40 | }
41 | });
42 |
43 | contentBuffer.on('error', function () {
44 | reject();
45 | });
46 |
47 | handler.get(
48 | {headers: {accept: mimetype}},
49 | contentBuffer,
50 | function () { resolve(null); },
51 | iri);
52 | });
53 | };
54 |
55 | return function (req, res, next) {
56 | req.negotiate({
57 | 'html': function() {
58 | var
59 | iri = req.absoluteUrl();
60 |
61 | if (req.method === 'GET') {
62 | log.info({script: __filename}, 'handle GET request for IRI <' + iri + '>');
63 |
64 | handlerGetRequest(iri, 'application/ld+json')
65 | .then(function (content) {
66 | if (content == null || Object.keys(JSON.parse(content)).length === 0) {
67 | res.writeHead(404);
68 | res.end(notFoundPage);
69 | } else {
70 | // we have already the content, so let's inject it to avoid another round trip
71 | var body = template.replace('%graph%', content);
72 |
73 | res.end(body);
74 | }
75 | })
76 | .catch(function () {
77 | res.writeHead(500);
78 | res.end();
79 | });
80 | } else {
81 | next();
82 | }
83 | },
84 | 'default': function () {
85 | next();
86 | }
87 | });
88 | };
89 | };
90 |
--------------------------------------------------------------------------------
/lib/sparql-handler.js:
--------------------------------------------------------------------------------
1 | /* global log:false */
2 |
3 | 'use strict';
4 |
5 | var
6 | request = require('request'),
7 | url = require('url');
8 |
9 |
10 | module.exports = function (options) {
11 | if (options == null) {
12 | options = {};
13 | }
14 |
15 | if ('buildQuery' in options) {
16 | this.buildQuery = options.buildQuery;
17 | } else {
18 | this.buildQuery = function (iri) {
19 | return 'CONSTRUCT {?s ?p ?o} WHERE { GRAPH <' + iri + '> {?s ?p ?o}}';
20 | };
21 | }
22 |
23 | if ('buildExistsQuery' in options) {
24 | this.buildExistsQuery = options.buildExistsQuery;
25 | }
26 |
27 | var mapIri = function (iri) {
28 | var parsed = url.parse(iri);
29 |
30 | if ('hostname' in options) {
31 | parsed.hostname = options.hostname;
32 | }
33 |
34 | if ('port' in options) {
35 | parsed.port = options.port;
36 | }
37 |
38 | delete parsed.host;
39 |
40 | return url.format(parsed);
41 | };
42 |
43 | var getSparqlUrl = function (query) {
44 | return options.endpointUrl + '?query=' + encodeURIComponent(query);
45 | };
46 |
47 | this.get = function (req, res, next, iri) {
48 | var self = this;
49 |
50 | log.info({script: __filename}, 'handle GET request for IRI <' + iri + '>');
51 |
52 | var runQuery = function () {
53 | var query = self.buildQuery(mapIri(iri));
54 |
55 | log.debug({script: __filename}, 'SPARQL query for IRI <' + iri + '> : ' + query);
56 |
57 | request
58 | .get(getSparqlUrl(query), {headers: {accept: req.headers.accept}})
59 | .on('response', function (response) {
60 | if (response.statusCode !== 200) {
61 | res.writeHead(500);
62 | res.end();
63 | }
64 | })
65 | .pipe(res);
66 | };
67 |
68 | var runExistsQuery = function (callback) {
69 | var query = self.buildExistsQuery(mapIri(iri));
70 |
71 | log.debug({script: __filename}, 'SPARQL exists query for IRI <' + iri + '> : ' + query);
72 |
73 | request.get({
74 | url: getSparqlUrl(query),
75 | headers: {Accept: 'application/sparql-results+json'}
76 | }, function (error, response, body) {
77 | if (!error && response.statusCode == 200 && JSON.parse(body).boolean) {
78 | callback();
79 | } else {
80 | res.writeHead(404);
81 | res.end();
82 | }
83 | });
84 | };
85 |
86 | if ('buildExistsQuery' in self) {
87 | runExistsQuery(runQuery);
88 | } else {
89 | runQuery();
90 | }
91 | };
92 | };
--------------------------------------------------------------------------------
/lib/sparql-proxy.js:
--------------------------------------------------------------------------------
1 |
2 | var
3 | request = require('request');
4 |
5 | var directPost = function (req, res, endpointUrl, query, options) {
6 | headers = {'Accept': req.headers.accept, 'Content-Type': 'application/sparql-query'};
7 |
8 | options = options || {}
9 | options.headers = headers
10 | options.body = query
11 |
12 | request
13 | .post(endpointUrl, options)
14 | .pipe(res);
15 | }
16 |
17 | var urlencodedPost = function (req, res, endpointUrl, query, options) {
18 | var headers = {'Accept': req.headers.accept, 'Content-Type': 'application/x-www-form-urlencoded'};
19 |
20 | options = options || {}
21 | options.headers = headers
22 | options.form = {query: query}
23 |
24 | request
25 | .post(endpointUrl, options)
26 | .pipe(res);
27 | }
28 |
29 | var sparqlProxy = function (options) {
30 | var reqOptions = {}
31 |
32 | if (options.authentication) {
33 | reqOptions.auth = {
34 | user: options.authentication.user,
35 | pass: options.authentication.password
36 | }
37 | }
38 |
39 | return function (req, res, next) {
40 | var query;
41 |
42 | if (req.method === 'GET') {
43 | query = req.query.query;
44 | } else if (req.method === 'POST') {
45 | if ('query' in req.body) {
46 | query = req.body.query;
47 | } else {
48 | query = req.body;
49 | }
50 | } else {
51 | return next();
52 | }
53 |
54 | log.info({script: __filename}, 'handle SPARQL request for endpoint: ' + options.endpointUrl);
55 | log.debug({script: __filename}, 'SPARQL query:' + query);
56 |
57 | switch(options.queryOperation) {
58 | case 'urlencoded':
59 | return urlencodedPost(req, res, options.endpointUrl, query, reqOptions)
60 | default:
61 | return directPost(req, res, options.endpointUrl, query, reqOptions)
62 | }
63 | };
64 | };
65 |
66 |
67 | module.exports = sparqlProxy;
--------------------------------------------------------------------------------
/lib/sparql-search.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var
4 | handlerMiddleware = require('./handler-middleware'),
5 | SparqlHandler = require('./sparql-handler');
6 |
7 |
8 | module.exports = function (options) {
9 | var
10 | sparqlHandler,
11 | wrapperHandler = {};
12 |
13 | if ('resultsPerPage' in options) {
14 | options.paging = true;
15 | }
16 |
17 | var escapeLiteral = function (value) {
18 | return value.replace(/"/g, '\\"')
19 | };
20 |
21 | var processVariable = function (variableConfig, value, query) {
22 | if (!('required' in variableConfig)) {
23 | variableConfig.required = false;
24 | }
25 |
26 | if (!('type' in variableConfig)) {
27 | variableConfig.type = 'Literal';
28 | }
29 |
30 | // not required and string is empty -> nothing to do
31 | if (!variableConfig.required && value.trim() === '') {
32 | return query;
33 | }
34 |
35 | if (variableConfig.type === 'Literal') {
36 | return query.split(variableConfig.variable).join('"""' + escapeLiteral(value) + '"""');
37 | } else if (variableConfig.type === 'NamedNode') {
38 | return query.split(variableConfig.variable).join('<' + value + '>');
39 | }
40 | };
41 |
42 | wrapperHandler.get = function (req, res, next, iri) {
43 | sparqlHandler.buildQuery = function () {
44 | var
45 | page = 0,
46 | query = options.queryTemplate;
47 |
48 | if ('variables' in options) {
49 | Object.keys(options.variables).forEach(function (parameter) {
50 | query = processVariable(
51 | options.variables[parameter],
52 | parameter in req.query ? req.query[parameter]: '',
53 | query);
54 | });
55 | }
56 |
57 | if (options.paging) {
58 | if ('page' in req.query) {
59 | page = parseInt(req.query.page) - 1;
60 | }
61 |
62 | query += ' OFFSET ' + page * options.resultsPerPage + ' LIMIT ' + options.resultsPerPage;
63 | }
64 |
65 | return query;
66 | };
67 |
68 | return sparqlHandler.get(req, res, next, iri);
69 | };
70 |
71 | sparqlHandler = new SparqlHandler({endpointUrl: options.endpointUrl});
72 |
73 | return handlerMiddleware(wrapperHandler);
74 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "trifid-ld",
3 | "description": "Trifid-LD - Lightweight Linked Data Server and Proxy",
4 | "version": "0.10.0",
5 | "homepage": "https://github.com/zazukoians/trifid-ld",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/zazukoians/trifid-ld.git"
9 | },
10 | "bugs": {
11 | "url": "https://github.com/zazukoians/trifid-ld/issues"
12 | },
13 | "bin": {
14 | "trifid-ld": "./server.js"
15 | },
16 | "dependencies": {
17 | "body-parser": "1.10.*",
18 | "bunyan": "1.2.*",
19 | "es6-promise": "1.0.*",
20 | "express": "4.*",
21 | "express-negotiate": "0.0.*",
22 | "express-utils": "0.1.*",
23 | "ldp": "0.1.*",
24 | "lodash": "^4.16.4",
25 | "morgan": "1.5.*",
26 | "rdf-ext": "0.2.*",
27 | "request": "2.47.*",
28 | "stream-buffers": "1.1.*",
29 | "tbbt-ld": "*",
30 | "yasgui": "2.*"
31 | },
32 | "license": "MIT"
33 | }
34 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var path = require('path')
4 |
5 | var config
6 |
7 | try {
8 | config = require(path.join(process.cwd(), 'config'))
9 | } catch (err) {
10 | config = require('./config')
11 | }
12 |
13 | require('.')(config)
14 |
--------------------------------------------------------------------------------