]>
git.wh0rd.org - tt-rss.git/blob - js/functions.js
1 var loading_progress
= 0 ;
2 var sanity_check_done
= false ;
4 var _label_base_index
= - 1024 ;
5 var notify_hide_timerid
= false ;
7 Ajax
. Base
. prototype . initialize
= Ajax
. Base
. prototype . initialize
. wrap (
8 function ( callOriginal
, options
) {
10 if ( getInitParam ( "csrf_token" ) != undefined ) {
11 Object
. extend ( options
, options
|| { });
13 if ( Object
. isString ( options
. parameters
))
14 options
. parameters
= options
. parameters
. toQueryParams ();
15 else if ( Object
. isHash ( options
. parameters
))
16 options
. parameters
= options
. parameters
. toObject ();
18 options
. parameters
[ "csrf_token" ] = getInitParam ( "csrf_token" );
21 return callOriginal ( options
);
25 /* add method to remove element from array */
27 Array
. prototype . remove = function ( s
) {
28 for ( var i
= 0 ; i
< this . length
; i
++) {
29 if ( s
== this [ i
]) this . splice ( i
, 1 );
34 function report_error ( message
, filename
, lineno
, colno
, error
) {
35 exception_error ( error
);
38 function exception_error ( e
, e_compat
) {
39 if ( typeof e
== "string" ) e
= e_compat
;
41 if (! e
) return ; // no exception object, nothing to report.
46 new Ajax
. Request ( "backend.php" , {
47 parameters
: { op
: "rpc" , method
: "log" , logmsg
: msg
},
48 onComplete : function ( transport
) {
49 console
. log ( transport
. responseText
);
53 console
. error ( "Exception while trying to log the error." , e
);
56 var msg
= e
. toString ();
58 msg
+= "<p>" + __ ( "The error will be reported to the configured log destination." ) +
63 var content
= "<div class= \" fatalError \" >" +
64 "<pre>" + msg
+ "</pre>" ;
66 content
+= "<form name= \" exceptionForm \" id= \" exceptionForm \" target= \" _blank \" " +
67 "action= \" https://tt-rss.org/report.php \" method= \" POST \" >" ;
69 content
+= "<textarea style= \" display : none \" name= \" message \" >" + msg
+ "</textarea>" ;
70 content
+= "<textarea style= \" display : none \" name= \" params \" >N/A</textarea>" ;
73 content
+= "<div><b>Stack trace:</b></div>" +
74 "<textarea name= \" stack \" readonly= \" 1 \" >" + e
. stack
+ "</textarea>" ;
81 content
+= "<div class='dlgButtons'>" ;
83 content
+= "<button dojoType= \" dijit.form.Button \" " +
84 "onclick= \" dijit.byId('exceptionDlg').report() \" >" +
85 __ ( 'Report to tt-rss.org' ) + "</button> " ;
86 content
+= "<button dojoType= \" dijit.form.Button \" " +
87 "onclick= \" dijit.byId('exceptionDlg').hide() \" >" +
88 __ ( 'Close' ) + "</button>" ;
91 if ( dijit
. byId ( "exceptionDlg" ))
92 dijit
. byId ( "exceptionDlg" ). destroyRecursive ();
94 var dialog
= new dijit
. Dialog ({
96 title
: "Unhandled exception" ,
97 style
: "width: 600px" ,
99 if ( confirm ( __ ( "Are you sure to report this exception to tt-rss.org? The report will include information about your web browser and tt-rss configuration. Your IP will be saved in the database." ))) {
101 document
. forms
[ 'exceptionForm' ]. params
. value
= $ H ({
102 browserName
: navigator
. appName
,
103 browserVersion
: navigator
. appVersion
,
104 browserPlatform
: navigator
. platform
,
105 browserCookies
: navigator
. cookieEnabled
,
106 ttrssVersion
: __ttrss_version
,
107 initParams
: JSON
. stringify ( init_params
),
110 document
. forms
[ 'exceptionForm' ]. submit ();
119 console
. error ( "Exception while trying to report an exception:" , ei
);
120 console
. error ( "Original exception:" , e
);
122 alert ( "Exception occured while trying to report an exception. \n " +
123 ei
. stack
+ " \n\n Original exception: \n " + e
. stack
);
128 function param_escape ( arg
) {
129 if ( typeof encodeURIComponent
!= 'undefined' )
130 return encodeURIComponent ( arg
);
135 function param_unescape ( arg
) {
136 if ( typeof decodeURIComponent
!= 'undefined' )
137 return decodeURIComponent ( arg
);
139 return unescape ( arg
);
142 function notify_real ( msg
, no_hide
, n_type
) {
148 if ( notify_hide_timerid
) {
149 window
. clearTimeout ( notify_hide_timerid
);
153 if ( n
. hasClassName ( "visible" )) {
154 notify_hide_timerid
= window
. setTimeout ( function () {
155 n
. removeClassName ( "visible" ) }, 0 );
169 msg
= "<span class= \" msg \" > " + __ ( msg
) + "</span>" ;
172 msg
= "<span><img src= \" " + getInitParam ( "icon_indicator_white" )+ " \" ></span>" + msg
;
174 } else if ( n_type
== 3 ) {
175 msg
= "<span><img src= \" " + getInitParam ( "icon_alert" )+ " \" ></span>" + msg
;
176 } else if ( n_type
== 4 ) {
177 msg
= "<span><img src= \" " + getInitParam ( "icon_information" )+ " \" ></span>" + msg
;
180 msg
+= " <span><img src= \" " + getInitParam ( "icon_cross" )+ " \" class= \" close \" title= \" " +
181 __ ( "Click to close" ) + " \" onclick= \" notify('') \" ></span>" ;
185 window
. setTimeout ( function () {
188 n
. className
= "notify notify_progress visible" ;
189 } else if ( n_type
== 3 ) {
190 n
. className
= "notify notify_error visible" ;
191 msg
= "<span><img src='images/alert.png'></span>" + msg
;
192 } else if ( n_type
== 4 ) {
193 n
. className
= "notify notify_info visible" ;
195 n
. className
= "notify visible" ;
199 notify_hide_timerid
= window
. setTimeout ( function () {
200 n
. removeClassName ( "visible" ) }, 5 * 1000 );
207 function notify ( msg
, no_hide
) {
208 notify_real ( msg
, no_hide
, 1 );
211 function notify_progress ( msg
, no_hide
) {
212 notify_real ( msg
, no_hide
, 2 );
215 function notify_error ( msg
, no_hide
) {
216 notify_real ( msg
, no_hide
, 3 );
220 function notify_info ( msg
, no_hide
) {
221 notify_real ( msg
, no_hide
, 4 );
224 function setCookie ( name
, value
, lifetime
, path
, domain
, secure
) {
230 d
. setTime ( d
. getTime () + ( lifetime
* 1000 ));
233 console
. log ( "setCookie: " + name
+ " => " + value
+ ": " + d
);
235 int_setCookie ( name
, value
, d
, path
, domain
, secure
);
239 function int_setCookie ( name
, value
, expires
, path
, domain
, secure
) {
240 document
. cookie
= name
+ "=" + escape ( value
) +
241 (( expires
) ? "; expires=" + expires
. toGMTString () : "" ) +
242 (( path
) ? "; path=" + path
: "" ) +
243 (( domain
) ? "; domain=" + domain
: "" ) +
244 (( secure
) ? "; secure" : "" );
247 function delCookie ( name
, path
, domain
) {
248 if ( getCookie ( name
)) {
249 document
. cookie
= name
+ "=" +
250 (( path
) ? ";path=" + path
: "" ) +
251 (( domain
) ? ";domain=" + domain
: "" ) +
252 ";expires=Thu, 01-Jan-1970 00:00:01 GMT" ;
257 function getCookie ( name
) {
259 var dc
= document
. cookie
;
260 var prefix
= name
+ "=" ;
261 var begin
= dc
. indexOf ( "; " + prefix
);
263 begin
= dc
. indexOf ( prefix
);
264 if ( begin
!= 0 ) return null ;
269 var end
= document
. cookie
. indexOf ( ";" , begin
);
273 return unescape ( dc
. substring ( begin
+ prefix
. length
, end
));
276 function gotoPreferences () {
277 document
. location
. href
= "prefs.php" ;
280 function gotoLogout () {
281 document
. location
. href
= "backend.php?op=logout" ;
284 function gotoMain () {
285 document
. location
. href
= "index.php" ;
288 /** * @(#)isNumeric.js * * Copyright (c) 2000 by Sundar Dorai-Raj
289 * * @author Sundar Dorai-Raj
290 * * Email: sdoraira@vt.edu
291 * * This program is free software; you can redistribute it and/or
292 * * modify it under the terms of the GNU General Public License
293 * * as published by the Free Software Foundation; either version 2
294 * * of the License, or (at your option) any later version,
295 * * provided that any use properly credits the author.
296 * * This program is distributed in the hope that it will be useful,
297 * * but WITHOUT ANY WARRANTY; without even the implied warranty of
298 * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
299 * * GNU General Public License for more details at http://www.gnu.org * * */
301 var numbers
= ".0123456789" ;
302 function isNumeric ( x
) {
303 // is x a String or a character?
305 // remove negative sign
307 for ( var j
= 0 ; j
< x
. length
; j
++) {
308 // call isNumeric recursively for each character
309 number
= isNumeric ( x
. substring ( j
, j
+ 1 ));
310 if (! number
) return number
;
315 // if x is number return true
316 if ( numbers
. indexOf ( x
)>= 0 ) return true ;
322 function toggleSelectRowById ( sender
, id
) {
324 return toggleSelectRow ( sender
, row
);
327 function toggleSelectListRow ( sender
) {
328 var row
= sender
. parentNode
;
329 return toggleSelectRow ( sender
, row
);
332 /* this is for dijit Checkbox */
333 function toggleSelectListRow2 ( sender
) {
334 var row
= sender
. domNode
. parentNode
;
335 return toggleSelectRow ( sender
, row
);
338 /* this is for dijit Checkbox */
339 function toggleSelectRow2 ( sender
, row
, is_cdm
) {
343 row
= sender
. domNode
. parentNode
. parentNode
;
345 row
= sender
. domNode
. parentNode
. parentNode
. parentNode
; // oh ffs
347 if ( sender
. checked
&& ! row
. hasClassName ( 'Selected' ))
348 row
. addClassName ( 'Selected' );
350 row
. removeClassName ( 'Selected' );
352 if ( typeof updateSelectedPrompt
!= undefined )
353 updateSelectedPrompt ();
357 function toggleSelectRow ( sender
, row
) {
359 if (! row
) row
= sender
. parentNode
. parentNode
;
361 if ( sender
. checked
&& ! row
. hasClassName ( 'Selected' ))
362 row
. addClassName ( 'Selected' );
364 row
. removeClassName ( 'Selected' );
366 if ( typeof updateSelectedPrompt
!= undefined )
367 updateSelectedPrompt ();
370 function checkboxToggleElement ( elem
, id
) {
372 Effect
. Appear ( id
, { duration
: 0.5 });
374 Effect
. Fade ( id
, { duration
: 0.5 });
378 function dropboxSelect ( e
, v
) {
379 for ( var i
= 0 ; i
< e
. length
; i
++) {
380 if ( e
[ i
]. value
== v
) {
387 function getURLParam ( param
){
388 return String ( window
. location
. href
). parseQuery ()[ param
];
391 function closeInfoBox ( cleanup
) {
392 dialog
= dijit
. byId ( "infoBox" );
394 if ( dialog
) dialog
. hide ();
400 function displayDlg ( title
, id
, param
, callback
) {
402 notify_progress ( "Loading, please wait..." , true );
404 var query
= "?op=dlg&method=" +
405 param_escape ( id
) + "¶m=" + param_escape ( param
);
407 new Ajax
. Request ( "backend.php" , {
409 onComplete : function ( transport
) {
410 infobox_callback2 ( transport
, title
);
411 if ( callback
) callback ( transport
);
417 function infobox_callback2 ( transport
, title
) {
420 if ( dijit
. byId ( "infoBox" )) {
421 dialog
= dijit
. byId ( "infoBox" );
424 //console.log("infobox_callback2");
427 var content
= transport
. responseText
;
430 dialog
= new dijit
. Dialog ({
433 style
: "width: 600px" ,
434 onCancel : function () {
437 onExecute : function () {
440 onClose : function () {
445 dialog
. attr ( 'title' , title
);
446 dialog
. attr ( 'content' , content
);
454 function getInitParam ( key
) {
455 return init_params
[ key
];
458 function setInitParam ( key
, value
) {
459 init_params
[ key
] = value
;
462 function fatalError ( code
, msg
, ext_info
) {
464 window
. location
. href
= "index.php" ;
465 } else if ( code
== 5 ) {
466 window
. location
. href
= "public.php?op=dbupdate" ;
469 if ( msg
== "" ) msg
= "Unknown error" ;
472 if ( ext_info
. responseText
) {
473 ext_info
= ext_info
. responseText
;
477 if ( ERRORS
&& ERRORS
[ code
] && ! msg
) {
481 var content
= "<div><b>Error code:</b> " + code
+ "</div>" +
482 "<p>" + msg
+ "</p>" ;
485 content
= content
+ "<div><b>Additional information:</b></div>" +
486 "<textarea style='width: 100%' readonly= \" 1 \" >" +
487 ext_info
+ "</textarea>" ;
490 var dialog
= new dijit
. Dialog ({
491 title
: "Fatal error" ,
492 style
: "width: 600px" ,
503 function filterDlgCheckAction ( sender
) {
504 var action
= sender
. value
;
506 var action_param
= $( "filterDlg_paramBox" );
509 console
. log ( "filterDlgCheckAction: can't find action param box!" );
513 // if selected action supports parameters, enable params field
514 if ( action
== 4 || action
== 6 || action
== 7 || action
== 9 ) {
515 new Effect
. Appear ( action_param
, { duration
: 0.5 });
517 Element
. hide ( dijit
. byId ( "filterDlg_actionParam" ). domNode
);
518 Element
. hide ( dijit
. byId ( "filterDlg_actionParamLabel" ). domNode
);
519 Element
. hide ( dijit
. byId ( "filterDlg_actionParamPlugin" ). domNode
);
522 Element
. show ( dijit
. byId ( "filterDlg_actionParamLabel" ). domNode
);
523 } else if ( action
== 9 ) {
524 Element
. show ( dijit
. byId ( "filterDlg_actionParamPlugin" ). domNode
);
526 Element
. show ( dijit
. byId ( "filterDlg_actionParam" ). domNode
);
530 Element
. hide ( action_param
);
535 function explainError ( code
) {
536 return displayDlg ( __ ( "Error explained" ), "explainError" , code
);
539 function loading_set_progress ( p
) {
540 loading_progress
+= p
;
542 if ( dijit
. byId ( "loading_bar" ))
543 dijit
. byId ( "loading_bar" ). update ({ progress
: loading_progress
});
545 if ( loading_progress
>= 90 )
550 function remove_splash () {
552 if ( Element
. visible ( "overlay" )) {
553 console
. log ( "about to remove splash, OMG!" );
554 Element
. hide ( "overlay" );
555 console
. log ( "removed splash!" );
559 function strip_tags ( s
) {
560 return s
. replace ( /<\/?[^>]+(>|$)/g , "" );
563 function truncate_string ( s
, length
) {
564 if (! length
) length
= 30 ;
565 var tmp
= s
. substring ( 0 , length
);
566 if ( s
. length
> length
) tmp
+= "…" ;
570 function hotkey_prefix_timeout () {
572 var date
= new Date ();
573 var ts
= Math
. round ( date
. getTime () / 1000 );
575 if ( hotkey_prefix_pressed
&& ts
- hotkey_prefix_pressed
>= 5 ) {
576 console
. log ( "hotkey_prefix seems to be stuck, aborting" );
577 hotkey_prefix_pressed
= false ;
578 hotkey_prefix
= false ;
579 Element
. hide ( 'cmdline' );
582 setTimeout ( hotkey_prefix_timeout
, 1000 );
586 function uploadIconHandler ( rc
) {
589 notify_info ( "Upload complete." );
590 if ( inPreferences ()) {
593 setTimeout ( 'updateFeedList(false, false)' , 50 );
597 notify_error ( "Upload failed: icon is too big." );
600 notify_error ( "Upload failed." );
605 function removeFeedIcon ( id
) {
606 if ( confirm ( __ ( "Remove stored feed icon?" ))) {
607 var query
= "backend.php?op=pref-feeds&method=removeicon&feed_id=" + param_escape ( id
);
611 notify_progress ( "Removing feed icon..." , true );
613 new Ajax
. Request ( "backend.php" , {
615 onComplete : function ( transport
) {
616 notify_info ( "Feed icon removed." );
617 if ( inPreferences ()) {
620 setTimeout ( 'updateFeedList(false, false)' , 50 );
628 function uploadFeedIcon () {
629 var file
= $( "icon_file" );
631 if ( file
. value
. length
== 0 ) {
632 alert ( __ ( "Please select an image file to upload." ));
634 if ( confirm ( __ ( "Upload new icon for this feed?" ))) {
635 notify_progress ( "Uploading, please wait..." , true );
643 function addLabel ( select
, callback
) {
645 var caption
= prompt ( __ ( "Please enter label caption:" ), "" );
647 if ( caption
!= undefined ) {
650 alert ( __ ( "Can't create label: missing caption." ));
654 var query
= "?op=pref-labels&method=add&caption=" +
655 param_escape ( caption
);
658 query
+= "&output=select" ;
660 notify_progress ( "Loading, please wait..." , true );
662 if ( inPreferences () && ! select
) active_tab
= "labelConfig" ;
664 new Ajax
. Request ( "backend.php" , {
666 onComplete : function ( transport
) {
669 } else if ( inPreferences ()) {
680 function quickAddFeed () {
681 var query
= "backend.php?op=feeds&method=quickAddFeed" ;
683 // overlapping widgets
684 if ( dijit
. byId ( "batchSubDlg" )) dijit
. byId ( "batchSubDlg" ). destroyRecursive ();
685 if ( dijit
. byId ( "feedAddDlg" )) dijit
. byId ( "feedAddDlg" ). destroyRecursive ();
687 var dialog
= new dijit
. Dialog ({
689 title
: __ ( "Subscribe to Feed" ),
690 style
: "width: 600px" ,
691 show_error : function ( msg
) {
692 var elem
= $( "fadd_error_message" );
694 elem
. innerHTML
= msg
;
696 if (! Element
. visible ( elem
))
697 new Effect
. Appear ( elem
);
700 execute : function () {
701 if ( this . validate ()) {
702 console
. log ( dojo
. objectToQuery ( this . attr ( 'value' )));
704 var feed_url
= this . attr ( 'value' ). feed
;
706 Element
. show ( "feed_add_spinner" );
707 Element
. hide ( "fadd_error_message" );
709 new Ajax
. Request ( "backend.php" , {
710 parameters
: dojo
. objectToQuery ( this . attr ( 'value' )),
711 onComplete : function ( transport
) {
715 var reply
= JSON
. parse ( transport
. responseText
);
717 Element
. hide ( "feed_add_spinner" );
718 alert ( __ ( "Failed to parse output. This can indicate server timeout and/or network issues. Backend output was logged to browser console." ));
719 console
. log ( 'quickAddFeed, backend returned:' + transport
. responseText
);
723 var rc
= reply
[ 'result' ];
726 Element
. hide ( "feed_add_spinner" );
730 switch ( parseInt ( rc
[ 'code' ])) {
733 notify_info ( __ ( "Subscribed to %s" ). replace ( "%s" , feed_url
));
738 dialog
. show_error ( __ ( "Specified URL seems to be invalid." ));
741 dialog
. show_error ( __ ( "Specified URL doesn't seem to contain any feeds." ));
746 Element
. show ( "fadd_multiple_notify" );
748 var select
= dijit
. byId ( "feedDlg_feedContainerSelect" );
750 while ( select
. getOptions (). length
> 0 )
751 select
. removeOption ( 0 );
753 select
. addOption ({ value
: '' , label
: __ ( "Expand to select feed" )});
756 for ( var feedUrl
in feeds
) {
757 select
. addOption ({ value
: feedUrl
, label
: feeds
[ feedUrl
]});
761 Effect
. Appear ( 'feedDlg_feedsContainer' , { duration
: 0.5 });
765 dialog
. show_error ( __ ( "Couldn't download the specified URL: %s" ).
766 replace ( "%s" , rc
[ 'message' ]));
769 dialog
. show_error ( __ ( "XML validation failed: %s" ).
770 replace ( "%s" , rc
[ 'message' ]));
774 dialog
. show_error ( __ ( "You are already subscribed to this feed." ));
779 console
. error ( transport
. responseText
);
792 function createNewRuleElement ( parentNode
, replaceNode
) {
793 var form
= document
. forms
[ "filter_new_rule_form" ];
795 //form.reg_exp.value = form.reg_exp.value.replace(/(<([^>]+)>)/ig,"");
797 var query
= "backend.php?op=pref-filters&method=printrulename&rule=" +
798 param_escape ( dojo
. formToJson ( form
));
802 new Ajax
. Request ( "backend.php" , {
804 onComplete : function ( transport
) {
806 var li
= dojo
. create ( "li" );
808 var cb
= dojo
. create ( "input" , { type
: "checkbox" }, li
);
810 new dijit
. form
. CheckBox ({
811 onChange : function () {
812 toggleSelectListRow2 ( this ) },
815 dojo
. create ( "input" , { type
: "hidden" ,
817 value
: dojo
. formToJson ( form
) }, li
);
819 dojo
. create ( "span" , {
820 onclick : function () {
821 dijit
. byId ( 'filterEditDlg' ). editRule ( this );
823 innerHTML
: transport
. responseText
}, li
);
826 parentNode
. replaceChild ( li
, replaceNode
);
828 parentNode
. appendChild ( li
);
836 function createNewActionElement ( parentNode
, replaceNode
) {
837 var form
= document
. forms
[ "filter_new_action_form" ];
839 if ( form
. action_id
. value
== 7 ) {
840 form
. action_param
. value
= form
. action_param_label
. value
;
841 } else if ( form
. action_id
. value
== 9 ) {
842 form
. action_param
. value
= form
. action_param_plugin
. value
;
845 var query
= "backend.php?op=pref-filters&method=printactionname&action=" +
846 param_escape ( dojo
. formToJson ( form
));
850 new Ajax
. Request ( "backend.php" , {
852 onComplete : function ( transport
) {
854 var li
= dojo
. create ( "li" );
856 var cb
= dojo
. create ( "input" , { type
: "checkbox" }, li
);
858 new dijit
. form
. CheckBox ({
859 onChange : function () {
860 toggleSelectListRow2 ( this ) },
863 dojo
. create ( "input" , { type
: "hidden" ,
865 value
: dojo
. formToJson ( form
) }, li
);
867 dojo
. create ( "span" , {
868 onclick : function () {
869 dijit
. byId ( 'filterEditDlg' ). editAction ( this );
871 innerHTML
: transport
. responseText
}, li
);
874 parentNode
. replaceChild ( li
, replaceNode
);
876 parentNode
. appendChild ( li
);
886 function addFilterRule ( replaceNode
, ruleStr
) {
887 if ( dijit
. byId ( "filterNewRuleDlg" ))
888 dijit
. byId ( "filterNewRuleDlg" ). destroyRecursive ();
890 var query
= "backend.php?op=pref-filters&method=newrule&rule=" +
891 param_escape ( ruleStr
);
893 var rule_dlg
= new dijit
. Dialog ({
894 id
: "filterNewRuleDlg" ,
895 title
: ruleStr
? __ ( "Edit rule" ) : __ ( "Add rule" ),
896 style
: "width: 600px" ,
897 execute : function () {
898 if ( this . validate ()) {
899 createNewRuleElement ($( "filterDlg_Matches" ), replaceNode
);
908 function addFilterAction ( replaceNode
, actionStr
) {
909 if ( dijit
. byId ( "filterNewActionDlg" ))
910 dijit
. byId ( "filterNewActionDlg" ). destroyRecursive ();
912 var query
= "backend.php?op=pref-filters&method=newaction&action=" +
913 param_escape ( actionStr
);
915 var rule_dlg
= new dijit
. Dialog ({
916 id
: "filterNewActionDlg" ,
917 title
: actionStr
? __ ( "Edit action" ) : __ ( "Add action" ),
918 style
: "width: 600px" ,
919 execute : function () {
920 if ( this . validate ()) {
921 createNewActionElement ($( "filterDlg_Actions" ), replaceNode
);
930 function editFilterTest ( query
) {
932 if ( dijit
. byId ( "filterTestDlg" ))
933 dijit
. byId ( "filterTestDlg" ). destroyRecursive ();
935 var test_dlg
= new dijit
. Dialog ({
937 title
: "Test Filter" ,
938 style
: "width: 600px" ,
942 getTestResults : function ( query
, offset
) {
943 var updquery
= query
+ "&offset=" + offset
+ "&limit=" + test_dlg
. limit
;
945 console
. log ( "getTestResults:" + offset
);
947 new Ajax
. Request ( "backend.php" , {
948 parameters
: updquery
,
949 onComplete : function ( transport
) {
951 var result
= JSON
. parse ( transport
. responseText
);
953 if ( result
&& dijit
. byId ( "filterTestDlg" ) && dijit
. byId ( "filterTestDlg" ). open
) {
954 test_dlg
. results
+= result
. size ();
956 console
. log ( "got results:" + result
. size ());
958 $( "prefFilterProgressMsg" ). innerHTML
= __ ( "Looking for articles (%d processed, %f found)..." )
959 . replace ( "%f" , test_dlg
. results
)
960 . replace ( "%d" , offset
);
962 console
. log ( offset
+ " " + test_dlg
. max_offset
);
964 for ( var i
= 0 ; i
< result
. size (); i
++) {
965 var tmp
= new Element ( "table" );
966 tmp
. innerHTML
= result
[ i
];
967 dojo
. parser
. parse ( tmp
);
969 $( "prefFilterTestResultList" ). innerHTML
+= tmp
. innerHTML
;
972 if ( test_dlg
. results
< 30 && offset
< test_dlg
. max_offset
) {
974 // get the next batch
975 window
. setTimeout ( function () {
976 test_dlg
. getTestResults ( query
, offset
+ test_dlg
. limit
);
982 Element
. hide ( "prefFilterLoadingIndicator" );
984 if ( test_dlg
. results
== 0 ) {
985 $( "prefFilterTestResultList" ). innerHTML
= "<tr><td align='center'>No recent articles matching this filter have been found.</td></tr>" ;
986 $( "prefFilterProgressMsg" ). innerHTML
= "Articles matching this filter:" ;
988 $( "prefFilterProgressMsg" ). innerHTML
= __ ( "Found %d articles matching this filter:" )
989 . replace ( "%d" , test_dlg
. results
);
994 } else if (! result
) {
995 console
. log ( "getTestResults: can't parse results object" );
997 Element
. hide ( "prefFilterLoadingIndicator" );
999 notify_error ( "Error while trying to get filter test results." );
1002 console
. log ( "getTestResults: dialog closed, bailing out." );
1012 dojo
. connect ( test_dlg
, "onLoad" , null , function ( e
) {
1013 test_dlg
. getTestResults ( query
, 0 );
1020 function quickAddFilter () {
1022 if (! inPreferences ()) {
1023 query
= "backend.php?op=pref-filters&method=newfilter&feed=" +
1024 param_escape ( getActiveFeedId ()) + "&is_cat=" +
1025 param_escape ( activeFeedIsCat ());
1027 query
= "backend.php?op=pref-filters&method=newfilter" ;
1032 if ( dijit
. byId ( "feedEditDlg" ))
1033 dijit
. byId ( "feedEditDlg" ). destroyRecursive ();
1035 if ( dijit
. byId ( "filterEditDlg" ))
1036 dijit
. byId ( "filterEditDlg" ). destroyRecursive ();
1038 dialog
= new dijit
. Dialog ({
1039 id
: "filterEditDlg" ,
1040 title
: __ ( "Create Filter" ),
1041 style
: "width: 600px" ,
1043 var query
= "backend.php?" + dojo
. formToQuery ( "filter_new_form" ) + "&savemode=test" ;
1045 editFilterTest ( query
);
1047 selectRules : function ( select
) {
1048 $$( "#filterDlg_Matches input[type=checkbox]" ). each ( function ( e
) {
1051 e
. parentNode
. addClassName ( "Selected" );
1053 e
. parentNode
. removeClassName ( "Selected" );
1056 selectActions : function ( select
) {
1057 $$( "#filterDlg_Actions input[type=checkbox]" ). each ( function ( e
) {
1061 e
. parentNode
. addClassName ( "Selected" );
1063 e
. parentNode
. removeClassName ( "Selected" );
1067 editRule : function ( e
) {
1068 var li
= e
. parentNode
;
1069 var rule
= li
. getElementsByTagName ( "INPUT" )[ 1 ]. value
;
1070 addFilterRule ( li
, rule
);
1072 editAction : function ( e
) {
1073 var li
= e
. parentNode
;
1074 var action
= li
. getElementsByTagName ( "INPUT" )[ 1 ]. value
;
1075 addFilterAction ( li
, action
);
1077 addAction : function () { addFilterAction (); },
1078 addRule : function () { addFilterRule (); },
1079 deleteAction : function () {
1080 $$( "#filterDlg_Actions li.[class*=Selected]" ). each ( function ( e
) { e
. parentNode
. removeChild ( e
) });
1082 deleteRule : function () {
1083 $$( "#filterDlg_Matches li.[class*=Selected]" ). each ( function ( e
) { e
. parentNode
. removeChild ( e
) });
1085 execute : function () {
1086 if ( this . validate ()) {
1088 var query
= dojo
. formToQuery ( "filter_new_form" );
1092 new Ajax
. Request ( "backend.php" , {
1094 onComplete : function ( transport
) {
1095 if ( inPreferences ()) {
1105 if (! inPreferences ()) {
1106 var selectedText
= getSelectionText ();
1108 var lh
= dojo
. connect ( dialog
, "onLoad" , function (){
1109 dojo
. disconnect ( lh
);
1111 if ( selectedText
!= "" ) {
1113 var feed_id
= activeFeedIsCat () ? 'CAT:' + parseInt ( getActiveFeedId ()) :
1116 var rule
= { reg_exp
: selectedText
, feed_id
: feed_id
, filter_type
: 1 };
1118 addFilterRule ( null , dojo
. toJson ( rule
));
1122 var query
= "op=rpc&method=getlinktitlebyid&id=" + getActiveArticleId ();
1124 new Ajax
. Request ( "backend.php" , {
1126 onComplete : function ( transport
) {
1127 var reply
= JSON
. parse ( transport
. responseText
);
1131 if ( reply
&& reply
) title
= reply
. title
;
1133 if ( title
|| getActiveFeedId () || activeFeedIsCat ()) {
1135 console
. log ( title
+ " " + getActiveFeedId ());
1137 var feed_id
= activeFeedIsCat () ? 'CAT:' + parseInt ( getActiveFeedId ()) :
1140 var rule
= { reg_exp
: title
, feed_id
: feed_id
, filter_type
: 1 };
1142 addFilterRule ( null , dojo
. toJson ( rule
));
1156 function resetPubSub ( feed_id
, title
) {
1158 var msg
= __ ( "Reset subscription? Tiny Tiny RSS will try to subscribe to the notification hub again on next feed update." ). replace ( "%s" , title
);
1160 if ( title
== undefined || confirm ( msg
)) {
1161 notify_progress ( "Loading, please wait..." );
1163 var query
= "?op=pref-feeds&quiet=1&method=resetPubSub&ids=" + feed_id
;
1165 new Ajax
. Request ( "backend.php" , {
1167 onComplete : function ( transport
) {
1168 dijit
. byId ( "pubsubReset_Btn" ). attr ( 'disabled' , true );
1169 notify_info ( "Subscription reset." );
1177 function unsubscribeFeed ( feed_id
, title
) {
1179 var msg
= __ ( "Unsubscribe from %s?" ). replace ( "%s" , title
);
1181 if ( title
== undefined || confirm ( msg
)) {
1182 notify_progress ( "Removing feed..." );
1184 var query
= "?op=pref-feeds&quiet=1&method=remove&ids=" + feed_id
;
1186 new Ajax
. Request ( "backend.php" , {
1188 onComplete : function ( transport
) {
1190 if ( dijit
. byId ( "feedEditDlg" )) dijit
. byId ( "feedEditDlg" ). hide ();
1192 if ( inPreferences ()) {
1195 if ( feed_id
== getActiveFeedId ())
1196 setTimeout ( function () { viewfeed ({ feed
:- 5 }) }, 100 );
1198 if ( feed_id
< 0 ) updateFeedList ();
1208 function backend_sanity_check_callback ( transport
) {
1210 if ( sanity_check_done
) {
1211 fatalError ( 11 , "Sanity check request received twice. This can indicate " +
1212 "presence of Firebug or some other disrupting extension. " +
1213 "Please disable it and try again." );
1217 var reply
= JSON
. parse ( transport
. responseText
);
1220 fatalError ( 3 , "Sanity check: invalid RPC reply" , transport
. responseText
);
1224 var error_code
= reply
[ 'error' ][ 'code' ];
1226 if ( error_code
&& error_code
!= 0 ) {
1227 return fatalError ( error_code
, reply
[ 'error' ][ 'message' ]);
1230 console
. log ( "sanity check ok" );
1232 var params
= reply
[ 'init-params' ];
1235 console
. log ( 'reading init-params...' );
1238 console
. log ( "IP: " + k
+ " => " + JSON
. stringify ( params
[ k
]));
1239 if ( k
== "label_base_index" ) _label_base_index
= parseInt ( params
[ k
]);
1242 init_params
= params
;
1244 // PluginHost might not be available on non-index pages
1245 window
. PluginHost
&& PluginHost
. run ( PluginHost
. HOOK_PARAMS_LOADED
, init_params
);
1248 sanity_check_done
= true ;
1250 init_second_stage ();
1254 function quickAddCat ( elem
) {
1255 var cat
= prompt ( __ ( "Please enter category title:" ));
1259 var query
= "?op=rpc&method=quickAddCat&cat=" + param_escape ( cat
);
1261 notify_progress ( "Loading, please wait..." , true );
1263 new Ajax
. Request ( "backend.php" , {
1265 onComplete : function ( transport
) {
1266 var response
= transport
. responseXML
;
1267 var select
= response
. getElementsByTagName ( "select" )[ 0 ];
1268 var options
= select
. getElementsByTagName ( "option" );
1270 dropbox_replace_options ( elem
, options
);
1279 function genUrlChangeKey ( feed
, is_cat
) {
1280 var ok
= confirm ( __ ( "Generate new syndication address for this feed?" ));
1284 notify_progress ( "Trying to change address..." , true );
1286 var query
= "?op=pref-feeds&method=regenFeedKey&id=" + param_escape ( feed
) +
1287 "&is_cat=" + param_escape ( is_cat
);
1289 new Ajax
. Request ( "backend.php" , {
1291 onComplete : function ( transport
) {
1292 var reply
= JSON
. parse ( transport
. responseText
);
1293 var new_link
= reply
. link
;
1295 var e
= $( 'gen_feed_url' );
1299 e
. innerHTML
= e
. innerHTML
. replace ( /\&key=.*$/ ,
1300 "&key=" + new_link
);
1302 e
. href
= e
. href
. replace ( /\&key=.*$/ ,
1303 "&key=" + new_link
);
1305 new Effect
. Highlight ( e
);
1310 notify_error ( "Could not change feed URL." );
1317 function dropbox_replace_options ( elem
, options
) {
1318 while ( elem
. hasChildNodes ())
1319 elem
. removeChild ( elem
. firstChild
);
1323 for ( var i
= 0 ; i
< options
. length
; i
++) {
1324 var text
= options
[ i
]. firstChild
. nodeValue
;
1325 var value
= options
[ i
]. getAttribute ( "value" );
1327 if ( value
== undefined ) value
= text
;
1329 var issel
= options
[ i
]. getAttribute ( "selected" ) == "1" ;
1331 var option
= new Option ( text
, value
, issel
);
1333 if ( options
[ i
]. getAttribute ( "disabled" ))
1334 option
. setAttribute ( "disabled" , true );
1336 elem
. insert ( option
);
1338 if ( issel
) sel_idx
= i
;
1341 // Chrome doesn't seem to just select stuff when you pass new Option(x, y, true)
1342 if ( sel_idx
>= 0 ) elem
. selectedIndex
= sel_idx
;
1345 // mode = all, none, invert
1346 function selectTableRows ( id
, mode
) {
1347 var rows
= $( id
). rows
;
1349 for ( var i
= 0 ; i
< rows
. length
; i
++) {
1354 if ( row
. id
&& row
. className
) {
1355 var bare_id
= row
. id
. replace ( /^[A-Z]*?-/ , "" );
1356 var inputs
= rows
[ i
]. getElementsByTagName ( "input" );
1358 for ( var j
= 0 ; j
< inputs
. length
; j
++) {
1359 var input
= inputs
[ j
];
1361 if ( input
. getAttribute ( "type" ) == "checkbox" &&
1362 input
. id
. match ( bare_id
)) {
1365 dcb
= dijit
. getEnclosingWidget ( cb
);
1371 var issel
= row
. hasClassName ( "Selected" );
1373 if ( mode
== "all" && ! issel
) {
1374 row
. addClassName ( "Selected" );
1376 if ( dcb
) dcb
. set ( "checked" , true );
1377 } else if ( mode
== "none" && issel
) {
1378 row
. removeClassName ( "Selected" );
1380 if ( dcb
) dcb
. set ( "checked" , false );
1382 } else if ( mode
== "invert" ) {
1385 row
. removeClassName ( "Selected" );
1387 if ( dcb
) dcb
. set ( "checked" , false );
1389 row
. addClassName ( "Selected" );
1391 if ( dcb
) dcb
. set ( "checked" , true );
1400 function getSelectedTableRowIds ( id
) {
1403 var elem_rows
= $( id
). rows
;
1405 for ( var i
= 0 ; i
< elem_rows
. length
; i
++) {
1406 if ( elem_rows
[ i
]. hasClassName ( "Selected" )) {
1407 var bare_id
= elem_rows
[ i
]. id
. replace ( /^[A-Z]*?-/ , "" );
1415 function editFeed ( feed
, event
) {
1417 return alert ( __ ( "You can't edit this kind of feed." ));
1419 var query
= "backend.php?op=pref-feeds&method=editfeed&id=" +
1424 if ( dijit
. byId ( "filterEditDlg" ))
1425 dijit
. byId ( "filterEditDlg" ). destroyRecursive ();
1427 if ( dijit
. byId ( "feedEditDlg" ))
1428 dijit
. byId ( "feedEditDlg" ). destroyRecursive ();
1430 dialog
= new dijit
. Dialog ({
1432 title
: __ ( "Edit Feed" ),
1433 style
: "width: 600px" ,
1434 execute : function () {
1435 if ( this . validate ()) {
1436 // console.log(dojo.objectToQuery(this.attr('value')));
1438 notify_progress ( "Saving data..." , true );
1440 new Ajax
. Request ( "backend.php" , {
1441 parameters
: dojo
. objectToQuery ( dialog
. attr ( 'value' )),
1442 onComplete : function ( transport
) {
1454 function feedBrowser () {
1455 var query
= "backend.php?op=feeds&method=feedBrowser" ;
1457 if ( dijit
. byId ( "feedAddDlg" ))
1458 dijit
. byId ( "feedAddDlg" ). hide ();
1460 if ( dijit
. byId ( "feedBrowserDlg" ))
1461 dijit
. byId ( "feedBrowserDlg" ). destroyRecursive ();
1463 var dialog
= new dijit
. Dialog ({
1464 id
: "feedBrowserDlg" ,
1465 title
: __ ( "More Feeds" ),
1466 style
: "width: 600px" ,
1467 getSelectedFeedIds : function () {
1468 var list
= $$( "#browseFeedList li[id*=FBROW]" );
1469 var selected
= new Array ();
1471 list
. each ( function ( child
) {
1472 var id
= child
. id
. replace ( "FBROW-" , "" );
1474 if ( child
. hasClassName ( 'Selected' )) {
1481 getSelectedFeeds : function () {
1482 var list
= $$( "#browseFeedList li.Selected" );
1483 var selected
= new Array ();
1485 list
. each ( function ( child
) {
1486 var title
= child
. getElementsBySelector ( "span.fb_feedTitle" )[ 0 ]. innerHTML
;
1487 var url
= child
. getElementsBySelector ( "a.fb_feedUrl" )[ 0 ]. href
;
1489 selected
. push ([ title
, url
]);
1496 subscribe : function () {
1497 var mode
= this . attr ( 'value' ). mode
;
1501 selected
= this . getSelectedFeeds ();
1503 selected
= this . getSelectedFeedIds ();
1505 if ( selected
. length
> 0 ) {
1506 dijit
. byId ( "feedBrowserDlg" ). hide ();
1508 notify_progress ( "Loading, please wait..." , true );
1510 // we use dojo.toJson instead of JSON.stringify because
1511 // it somehow escapes everything TWICE, at least in Chrome 9
1513 var query
= "?op=rpc&method=massSubscribe&payload=" +
1514 param_escape ( dojo
. toJson ( selected
)) + "&mode=" + param_escape ( mode
);
1518 new Ajax
. Request ( "backend.php" , {
1520 onComplete : function ( transport
) {
1527 alert ( __ ( "No feeds are selected." ));
1531 update : function () {
1532 var query
= dojo
. objectToQuery ( dialog
. attr ( 'value' ));
1534 Element
. show ( 'feed_browser_spinner' );
1536 new Ajax
. Request ( "backend.php" , {
1538 onComplete : function ( transport
) {
1541 Element
. hide ( 'feed_browser_spinner' );
1543 var c
= $( "browseFeedList" );
1545 var reply
= JSON
. parse ( transport
. responseText
);
1547 var r
= reply
[ 'content' ];
1548 var mode
= reply
[ 'mode' ];
1554 dojo
. parser
. parse ( "browseFeedList" );
1557 Element
. show ( dijit
. byId ( 'feed_archive_remove' ). domNode
);
1559 Element
. hide ( dijit
. byId ( 'feed_archive_remove' ). domNode
);
1565 removeFromArchive : function () {
1566 var selected
= this . getSelectedFeedIds ();
1568 if ( selected
. length
> 0 ) {
1570 var pr
= __ ( "Remove selected feeds from the archive? Feeds with stored articles will not be removed." );
1573 Element
. show ( 'feed_browser_spinner' );
1575 var query
= "?op=rpc&method=remarchive&ids=" +
1576 param_escape ( selected
. toString ());
1579 new Ajax
. Request ( "backend.php" , {
1581 onComplete : function ( transport
) {
1588 execute : function () {
1589 if ( this . validate ()) {
1599 function showFeedsWithErrors () {
1600 var query
= "backend.php?op=pref-feeds&method=feedsWithErrors" ;
1602 if ( dijit
. byId ( "errorFeedsDlg" ))
1603 dijit
. byId ( "errorFeedsDlg" ). destroyRecursive ();
1605 dialog
= new dijit
. Dialog ({
1606 id
: "errorFeedsDlg" ,
1607 title
: __ ( "Feeds with update errors" ),
1608 style
: "width: 600px" ,
1609 getSelectedFeeds : function () {
1610 return getSelectedTableRowIds ( "prefErrorFeedList" );
1612 removeSelected : function () {
1613 var sel_rows
= this . getSelectedFeeds ();
1615 console
. log ( sel_rows
);
1617 if ( sel_rows
. length
> 0 ) {
1618 var ok
= confirm ( __ ( "Remove selected feeds?" ));
1621 notify_progress ( "Removing selected feeds..." , true );
1623 var query
= "?op=pref-feeds&method=remove&ids=" +
1624 param_escape ( sel_rows
. toString ());
1626 new Ajax
. Request ( "backend.php" , {
1628 onComplete : function ( transport
) {
1636 alert ( __ ( "No feeds are selected." ));
1639 execute : function () {
1640 if ( this . validate ()) {
1648 function get_timestamp () {
1649 var date
= new Date ();
1650 return Math
. round ( date
. getTime () / 1000 );
1653 function helpDialog ( topic
) {
1654 var query
= "backend.php?op=backend&method=help&topic=" + param_escape ( topic
);
1656 if ( dijit
. byId ( "helpDlg" ))
1657 dijit
. byId ( "helpDlg" ). destroyRecursive ();
1659 dialog
= new dijit
. Dialog ({
1662 style
: "width: 600px" ,
1669 function htmlspecialchars_decode ( string
, quote_style
) {
1670 // http://kevin.vanzonneveld.net
1671 // + original by: Mirek Slugen
1672 // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1673 // + bugfixed by: Mateusz "loonquawl" Zalega
1674 // + input by: ReverseSyntax
1675 // + input by: Slawomir Kaniecki
1676 // + input by: Scott Cariss
1677 // + input by: Francois
1678 // + bugfixed by: Onno Marsman
1679 // + revised by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
1680 // + bugfixed by: Brett Zamir (http://brett-zamir.me)
1681 // + input by: Ratheous
1682 // + input by: Mailfaker (http://www.weedem.fr/)
1683 // + reimplemented by: Brett Zamir (http://brett-zamir.me)
1684 // + bugfixed by: Brett Zamir (http://brett-zamir.me)
1685 // * example 1: htmlspecialchars_decode("<p>this -> "</p>", 'ENT_NOQUOTES');
1686 // * returns 1: '<p>this -> "</p>'
1687 // * example 2: htmlspecialchars_decode("&quot;");
1688 // * returns 2: '"'
1692 if ( typeof quote_style
=== 'undefined' ) {
1695 string
= string
. toString (). replace ( /</g , '<' ). replace ( />/g , '>' );
1698 'ENT_HTML_QUOTE_SINGLE' : 1 ,
1699 'ENT_HTML_QUOTE_DOUBLE' : 2 ,
1704 if ( quote_style
=== 0 ) {
1707 if ( typeof quote_style
!== 'number' ) { // Allow for a single string or an array of string flags
1708 quote_style
= []. concat ( quote_style
);
1709 for ( i
= 0 ; i
< quote_style
. length
; i
++) {
1710 // Resolve string input to bitwise e.g. 'PATHINFO_EXTENSION' becomes 4
1711 if ( OPTS
[ quote_style
[ i
]] === 0 ) {
1713 } else if ( OPTS
[ quote_style
[ i
]]) {
1714 optTemp
= optTemp
| OPTS
[ quote_style
[ i
]];
1717 quote_style
= optTemp
;
1719 if ( quote_style
& OPTS
. ENT_HTML_QUOTE_SINGLE
) {
1720 string
= string
. replace ( /�*39;/g , "'" ); // PHP doesn't currently escape if more than one 0, but it should
1721 // string = string.replace(/'|�*27;/g, "'"); // This would also be useful here, but not a part of PHP
1724 string
= string
. replace ( /"/g , '"' );
1726 // Put this in last place to avoid escape being double-decoded
1727 string
= string
. replace ( /&/g , '&' );
1733 function label_to_feed_id ( label
) {
1734 return _label_base_index
- 1 - Math
. abs ( label
);
1737 function feed_to_label_id ( feed
) {
1738 return _label_base_index
- 1 + Math
. abs ( feed
);
1741 // http://stackoverflow.com/questions/6251937/how-to-get-selecteduser-highlighted-text-in-contenteditable-element-and-replac
1743 function getSelectionText () {
1746 if ( typeof window
. getSelection
!= "undefined" ) {
1747 var sel
= window
. getSelection ();
1748 if ( sel
. rangeCount
) {
1749 var container
= document
. createElement ( "div" );
1750 for ( var i
= 0 , len
= sel
. rangeCount
; i
< len
; ++ i
) {
1751 container
. appendChild ( sel
. getRangeAt ( i
). cloneContents ());
1753 text
= container
. innerHTML
;
1755 } else if ( typeof document
. selection
!= "undefined" ) {
1756 if ( document
. selection
. type
== "Text" ) {
1757 text
= document
. selection
. createRange (). textText
;
1761 return text
. stripTags ();
1764 function openUrlPopup ( url
) {
1765 var w
= window
. open ( "" );
1770 function openArticlePopup ( id
) {
1771 var w
= window
. open ( "" ,
1772 "ttrss_article_popup" ,
1773 "height=900,width=900,resizable=yes,status=no,location=no,menubar=no,directories=no,scrollbars=yes,toolbar=no" );
1776 w
. location
= "backend.php?op=article&method=view&mode=raw&html=1&zoom=1&id=" + id
+ "&csrf_token=" + getInitParam ( "csrf_token" );