]> git.openstreetmap.org Git - osqa.git/blob - forum/skins/default/media/js/se_hilite_src.js
Improves the answer position guessing.
[osqa.git] / forum / skins / default / media / js / se_hilite_src.js
1 /**
2  * Search Engine Keyword Highlight (http://fucoder.com/code/se-hilite/)
3  *
4  * This module can be imported by any HTML page, and it would analyse the
5  * referrer for search engine keywords, and then highlight those keywords on
6  * the page, by wrapping them around <span class="hilite">...</span> tags.
7  * Document can then define styles else where to provide visual feedbacks.
8  *
9  * Usage:
10  *
11  *   In HTML. Add the following line towards the end of the document.
12  *
13  *     <script type="text/javascript" src="se_hilite.js"></script>
14  *
15  *   In CSS, define the following style:
16  *
17  *     .hilite { background-color: #ff0; }
18  *
19  *   If Hilite.style_name_suffix is true, then define the follow styles:
20  *
21  *     .hilite1 { background-color: #ff0; }
22  *     .hilite2 { background-color: #f0f; }
23  *     .hilite3 { background-color: #0ff; }
24  *     .hilite4 ...
25  *
26  * @author Scott Yang <http://scott.yang.id.au/>
27  * @version 1.5
28  */
29
30 // Configuration:
31 Hilite = {
32     /**
33      * Element ID to be highlighted. If set, then only content inside this DOM
34      * element will be highlighted, otherwise everything inside document.body
35      * will be searched.
36      */
37     elementid: 'content',
38     
39     /**
40      * Whether we are matching an exact word. For example, searching for
41      * "highlight" will only match "highlight" but not "highlighting" if exact
42      * is set to true.
43      */
44     exact: true,
45
46     /**
47      * Maximum number of DOM nodes to test, before handing the control back to
48      * the GUI thread. This prevents locking up the UI when parsing and
49      * replacing inside a large document.
50      */
51     max_nodes: 1000,
52
53     /**
54      * Whether to automatically hilite a section of the HTML document, by
55      * binding the "Hilite.hilite()" to window.onload() event. If this
56      * attribute is set to false, you can still manually trigger the hilite by
57      * calling Hilite.hilite() in Javascript after document has been fully
58      * loaded.
59      */
60     onload: true,
61
62     /**
63      * Name of the style to be used. Default to 'hilite'.
64      */
65     style_name: 'hilite',
66     
67     /**
68      * Whether to use different style names for different search keywords by
69      * appending a number starting from 1, i.e. hilite1, hilite2, etc.
70      */
71     style_name_suffix: true,
72
73     /**
74      * Set it to override the document.referrer string. Used for debugging
75      * only.
76      */
77     debug_referrer: ''
78 };
79
80 Hilite.search_engines = [
81     ['google\\.', 'q'],                             // Google
82     ['search\\.yahoo\\.', 'p'],                     // Yahoo
83     ['search\\.msn\\.', 'q'],                       // MSN
84     ['search\\.live\\.', 'query'],                  // MSN Live
85     ['search\\.aol\\.', 'userQuery'],               // AOL
86     ['ask\\.com', 'q'],                             // Ask.com
87     ['altavista\\.', 'q'],                          // AltaVista
88     ['feedster\\.', 'q'],                           // Feedster
89     ['search\\.lycos\\.', 'q'],                     // Lycos
90     ['alltheweb\\.', 'q'],                          // AllTheWeb
91     ['technorati\\.com/search/([^\\?/]+)', 1],      // Technorati
92     ['dogpile\\.com/info\\.dogpl/search/web/([^\\?/]+)', 1, true] // DogPile
93 ];
94
95 /**
96  * Decode the referrer string and return a list of search keywords.
97  */
98 Hilite.decodeReferrer = function(referrer) {
99     var query = null;
100     var regex = new RegExp('');
101
102     for (var i = 0; i < Hilite.search_engines.length; i ++) {
103         var se = Hilite.search_engines[i];
104         regex.compile('^http://(www\\.)?' + se[0], 'i');
105         var match = referrer.match(regex);
106         if (match) {
107             var result;
108             if (isNaN(se[1])) {
109                 result = Hilite.decodeReferrerQS(referrer, se[1]);
110             } else {
111                 result = match[se[1] + 1];
112             }
113             if (result) {
114                 result = decodeURIComponent(result);
115                 // XXX: DogPile's URI requires decoding twice.
116                 if (se.length > 2 && se[2])
117                     result = decodeURIComponent(result);
118                 result = result.replace(/\'|"/g, '');
119                 result = result.split(/[\s,\+\.]+/);
120                 return result;
121             }
122             break;
123         }
124     }
125     return null;
126 };
127
128 Hilite.decodeReferrerQS = function(referrer, match) {
129     var idx = referrer.indexOf('?');
130     var idx2;
131     if (idx >= 0) {
132         var qs = new String(referrer.substring(idx + 1));
133         idx  = 0;
134         idx2 = 0;
135         while ((idx >= 0) && ((idx2 = qs.indexOf('=', idx)) >= 0)) {
136             var key, val;
137             key = qs.substring(idx, idx2);
138             idx = qs.indexOf('&', idx2) + 1;
139             if (key == match) {
140                 if (idx <= 0) {
141                     return qs.substring(idx2+1);
142                 } else {
143                     return qs.substring(idx2+1, idx - 1);
144                 }
145             }
146             else if (idx <=0) {
147                 return null;
148             }
149         }
150     }
151     return null;
152 };
153
154 /**
155  * Highlight a DOM element with a list of keywords.
156  */
157 Hilite.hiliteElement = function(elm, query) {
158     if (!query || elm.childNodes.length == 0)
159         return;
160
161     var qre = new Array();
162     for (var i = 0; i < query.length; i ++) {
163         query[i] = query[i].toLowerCase();
164         if (Hilite.exact)
165             qre.push('\\b'+query[i]+'\\b');
166         else
167             qre.push(query[i]);
168     }
169
170     qre = new RegExp(qre.join("|"), "i");
171
172     var stylemapper = {};
173     for (var i = 0; i < query.length; i ++) {
174         if (Hilite.style_name_suffix)
175             stylemapper[query[i]] = Hilite.style_name+(i+1);
176         else
177             stylemapper[query[i]] = Hilite.style_name;
178     }
179
180     var textproc = function(node) {
181         var match = qre.exec(node.data);
182         if (match) {
183             var val = match[0];
184             var k = '';
185             var node2 = node.splitText(match.index);
186             var node3 = node2.splitText(val.length);
187             var span = node.ownerDocument.createElement('SPAN');
188             node.parentNode.replaceChild(span, node2);
189             span.className = stylemapper[val.toLowerCase()];
190             span.appendChild(node2);
191             return span;
192         } else {
193             return node;
194         }
195     };
196     Hilite.walkElements(elm.childNodes[0], 1, textproc);
197 };
198
199 /**
200  * Highlight a HTML document using keywords extracted from document.referrer.
201  * This is the main function to be called to perform search engine highlight
202  * on a document.
203  *
204  * Currently it would check for DOM element 'content', element 'container' and
205  * then document.body in that order, so it only highlights appropriate section
206  * on WordPress and Movable Type pages.
207  */
208 Hilite.hilite = function() {
209     // If 'debug_referrer' then we will use that as our referrer string
210     // instead.
211     var q = Hilite.debug_referrer ? Hilite.debug_referrer : document.referrer;
212     var e = null;
213     q = Hilite.decodeReferrer(q);
214     if (q && ((Hilite.elementid && 
215                (e = document.getElementById(Hilite.elementid))) || 
216               (e = document.body)))
217     {
218         Hilite.hiliteElement(e, q);
219     }
220 };
221
222 Hilite.walkElements = function(node, depth, textproc) {
223     var skipre = /^(script|style|textarea)/i;
224     var count = 0;
225     while (node && depth > 0) {
226         count ++;
227         if (count >= Hilite.max_nodes) {
228             var handler = function() {
229                 Hilite.walkElements(node, depth, textproc);
230             };
231             setTimeout(handler, 50);
232             return;
233         }
234
235         if (node.nodeType == 1) { // ELEMENT_NODE
236             if (!skipre.test(node.tagName) && node.childNodes.length > 0) {
237                 node = node.childNodes[0];
238                 depth ++;
239                 continue;
240             }
241         } else if (node.nodeType == 3) { // TEXT_NODE
242             node = textproc(node);
243         }
244
245         if (node.nextSibling) {
246             node = node.nextSibling;
247         } else {
248             while (depth > 0) {
249                 node = node.parentNode;
250                 depth --;
251                 if (node.nextSibling) {
252                     node = node.nextSibling;
253                     break;
254                 }
255             }
256         }
257     }
258 };
259
260 // Trigger the highlight using the onload handler.
261 if (Hilite.onload) {
262     if (window.attachEvent) {
263         window.attachEvent('onload', Hilite.hilite);
264     } else if (window.addEventListener) {
265         window.addEventListener('load', Hilite.hilite, false);
266     } else {
267         var __onload = window.onload;
268         window.onload = function() {
269             Hilite.hilite();
270             __onload();
271         };
272     }
273 }