qvitter.js 156 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368
  1. /*· · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
  2. · ·
  3. · ·
  4. · Q V I T T E R ·
  5. · ·
  6. · ·
  7. · <o) ·
  8. · /_//// ·
  9. · (____/ ·
  10. · (o< ·
  11. · o> \\\\_\ ·
  12. · \\) \____) ·
  13. · ·
  14. · ·
  15. · @licstart The following is the entire license notice for the ·
  16. · JavaScript code in this page. ·
  17. · ·
  18. · Copyright (C) 2015 Hannes Mannerheim and other contributors ·
  19. · ·
  20. · ·
  21. · This program is free software: you can redistribute it and/or modify ·
  22. · it under the terms of the GNU Affero General Public License as ·
  23. · published by the Free Software Foundation, either version 3 of the ·
  24. · License, or (at your option) any later version. ·
  25. · ·
  26. · This program is distributed in the hope that it will be useful, ·
  27. · but WITHOUT ANY WARRANTY; without even the implied warranty of ·
  28. · MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ·
  29. · GNU Affero General Public License for more details. ·
  30. · ·
  31. · You should have received a copy of the GNU Affero General Public License ·
  32. · along with this program. If not, see <http://www.gnu.org/licenses/>. ·
  33. · ·
  34. · @licend The above is the entire license notice ·
  35. · for the JavaScript code in this page. ·
  36. · ·
  37. · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */
  38. // plugins can add translatons to this object
  39. window.pluginTranslations = [];
  40. // object to keep old states of streams in, to speed up stream change
  41. window.oldStreams = new Object();
  42. // check our localStorage and make sure it's correct
  43. checkLocalStorage();
  44. // don't let users inject html/scripts into their own user data... not that it matters, it is only displayed to themselves, but just to be 200% safe
  45. window.loggedIn = iterateRecursiveReplaceHtmlSpecialChars(window.loggedIn);
  46. // hack to supress basic auth popup, e.g. if the user has to tabs open and
  47. // log out in one of them. but microsoft browsers and chrome 59+ doesn't support this
  48. if(typeof bowser != 'undefined') {
  49. var bowserIntVersion = parseInt(bowser.version,10);
  50. if(typeof bowser.msie == 'undefined'
  51. && typeof bowser.msedge == 'undefined'
  52. && !(typeof bowser.chrome != 'undefined' && bowser.chrome === true && bowserIntVersion <= 59)) {
  53. window.apiRoot = window.apiRoot.replace('://','://x:x@');
  54. }
  55. }
  56. /* ·
  57. ·
  58. · Update stream on back button
  59. ·
  60. · · · · · · · · · · · · · */
  61. window.onpopstate = function(event) {
  62. if(event && event.state) {
  63. display_spinner();
  64. setNewCurrentStream(pathToStreamRouter(event.state.strm),false,false,function(){
  65. remove_spinner();
  66. });
  67. }
  68. }
  69. /* ·
  70. ·
  71. · Discard error messages
  72. ·
  73. · · · · · · · · · · · · · */
  74. $('body').on('click','.discard-error-message',function(){
  75. // don't nag on people
  76. if($(this).parent().hasClass('language-error-message')) {
  77. localStorageObjectCache_STORE('languageErrorMessageDiscarded',$(this).parent().attr('data-language-name'), true);
  78. }
  79. $(this).addClass('clicked');
  80. $(this).closest('.error-message, .language-error-message').slideUp(100,function(){
  81. $(this).remove();
  82. });
  83. });
  84. /* ·
  85. ·
  86. · welcome text expand and collapse
  87. ·
  88. · · · · · · · · · · · · · */
  89. $('body').on('click','.show-full-welcome-text, .front-welcome-text:not(.expanded) sup',function(){
  90. $('.front-welcome-text').toggleClass('expanded');
  91. if($('.front-welcome-text').hasClass('expanded')) {
  92. var welcomeTextInnerObjectsHeightSum = $('.front-welcome-text > p').outerHeight() + $('.front-welcome-text > h1').outerHeight() + 50;
  93. $('.front-welcome-text').css('height', welcomeTextInnerObjectsHeightSum + 'px')
  94. }
  95. else {
  96. $('.front-welcome-text').css('height', '180px');
  97. $('.front-welcome-text').css('overflow', 'hidden');
  98. var scrollTo = $(window).scrollTop() - ($('.front-welcome-text').outerHeight()-200);
  99. if(scrollTo < 0) { scrollTo = 0;}
  100. $('html, body').animate({ scrollTop: scrollTo}, 300, 'linear');
  101. }
  102. });
  103. $('body').on('click','.welcome-text-register-link',function(){
  104. var scrollTo = $('#user-container').offset().top;
  105. $('html, body').animate({ scrollTop: scrollTo}, 300, 'linear');
  106. });
  107. /* ·
  108. ·
  109. · Check for tooltips to display
  110. ·
  111. · · · · · · · · · · · · · */
  112. $('body').on({
  113. mouseover: function (e) {
  114. removeAllTooltips();
  115. // convert title to tooltip
  116. if($(e.target).is('[title]')) {
  117. var titleAttribute = replaceHtmlSpecialChars($(e.target).attr('title')); // can contain malicious code
  118. $(e.target).attr('data-tooltip',titleAttribute);
  119. $(e.target).removeAttr('title');
  120. }
  121. // regular tooltips
  122. if($(e.target).is('[data-tooltip]')) {
  123. var tooltipClass = '';
  124. tooltip_data = $(e.target).attr('data-tooltip');
  125. // if embedded content is hidden, we show it in tooltips
  126. if($('#feed-body').hasClass('embedded-content-hidden-by-user')
  127. && !$(e.target).is('.oembed-item')
  128. && $(e.target).closest('.queet').length > 0
  129. && $(e.target).closest('.queet').find('.oembed-item[href="' + $(e.target).attr('href') + '"]').length > 0) {
  130. tooltip_data = $(e.target).closest('.queet').find('.oembed-item[href="' + $(e.target).attr('href') + '"]').html();
  131. tooltipClass = 'oembed';
  132. }
  133. else if($('#feed-body').hasClass('embedded-content-hidden-by-user')
  134. && !$(e.target).is('.attachment-thumb')
  135. && $(e.target).closest('.queet').length > 0
  136. && $(e.target).text().indexOf('/attachment/') > -1) {
  137. // local attachments has /attachment/-url in its href attribute
  138. if($(e.target).closest('.queet').find('.thumb-container[data-local-attachment-url="' + $(e.target).attr('href') + '"]').length>0) {
  139. tooltip_data = $(e.target).closest('.queet').find('.thumb-container[data-local-attachment-url="' + $(e.target).attr('href') + '"]').outerHTML();
  140. tooltipClass = 'thumb';
  141. }
  142. // remote attachments are identified by full url
  143. else if($(e.target).closest('.queet').find('.thumb-container[href="' + $(e.target).attr('data-tooltip') + '"]').length>0) {
  144. tooltip_data = $(e.target).closest('.queet').find('.thumb-container[href="' + $(e.target).attr('data-tooltip') + '"]').outerHTML();
  145. tooltipClass = 'thumb';
  146. }
  147. // sometimes the attachment link in the queet text does not give us any clue to
  148. // which attachment it is referring to. but if it is the only link and there is
  149. // exactly one attachment, we can safely assume that the link is referring to
  150. // that attachment
  151. else if($(e.target).closest('.queet').find('.thumb-container').length == 1
  152. && $(e.target).closest('.queet-text').find('a.attachment').length == 1) {
  153. tooltip_data = $(e.target).closest('.queet').find('.thumb-container').outerHTML();
  154. tooltipClass = 'thumb';
  155. }
  156. }
  157. else if($('#feed-body').hasClass('quotes-hidden-by-user')
  158. && !$(e.target).is('.quote-link-container')
  159. && $(e.target).is('[data-quote-url]')
  160. && $(e.target).closest('.queet-text').find('.quote-link-container[data-quote-url="' + $(e.target).attr('data-quote-url') + '"]').length > 0) {
  161. tooltip_data = $(e.target).closest('.queet-text').find('.quote-link-container[data-quote-url="' + $(e.target).attr('data-quote-url') + '"]').html();
  162. tooltipClass = 'quote';
  163. }
  164. var tooltipElement = $('<div class="tooltip ' + tooltipClass + '" lang="' + window.selectedLanguage + '">' + tooltip_data + '</div>');
  165. var tooltipCaret = $('<div class="tooltip-caret"></div>');
  166. $('body').prepend(tooltipElement);
  167. $('body').prepend(tooltipCaret);
  168. // align tooltip to the hovered element
  169. alignTooltipTohoveredElement(tooltipElement,tooltipCaret,$(e.target));
  170. // fade in
  171. tooltipElement.css('opacity','1');
  172. tooltipCaret.css('opacity','1');
  173. }
  174. },
  175. mouseleave: function (e) {
  176. removeAllTooltips();
  177. }
  178. });
  179. // tooltips should be removed very easily, e.g. when any of these events happen
  180. $('body').on("touchstart scroll click dblclick mousedown mouseup submit keydown keypress keyup", function(e){
  181. removeAllTooltips();
  182. });
  183. // removes all tooltips
  184. function removeAllTooltips() {
  185. $('.tooltip,.tooltip-caret').remove();
  186. }
  187. /* ·
  188. ·
  189. · Check for profile hovercards to display
  190. ·
  191. · · · · · · · · · · · · · */
  192. window.userArrayLastRetrieved = new Object();
  193. $('body').on('mouseover',function (e) {
  194. // no hovercards on these elements
  195. if($(e.target).is('#user-queets') || $(e.target).closest('a').is('#user-queets')
  196. || $(e.target).is('.tweet-stats') || $(e.target).closest('a').is('.tweet-stats')) {
  197. return true;
  198. }
  199. var timeNow = new Date().getTime();
  200. removeAllhoverCards(e,timeNow);
  201. var hoverCardData = false;
  202. var userArray = false;
  203. var hrefAttr = false;
  204. var possibleNickname = false;
  205. // closest a-element with a href
  206. if($(e.target).is('[href]')) {
  207. var targetElement = $(e.target);
  208. }
  209. else if($(e.target).closest('a').length>0) {
  210. if($(e.target).closest('a').is('[href]')) {
  211. var targetElement = $(e.target).closest('a');
  212. }
  213. else {
  214. var targetElement = $(e.target);
  215. }
  216. }
  217. else {
  218. var targetElement = $(e.target);
  219. }
  220. // get href-attribute
  221. if(targetElement.is('[href]')) {
  222. hrefAttr = targetElement.attr('href');
  223. }
  224. else {
  225. return true;
  226. }
  227. // no hover card if the element has the no-hover-card class
  228. if(targetElement.hasClass('no-hover-card')) {
  229. return true;
  230. }
  231. // no hovercard for anchor links
  232. if(hrefAttr.substring(0,1) == '#') {
  233. return true;
  234. }
  235. // guess what element close by could be a nickname
  236. if($(e.target).is('[href]')) {
  237. possibleNickname = $(e.target).text();
  238. }
  239. else if($(e.target).closest('a').length>0 && $(e.target).closest('a').is('[href]')) {
  240. if($(e.target).siblings('.screen-name').length>0) { // the screen name can be in a sibling if we're lucky
  241. possibleNickname = $(e.target).siblings('.screen-name').text();
  242. }
  243. else {
  244. possibleNickname = $(e.target).text();
  245. }
  246. }
  247. // see if we have it in cache, otherwise query server
  248. getUserArrayData(hrefAttr, possibleNickname, timeNow, targetElement, function(userArray, timeOut){
  249. // bad data
  250. if(typeof userArray.local == 'undefined') {
  251. return;
  252. }
  253. // build card from either the local or external data, depending on what we got
  254. if (userArray.local !== null && userArray.local.is_local == true) {
  255. var profileCard = buildProfileCard(userArray.local);
  256. }
  257. else if(userArray.local !== null && userArray.local.is_local == false && (typeof userArray.external == 'undefined' || userArray.external === null || userArray.external === false)) {
  258. var profileCard = buildProfileCard(userArray.local);
  259. }
  260. else if ((userArray.local === null || userArray.local === false || userArray.local.is_local == false) && typeof userArray.external != 'undefined' && userArray.external !== false && userArray.external !== null) {
  261. var profileCard = buildExternalProfileCard(userArray);
  262. }
  263. else {
  264. console.log('could not build profile card...');
  265. return;
  266. }
  267. var hoverCardElement = $('<div id="hover-card-' + timeNow + '" class="hover-card" data-card-created="' + timeNow + '">' + profileCard.profileCardHtml + '</div>');
  268. var hoverCardCaret = $('<div id="hover-card-caret-' + timeNow + '" class="hover-card-caret"></div>');
  269. targetElement.attr('data-awaiting-hover-card',timeNow);
  270. // let user hover for 600ms before showing the card
  271. setTimeout(function(){
  272. // make sure user is still hovering the same link and that that the link awaits the same hover card
  273. // (user can have flickered on and off the link triggering two or more hover cards to in setTimeout delay)
  274. if(targetElement.is(":hover") && parseInt(targetElement.attr('data-awaiting-hover-card'),10) == timeNow) {
  275. if($('.hover-card').length == 0) { // no card if there already is one open
  276. $('body').prepend(hoverCardElement);
  277. $('body').prepend(hoverCardCaret);
  278. targetElement.attr('data-hover-card-active',timeNow);
  279. // if the user array has not been retrieved from the server for the last 60 seconds,
  280. // we query it for the lastest data
  281. if((typeof window.userArrayLastRetrieved[hrefAttr] == 'undefined') || (timeNow - window.userArrayLastRetrieved[hrefAttr]) > 60000) {
  282. window.userArrayLastRetrieved[hrefAttr] = timeNow;
  283. // local users
  284. if(userArray.local && userArray.local.is_local === true) {
  285. getFromAPI('users/show.json?id=' + userArray.local.screen_name, function(data){
  286. if(data) {
  287. var newProfileCard = buildProfileCard(data);
  288. hoverCardElement.html(newProfileCard.profileCardHtml);
  289. alignTooltipTohoveredElement(hoverCardElement,hoverCardCaret,targetElement);
  290. }
  291. });
  292. }
  293. // external users
  294. else if(!userArray.local || userArray.local.is_local === false) {
  295. getFromAPI('qvitter/external_user_show.json?profileurl=' + encodeURIComponent(hrefAttr),function(data){
  296. if(data && data.external !== null) {
  297. var newProfileCard = buildExternalProfileCard(data);
  298. hoverCardElement.html(newProfileCard.profileCardHtml);
  299. alignTooltipTohoveredElement(hoverCardElement,hoverCardCaret,targetElement);
  300. }
  301. });
  302. }
  303. }
  304. // hide tooltips
  305. $('.tooltip,.tooltip-caret').remove();
  306. // align hover card to the hovered element
  307. alignTooltipTohoveredElement(hoverCardElement,hoverCardCaret,targetElement);
  308. // fade in
  309. hoverCardElement.css('opacity','1');
  310. hoverCardCaret.css('opacity','1');
  311. }
  312. }
  313. },timeOut);
  314. });
  315. });
  316. // get user array from cache (or from server)
  317. function getUserArrayData(maybeProfileUrl,maybeNickname,timeNow,targetElement,callback) {
  318. if(maybeProfileUrl && maybeNickname) {
  319. userArray = userArrayCacheGetByProfileUrlAndNickname(maybeProfileUrl, maybeNickname);
  320. // no cached user array found, query server if this seems to be a profile url
  321. if(!userArray) {
  322. var streamObject = URLtoStreamRouter(maybeProfileUrl);
  323. // pathToStreamRouter failed finding a local stream for this path, maybe it's a remote profile?
  324. if(streamObject === false) {
  325. // we don't want to query the server every time we just pass an a-element with the cursor, so if the user
  326. // hovers the element for, say, 200ms we ask the server if the link could be a remote profile
  327. setTimeout(function(){
  328. if(targetElement.is(":hover")) {
  329. // don't try this if we already tried it less than a minute ago
  330. if((typeof window.userArrayLastRetrieved[maybeProfileUrl] == 'undefined') || (timeNow - window.userArrayLastRetrieved[maybeProfileUrl]) > 60000) {
  331. window.userArrayLastRetrieved[maybeProfileUrl] = timeNow;
  332. getFromAPI('qvitter/external_user_show.json?profileurl=' + encodeURIComponent(maybeProfileUrl),function(data){
  333. if(data) {
  334. // we want hover cards to appear _at least_ 600ms after hover (see below)
  335. var timeAfterServerQuery = new Date().getTime();
  336. var queryTime = timeAfterServerQuery-timeNow;
  337. if(queryTime<600) {
  338. var timeOut = 600-queryTime;
  339. }
  340. else {
  341. var timeOut = 0;
  342. }
  343. callback(data,timeOut);
  344. }
  345. });
  346. }
  347. }
  348. },200);
  349. }
  350. // likely an uncached local profile
  351. else if(streamObject && (streamObject.name == 'profile' || streamObject.name == 'profile by id')) {
  352. var nicknameOrId = streamObject.nickname;
  353. if(!nicknameOrId) {
  354. nicknameOrId = streamObject.id;
  355. }
  356. // don't query too often for the same user
  357. if(typeof window.userArrayLastRetrieved[maybeProfileUrl] == 'undefined' || (timeNow - window.userArrayLastRetrieved[maybeProfileUrl]) > 60000) {
  358. window.userArrayLastRetrieved[maybeProfileUrl] = timeNow;
  359. // query server and cache user data (done automatically in getFromAPI)
  360. getFromAPI('users/show.json?id=' + nicknameOrId, function(data){
  361. if(data) {
  362. userArray = {local:data};
  363. // we want hover cards to appear _at least_ 600ms after hover
  364. // we could just set the timeout to 0 and let the card appear
  365. // whenever it's loaded, but this will not feel good if we're
  366. // on a crazy fast server. so we calculate the diff time and makes
  367. // sure the total delay is at least 600ms
  368. var timeAfterServerQuery = new Date().getTime();
  369. var queryTime = timeAfterServerQuery-timeNow;
  370. if(queryTime<600) {
  371. var timeOut = 600-queryTime;
  372. }
  373. else {
  374. var timeOut = 0;
  375. }
  376. // continue to display the hover card
  377. callback(userArray,timeOut);
  378. }
  379. });
  380. }
  381. }
  382. }
  383. // from cache
  384. else {
  385. // continue to display the hover card
  386. // 600ms before cards appear feels pretty good
  387. // but this can be tweaked if cards appear to fast/slow
  388. callback(userArray,600);
  389. }
  390. }
  391. }
  392. // hover cards should be removed very easily, e.g. when any of these events happen
  393. $('body').on("mouseleave touchstart scroll click dblclick mousedown mouseup submit keydown keypress keyup", function(e){
  394. var timeNow = new Date().getTime();
  395. removeAllhoverCards(e,timeNow);
  396. });
  397. // removes all hover cards
  398. function removeAllhoverCards(event,priorTo) {
  399. // don't remove hovercards until after 100ms, so user have time to move the cursor to it (which gives it the dont-remove-card class)
  400. setTimeout(function(){
  401. $.each($('.hover-card'),function(){
  402. // don't remove card if it was created after removeAllhoverCards() was called
  403. if($(this).data('card-created') < priorTo) {
  404. // don't remove it if we're hovering it right now!
  405. if(!$(this).hasClass('dont-remove-card')) {
  406. $('[data-hover-card-active="' + $(this).data('card-created') + '"]').removeAttr('data-hover-card-active');
  407. $('#hover-card-caret-' + $(this).data('card-created')).remove();
  408. $(this).remove();
  409. }
  410. }
  411. });
  412. },100);
  413. }
  414. // if we're hovering a hover card, give it a class, so we don't remove it
  415. $('body').on('mouseover','.hover-card', function(e) {
  416. $(this).addClass('dont-remove-card');
  417. });
  418. $('body').on('mouseleave','.hover-card', function(e) {
  419. $(this).removeClass('dont-remove-card');
  420. });
  421. /* ·
  422. ·
  423. · find someone tool
  424. ·
  425. · · · · · · · · · · · · · */
  426. $('#find-someone input').keyup(function(e){
  427. var thisFindSomeoneInput = $(this);
  428. if(e.keyCode==13 && !thisFindSomeoneInput.hasClass('submitted')) {
  429. thisFindSomeoneInput.addClass('submitted');
  430. thisFindSomeoneInput.attr('disabled','disabled');
  431. var val = $.trim(thisFindSomeoneInput.val());
  432. // if this is a simple text input, we assume it is a local user
  433. if(val.length>1 && /^(@)?[a-zA-Z0-9]+$/.test(val)) {
  434. if(val.indexOf('@') == 0) {
  435. val = val.replace('@','');
  436. }
  437. setNewCurrentStream(pathToStreamRouter(val),true,false,function(){
  438. foundSomeone(thisFindSomeoneInput);
  439. });
  440. }
  441. // urls might be a remote user
  442. else if(val.length==0 || /^(ftp|http|https):\/\/[^ "]+$/.test(val)) {
  443. getFromAPI('qvitter/external_user_show.json?profileurl=' + encodeURIComponent(val),function(data){
  444. if(data && data.local !== null) {
  445. setNewCurrentStream(pathToStreamRouter('user/' + data.local.id),true,false,function(){
  446. foundSomeone(thisFindSomeoneInput);
  447. });
  448. }
  449. else {
  450. cantFindSomeone(thisFindSomeoneInput);
  451. }
  452. });
  453. }
  454. // @user@instance.domain style syntax
  455. else if(val.length==0 || /^(@)?[a-zA-Z0-9]+@[a-zA-Z0-9\-]+(\.)(.*)+$/.test(val)) {
  456. if(val.indexOf('@') == 0) {
  457. val = val.substring(1)
  458. }
  459. var username = val.substring(0, val.indexOf('@'));
  460. var domain = val.substring(val.indexOf('@')+1);
  461. var urlToTry = 'https://' + domain + '/' + username;
  462. var secondUrlToTry = 'http://' + domain + '/' + username;
  463. var thirdUrlToTry = 'https://' + domain + '/@' + username; // mastodon
  464. getFromAPI('qvitter/external_user_show.json?profileurl=' + encodeURIComponent(urlToTry),function(data){
  465. if(data && data.local !== null) {
  466. setNewCurrentStream(pathToStreamRouter('user/' + data.local.id),true,false,function(){
  467. foundSomeone(thisFindSomeoneInput);
  468. });
  469. }
  470. else {
  471. getFromAPI('qvitter/external_user_show.json?profileurl=' + encodeURIComponent(secondUrlToTry),function(data){
  472. if(data && data.local !== null) {
  473. setNewCurrentStream(pathToStreamRouter('user/' + data.local.id),true,false,function(){
  474. foundSomeone(thisFindSomeoneInput);
  475. });
  476. }
  477. else {
  478. getFromAPI('qvitter/external_user_show.json?profileurl=' + encodeURIComponent(thirdUrlToTry),function(data){
  479. if(data && data.local !== null) {
  480. setNewCurrentStream(pathToStreamRouter('user/' + data.local.id),true,false,function(){
  481. foundSomeone(thisFindSomeoneInput);
  482. });
  483. }
  484. else {
  485. cantFindSomeone(thisFindSomeoneInput);
  486. }
  487. });
  488. }
  489. });
  490. }
  491. });
  492. }
  493. else {
  494. cantFindSomeone(thisFindSomeoneInput);
  495. }
  496. }
  497. });
  498. function cantFindSomeone(thisFindSomeoneInput) {
  499. thisFindSomeoneInput.css('background-color','pink');
  500. thisFindSomeoneInput.effect('shake',{distance:5,times:3,duration:700},function(){
  501. thisFindSomeoneInput.animate({backgroundColor:'#fff'},1000);
  502. thisFindSomeoneInput.removeAttr('disabled');
  503. thisFindSomeoneInput.removeClass('submitted');
  504. thisFindSomeoneInput.focus();
  505. });
  506. }
  507. function foundSomeone(thisFindSomeoneInput) {
  508. thisFindSomeoneInput.removeAttr('disabled');
  509. thisFindSomeoneInput.val('');
  510. thisFindSomeoneInput.blur();
  511. thisFindSomeoneInput.removeClass('submitted');
  512. }
  513. /* ·
  514. ·
  515. · fix login and register box to top when they reach top
  516. ·
  517. · · · · · · · · · · · · · */
  518. $(window).scroll(function(e){
  519. if($('#page-container > .profile-card').length > 0) {
  520. var feedOrProfileCardOffsetTop = $('#page-container > .profile-card').offset().top;
  521. }
  522. else {
  523. var feedOrProfileCardOffsetTop = $('#feed').offset().top;
  524. }
  525. if ($(this).scrollTop() > (feedOrProfileCardOffsetTop-55) && $('#login-content').css('position') != 'fixed'){
  526. var loginAndSignUpBoxesHeight = $('#login-content').outerHeight() + $('.front-signup').not('#popup-signup').outerHeight();
  527. $('#login-register-container').css({'position': 'fixed', 'top': '55px'});
  528. }
  529. else if ($(this).scrollTop() < (feedOrProfileCardOffsetTop-55) && $('#login-content').css('position') != 'absolute'){
  530. $('#login-register-container').css({'position': 'relative', 'top': 'auto'});
  531. }
  532. });
  533. /* ·
  534. ·
  535. · Tooltip to show what federated means
  536. ·
  537. · · · · · · · · · · · · · */
  538. $('body').on('mouseenter','#federated-tooltip',function(){
  539. $('#what-is-federation').fadeIn(100);
  540. });
  541. $('body').on('mouseleave','#what-is-federation',function(){
  542. $('#what-is-federation').fadeOut(100);
  543. });
  544. /* ·
  545. ·
  546. · Scroll to top when clicking top bar
  547. ·
  548. · · · · · · · · · · · · · */
  549. $('body').on('click','.global-nav',function(e) {
  550. if($(e.target).hasClass('global-nav')) {
  551. $(window).scrollTop(0);
  552. }
  553. });
  554. /* ·
  555. ·
  556. · Register
  557. ·
  558. · · · · · · · · · · · · · */
  559. if(!window.registrationsClosed) {
  560. $('.front-signup input, .front-signup button').removeAttr('disabled'); // clear this onload
  561. $('#signup-btn-step1').click(function(){
  562. $(document).trigger('onClickStep1Register'); // hook
  563. display_spinner();
  564. $('.front-signup input, .front-signup button').addClass('disabled');
  565. $('.front-signup input, .front-signup button').attr('disabled','disabled');
  566. // 7 s timeout to annoy human spammers
  567. setTimeout(function(){
  568. remove_spinner();
  569. popUpAction('popup-register',window.sL.signUp,'<div id="popup-signup" class="front-signup">' +
  570. '<div class="signup-input-container"><div id="atsign">@</div><input placeholder="' + window.sL.registerNickname + '" type="text" autocomplete="off" class="text-input" id="signup-user-nickname-step2"><div class="fieldhelp">a-z0-9</div></div>' +
  571. '<div class="signup-input-container"><input placeholder="' + window.sL.signUpFullName + '" type="text" autocomplete="off" class="text-input" id="signup-user-name-step2" value="' + $('#signup-user-name').val() + '"></div>' +
  572. '<div class="signup-input-container"><input placeholder="' + window.sL.signUpEmail + '" type="text" autocomplete="off" id="signup-user-email-step2" value="' + $('#signup-user-email').val() + '"></div>' +
  573. '<div class="signup-input-container"><input placeholder="' + window.sL.registerHomepage + '" type="text" autocomplete="off" class="text-input" id="signup-user-homepage-step2"></div>' +
  574. '<div class="signup-input-container"><input placeholder="' + window.sL.registerBio + '" type="text" autocomplete="off" class="text-input" id="signup-user-bio-step2"></div>' +
  575. '<div class="signup-input-container"><input placeholder="' + window.sL.registerLocation + '" type="text" autocomplete="off" class="text-input" id="signup-user-location-step2"></div>' +
  576. '<div class="signup-input-container"><input placeholder="' + window.sL.loginPassword + '" type="password" class="text-input" id="signup-user-password1-step2" value="' + $('#signup-user-password').val() + '"><div class="fieldhelp">>5</div></div>' +
  577. '<div class="signup-input-container"><input placeholder="' + window.sL.registerRepeatPassword + '" type="password" class="text-input" id="signup-user-password2-step2"></div>' +
  578. '<div id="signup-terms-header">' + window.sL.showTerms.replace(/{site-title}/g,window.siteTitle) + '</div><div id="signup-terms-container"></div>' +
  579. '<button id="signup-btn-step2" class="signup-btn disabled" type="submit">' + window.sL.signUpButtonText + '</button>' +
  580. '</div>',false);
  581. // ask api if nickname is ok, if no typing for 1 s
  582. $('#signup-user-nickname-step2').on('keyup',function(){
  583. clearTimeout(window.checkNicknameTimeout);
  584. var thisInputElement = $(this);
  585. var thisValue = $(this).val();
  586. if(thisValue.length>1 && /^[a-zA-Z0-9]+$/.test(thisValue)) {
  587. thisInputElement.addClass('nickname-taken');
  588. if($('.spinner-wrap').length==0) {
  589. thisInputElement.after('<div class="spinner-wrap"><div class="spinner"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>');
  590. }
  591. window.checkNicknameTimeout = setTimeout(function(){
  592. $.get(window.apiRoot + 'check_nickname.json?nickname=' + encodeURIComponent(thisValue),function(data){
  593. $('.spinner-wrap').remove();
  594. if(data==0) {
  595. $('#signup-user-password2-step2').trigger('keyup'); // revalidates
  596. }
  597. else {
  598. thisInputElement.removeClass('nickname-taken');
  599. $('#signup-user-password2-step2').trigger('keyup');
  600. }
  601. });
  602. },1000);
  603. }
  604. else {
  605. $('.spinner-wrap').remove();
  606. }
  607. });
  608. // ask api if email is in use, if no typing for 1 s
  609. $('#signup-user-email-step2').on('keyup',function(){
  610. clearTimeout(window.checkEmailTimeout);
  611. var thisInputElement = $(this);
  612. var thisValue = $(this).val();
  613. if(thisValue.length>1 && validEmail(thisValue)) {
  614. thisInputElement.addClass('email-in-use');
  615. if($('.spinner-wrap').length==0) {
  616. thisInputElement.after('<div class="spinner-wrap"><div class="spinner"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>');
  617. }
  618. window.checkEmailTimeout = setTimeout(function(){
  619. $.get(window.apiRoot + 'qvitter/check_email.json?email=' + encodeURIComponent(thisValue),function(data){
  620. $('.spinner-wrap').remove();
  621. if(data==1) {
  622. $('#signup-user-password2-step2').trigger('keyup'); // revalidates
  623. thisInputElement.after('<div class="fieldhelp email-in-use">' + window.sL.emailAlreadyInUse + '</div>');
  624. }
  625. else {
  626. thisInputElement.removeClass('email-in-use');
  627. thisInputElement.siblings('.fieldhelp.email-in-use').remove();
  628. $('#signup-user-password2-step2').trigger('keyup');
  629. }
  630. });
  631. },1000);
  632. }
  633. else {
  634. $('.spinner-wrap').remove();
  635. }
  636. });
  637. // validate on keyup / paste / blur
  638. $('#popup-register input').on('keyup paste blur',function(){
  639. setTimeout(function () { // defer validation as after paste the content is not immediately available
  640. if(validateRegisterForm($('#popup-register'))
  641. && !$('#signup-user-nickname-step2').hasClass('nickname-taken')
  642. && !$('#signup-user-email-step2').hasClass('email-in-use')) {
  643. $('#signup-btn-step2').removeClass('disabled');
  644. }
  645. else {
  646. $('#signup-btn-step2').addClass('disabled');
  647. }
  648. }, 0);
  649. });
  650. $('#popup-register input').trigger('keyup');
  651. // submit on enter
  652. $('input#signup-user-name-step2,input#signup-user-email-step2,input#signup-user-password1-step2, input#signup-user-password2-step2').keyup(function(e){
  653. if(e.keyCode==13) {
  654. $('#signup-btn-step2').trigger('click');
  655. }
  656. });
  657. $('#signup-btn-step2').click(function(){
  658. if(!$(this).hasClass('disabled')) {
  659. $('#popup-register input,#popup-register button').addClass('disabled');
  660. display_spinner();
  661. $.ajax({ url: window.apiRoot + 'account/register.json',
  662. type: "POST",
  663. data: {
  664. nickname: $('#signup-user-nickname-step2').val(),
  665. email: $('#signup-user-email-step2').val(),
  666. fullname: $('#signup-user-name-step2').val(),
  667. homepage: $('#signup-user-homepage-step2').val(),
  668. bio: $('#signup-user-bio-step2').val(),
  669. location: $('#signup-user-location-step2').val(),
  670. password: $('#signup-user-password1-step2').val(),
  671. confirm: $('#signup-user-password2-step2').val(),
  672. cBS: window.cBS,
  673. cBSm: window.cBSm,
  674. username: 'none',
  675. },
  676. dataType:"json",
  677. error: function(data){
  678. if(typeof data.responseJSON != 'undefined' && typeof data.responseJSON.error != 'undefined') {
  679. remove_spinner();
  680. $('#popup-register input,#popup-register button').removeClass('disabled');
  681. $('#signup-user-password2-step2').trigger('keyup'); // revalidate
  682. showErrorMessage(data.responseJSON.error,$('#popup-register .modal-header'));
  683. }
  684. },
  685. success: function(data) {
  686. remove_spinner();
  687. if(typeof data.error == 'undefined') {
  688. $('input#nickname').val($('#signup-user-nickname-step2').val());
  689. $('input#password').val($('#signup-user-password1-step2').val());
  690. $('input#rememberme').prop('checked', true);
  691. $('#submit-login').trigger('click');
  692. $('#popup-register').remove();
  693. }
  694. else {
  695. alert('Try again! ' + data.error);
  696. $('#popup-register input,#popup-register button').removeClass('disabled');
  697. }
  698. }
  699. });
  700. }
  701. });
  702. // reactivate register form on popup close
  703. $('#popup-register').on('remove',function(){
  704. $('.front-signup input, .front-signup button').removeAttr('disabled');
  705. $('.front-signup input, .front-signup button').removeClass('disabled');
  706. });
  707. },7000);
  708. });
  709. // submit on enter
  710. $('input#signup-user-name,input#signup-user-email,input#signup-user-password').keyup(function(e){
  711. if(e.keyCode==13) {
  712. $('#signup-btn-step1').trigger('click');
  713. }
  714. });
  715. }
  716. /* ·
  717. ·
  718. · Show/hide Terms of Use
  719. ·
  720. · · · · · · · · · · · · · */
  721. $('body').on('click','#signup-terms-header',function(){
  722. if($('#signup-terms-container').text().length > 0) {
  723. $('#signup-terms-container').html('');
  724. }
  725. else {
  726. if(window.customTermsOfUse) {
  727. $('#signup-terms-container').html(window.customTermsOfUse);
  728. }
  729. else {
  730. getDoc('terms',function(termsHtml){
  731. $('#signup-terms-container').html(termsHtml);
  732. });
  733. }
  734. }
  735. });
  736. /* ·
  737. ·
  738. · set language, autologin or show welcome screen
  739. ·
  740. · · · · · · · · · · · · · */
  741. $('#submit-login').removeAttr('disabled'); // might be remebered by browser...
  742. $(window).load(function() {
  743. // set language, from local storage, else browser language
  744. var browserLang = navigator.language || navigator.userLanguage;
  745. // use english if browser has no language set
  746. if(typeof browserLang == 'undefined') {
  747. var browserLang = 'en-GB';
  748. }
  749. // browsers report e.g. sv-SE but we want it in the format "sv" or "sv_se"
  750. browserLang = browserLang.replace('-','_').toLowerCase();
  751. // if we're logged out, we see if there's a saved language in localstorage
  752. if(window.loggedIn === false) {
  753. var cacheDataLoggedOut = localStorageObjectCache_GET('selectedLanguage','logged_out');
  754. if(cacheDataLoggedOut) {
  755. window.selectedLanguage = cacheDataLoggedOut;
  756. }
  757. else {
  758. window.selectedLanguage = browserLang;
  759. }
  760. }
  761. // if we're logged in, we check
  762. // 1) check if the logged in user has selected a language
  763. // 2) check if there a selected language for logged_out users (i.e. the user selected the language before logging in)
  764. // 3) go with the browser language
  765. else {
  766. var cacheDataLoggedOut = localStorageObjectCache_GET('selectedLanguage','logged_out');
  767. var cacheDataLoggedIn = localStorageObjectCache_GET('selectedLanguage',window.loggedIn.id);
  768. if(cacheDataLoggedIn) {
  769. window.selectedLanguage = cacheDataLoggedIn;
  770. }
  771. else if(cacheDataLoggedOut) {
  772. window.selectedLanguage = cacheDataLoggedOut;
  773. }
  774. else {
  775. window.selectedLanguage = browserLang;
  776. }
  777. }
  778. // check that the language is available,
  779. if(typeof window.availableLanguages[window.selectedLanguage] == 'undefined') {
  780. var similarLanguageFound = false;
  781. // if not there might be a base language, e.g. "sv" instead of "sv_se"
  782. if(window.selectedLanguage.indexOf('_') > -1
  783. && typeof window.availableLanguages[window.selectedLanguage.substring(0,window.selectedLanguage.indexOf('_'))] != 'undefined') {
  784. window.selectedLanguage = window.selectedLanguage.substring(0,window.selectedLanguage.indexOf('_'));
  785. similarLanguageFound = true;
  786. }
  787. else {
  788. // there's also a chance there no base language, but a similar country specific language that we can use (rather than english)
  789. if(window.selectedLanguage.indexOf('_') > -1) {
  790. var baseLan = window.selectedLanguage.indexOf('_');
  791. }
  792. else {
  793. var baseLan = window.selectedLanguage;
  794. }
  795. $.each(window.availableLanguages, function(lanCode,lanData){
  796. if(lanCode.substring(0,lanCode.indexOf('_')) == baseLan) {
  797. window.selectedLanguage = lanCode;
  798. similarLanguageFound = true;
  799. return false;
  800. }
  801. });
  802. }
  803. // if we can't find a similar language, go with english
  804. if(similarLanguageFound === false) {
  805. window.selectedLanguage = 'en';
  806. }
  807. }
  808. // english is always available
  809. if(window.selectedLanguage == 'en') {
  810. proceedToSetLanguageAndLogin(window.englishLanguageData);
  811. }
  812. else {
  813. // if we already have this version of this language in localstorage, we
  814. // use that cached version. we do this because $.ajax doesn't respect caching, it seems
  815. var cacheData = localStorageObjectCache_GET('languageData',window.availableLanguages[window.selectedLanguage]);
  816. if(cacheData) {
  817. proceedToSetLanguageAndLogin(cacheData);
  818. }
  819. else {
  820. $.ajax({
  821. dataType: "json",
  822. url: window.fullUrlToThisQvitterApp + 'locale/' + window.availableLanguages[window.selectedLanguage],
  823. error: function(data){console.log(data)},
  824. success: function(data) {
  825. // store this response in localstorage
  826. localStorageObjectCache_STORE('languageData',window.availableLanguages[window.selectedLanguage], data);
  827. proceedToSetLanguageAndLogin(data);
  828. }
  829. });
  830. }
  831. }
  832. });
  833. // proceed to set language and login
  834. function proceedToSetLanguageAndLogin(data){
  835. window.sL = data;
  836. // plugins might have added translations
  837. $.each(window.pluginTranslations,function(k,pluginTranslation) {
  838. if(typeof pluginTranslation[window.selectedLanguage] != 'undefined') {
  839. $.extend(window.sL,pluginTranslation[window.selectedLanguage]);
  840. }
  841. else if(typeof pluginTranslation['en'] != 'undefined') {
  842. $.extend(window.sL,pluginTranslation['en']);
  843. }
  844. });
  845. // plugins might want to wait and do stuff until after language is set
  846. $(document).trigger('qvitterAfterLanguageIsSet');
  847. // if this is a RTL-language, add rtl class to body
  848. if(window.sL.directionality == 'rtl') {
  849. $('body').addClass('rtl');
  850. }
  851. window.siteTitle = $('head title').html(); // remember this for later use
  852. // replace placeholders in translation
  853. $.each(window.sL,function(k,v){
  854. window.sL[k] = v.replace(/{site-title}/g,window.siteTitle);
  855. });
  856. // suggest user to help translate if their browsers language does't exist
  857. if(typeof window.availableLanguages[window.usersLanguageCode] == 'undefined' && !localStorageObjectCache_GET('languageErrorMessageDiscarded',window.usersLanguageNameInEnglish)) { // but don't nag
  858. $('#page-container').prepend('<div class="language-error-message" data-language-name="' + window.usersLanguageNameInEnglish + '">' + window.siteTitle + ' is not availible in your language (' + replaceHtmlSpecialChars(window.usersLanguageNameInEnglish) + '). Visit <a href="https://git.gnu.io/h2p/Qvitter/tree/master/locale">Qvitter\'s repository homepage</a> if you want to help us to translate the interface. <span class="discard-error-message"></span></div>');
  859. }
  860. // if selected language is too similar to english, we display a message telling people to help with the translation
  861. if(window.sL.languageName != 'English') {
  862. var numberOfStringsSameAsEnglish = 0;
  863. var totalStrings = 0;
  864. $.each(window.sL,function(k,v){
  865. if(v == window.englishLanguageData[k]) {
  866. numberOfStringsSameAsEnglish++;
  867. }
  868. totalStrings++;
  869. });
  870. numberOfStringsSameAsEnglish = Math.max(0, numberOfStringsSameAsEnglish-20); totalStrings = Math.max(0, totalStrings-20); // ~20 strings, e.g. shortened months, is often same in many languages
  871. var percentTranslated = parseInt((1-(numberOfStringsSameAsEnglish/totalStrings))*100,10);
  872. if(percentTranslated < 95) {
  873. if(!localStorageObjectCache_GET('languageErrorMessageDiscarded',window.sL.languageName)) { // but don't nag
  874. $('#page-container').prepend('<div class="language-error-message" data-language-name="' + window.sL.languageName + '">' + window.sL.onlyPartlyTranslated.replace('{percent}',percentTranslated).replace('{language-name}',window.sL.languageName) + '<span class="discard-error-message"></span></div>');
  875. }
  876. }
  877. }
  878. // set some static strings
  879. if(window.customWelcomeText !== false && typeof window.customWelcomeText[window.selectedLanguage] != 'undefined') {
  880. $('.front-welcome-text').html(window.customWelcomeText[window.selectedLanguage]);
  881. // collapse long welcome texts and add expand button
  882. if($('.front-welcome-text').outerHeight()>250) {
  883. $('.front-welcome-text').css('height','240px');
  884. $('.front-welcome-text').css('overflow', 'hidden');
  885. $('.front-welcome-text').append('<div class="show-full-welcome-text"></div>');
  886. }
  887. }
  888. else {
  889. $('.front-welcome-text').html('<h1>' + window.sL.welcomeHeading + '</h1>');
  890. if(window.enableWelcomeText) {
  891. $('.front-welcome-text').append(window.sL.welcomeText);
  892. }
  893. }
  894. $('#nickname').attr('placeholder',window.sL.loginUsername);
  895. $('#password').attr('placeholder',window.sL.loginPassword);
  896. $('button#submit-login').html(window.sL.loginSignIn);
  897. $('#rememberme_label').html(window.sL.loginRememberMe);
  898. $('#forgot-password').html(window.sL.loginForgotPassword);
  899. $('.front-signup h2').html('<strong>' + window.sL.newToQuitter + '</strong> ' + window.sL.signUp);
  900. $('#signup-user-name').attr('placeholder',window.sL.signUpFullName);
  901. $('#signup-user-email').attr('placeholder',window.sL.signUpEmail);
  902. $('#signup-user-password').attr('placeholder',window.sL.loginPassword);
  903. $('.front-signup button.signup-btn').html(window.sL.signUpButtonText);
  904. $('#user-queets .label').html(window.sL.notices);
  905. $('#user-following .label').html(window.sL.following);
  906. $('#user-followers .label').html(window.sL.followers);
  907. $('#user-groups .label').html(window.sL.groups);
  908. $('#queet-box').html(window.sL.compose);
  909. $('#queet-box').attr('data-start-text',encodeURIComponent(window.sL.compose));
  910. $('#user-footer .queet-button button').html(window.sL.queetVerb);
  911. $('#stream-header').html(window.sL.queetsNounPlural);
  912. $('#logout').html(window.sL.logout);
  913. $('#settings').html(window.sL.settings);
  914. $('#other-servers-link').html(window.sL.otherServers);
  915. $('.language-dropdown .dropdown-toggle small').html(window.sL.languageSelected);
  916. $('.language-dropdown .current-language').html(window.sL.languageName);
  917. $('.stream-selection.friends-timeline').prepend(window.sL.timeline);
  918. $('.stream-selection.mentions').prepend(window.sL.mentions);
  919. $('.stream-selection.notifications').prepend(window.sL.notifications);
  920. $('.stream-selection.favorites').prepend(window.sL.favoritesNoun);
  921. $('.stream-selection.public-timeline').prepend(window.sL.publicTimeline);
  922. $('.stream-selection.public-and-external-timeline').prepend(window.sL.publicAndExtTimeline)
  923. $('#search-query').attr('placeholder',window.sL.searchVerb);
  924. $('#blocking-link').html(window.sL.userBlocks);
  925. $('#faq-link').html(window.sL.FAQ);
  926. $('#tou-link').html(window.sL.showTerms);
  927. $('#add-edit-language-link').html(window.sL.addEditLanguageLink);
  928. $('#shortcuts-link').html(window.sL.keyboardShortcuts);
  929. $('#invite-link').html(window.sL.inviteAFriend);
  930. $('#classic-link').html(window.sL.classicInterface);
  931. $('#edit-profile-header-link').html(window.sL.editMyProfile);
  932. $('#mini-logged-in-user-cog-wheel').attr('data-tooltip',window.sL.profileSettings);
  933. $('#accessibility-toggle-link').html(window.sL.accessibilityToggleLink);
  934. $('#settingslink .nav-session').attr('data-tooltip',window.sL.profileAndSettings);
  935. $('#top-compose').attr('data-tooltip',window.sL.compose);
  936. $('button.upload-image').attr('data-tooltip',window.sL.tooltipAttachFile);
  937. $('button.shorten').attr('data-tooltip',window.sL.tooltipShortenUrls);
  938. $('.reload-stream').attr('data-tooltip',window.sL.tooltipReloadStream);
  939. $('#clear-history').html(window.sL.clearHistory);
  940. $('#user-screen-name, #user-avatar, #user-name').attr('data-tooltip', window.sL.viewMyProfilePage);
  941. $('#top-menu-profile-link-view-profile').html(window.sL.viewMyProfilePage);
  942. $('#find-someone input').attr('placeholder',window.sL.findSomeone);
  943. $('#find-someone input').attr('data-tooltip',window.sL.findSomeoneTooltip);
  944. // show site body now
  945. $('#user-container').css('display','block');
  946. $('#feed').css('display','block');
  947. // logged in or not?
  948. if(window.loggedIn) {
  949. proceedLoggedIn();
  950. }
  951. else {
  952. proceedLoggedOut();
  953. }
  954. }
  955. /* ·
  956. ·
  957. · Stuff we do on load specifically for logged out users
  958. ·
  959. · · · · · · · · · · · · · */
  960. function proceedLoggedOut() {
  961. display_spinner();
  962. setNewCurrentStream(getStreamFromUrl(),true,false,function(){
  963. // $('input#nickname').focus(); --> maybe not a good idea on mobile?
  964. $('#page-container').css('opacity','1');
  965. });
  966. }
  967. /* ·
  968. ·
  969. · Stuff we do on load specifically for logged in users
  970. ·
  971. · · · · · · · · · · · · · */
  972. function proceedLoggedIn() {
  973. display_spinner();
  974. // get everyone we follow, block and our memberships and stor in global objects
  975. getAllFollowsMembershipsAndBlocks(function(){
  976. // do this now not to stall slow computers, also we know of group memberships to highlight now
  977. cacheSyntaxHighlighting();
  978. // we might have cached text for the queet box
  979. // (we need to get the mentions suggestions and cache the syntax highlighting before doing this)
  980. $('#queet-box').attr('data-cached-text',encodeURIComponent(localStorageObjectCache_GET('queetBoxInput','queet-box')));
  981. maybePrefillQueetBoxWithCachedText($('#queet-box'));
  982. });
  983. // load history
  984. loadHistoryFromLocalStorage();
  985. // show bookmarks
  986. appendAllBookmarks(window.qvitterProfilePrefs.bookmarks);
  987. // set stream
  988. setNewCurrentStream(getStreamFromUrl(),true,false,function(){
  989. $('.language-dropdown').css('display','none');
  990. $('#page-container').css('opacity','1');
  991. $('#search').fadeIn('slow');
  992. $('#settingslink .dropdown-toggle').fadeIn('slow');
  993. $('#top-compose').fadeIn('slow');
  994. $('input#nickname').blur();
  995. });
  996. }
  997. /* ·
  998. ·
  999. · Shake the login box, i.e. when indicating an error
  1000. ·
  1001. · · · · · · · · · · · · · */
  1002. function shakeLoginBox() {
  1003. $('input#nickname').css('background-color','pink');
  1004. $('input#password').css('background-color','pink');
  1005. $('#login-content').effect('shake',{distance:5,times:2},function(){
  1006. $('input#nickname').animate({backgroundColor:'#fff'},1000);
  1007. $('input#password').animate({backgroundColor:'#fff'},1000);
  1008. });
  1009. }
  1010. /* ·
  1011. ·
  1012. · In the login form, we want to check the remember-me-checkbox when its label is clicked
  1013. ·
  1014. · · · · · · · · · · · · · */
  1015. $('#rememberme_label').click(function(){
  1016. if($('#rememberme').prop('checked')) {
  1017. $('#rememberme').prop('checked', false);
  1018. }
  1019. else {
  1020. $('#rememberme').prop('checked', true);
  1021. }
  1022. });
  1023. $('#rememberme_label').disableSelection();
  1024. /* ·
  1025. ·
  1026. · Submit login form
  1027. ·
  1028. · · · · · · · · · · · · · */
  1029. $('#form_login').submit(function(e) {
  1030. if(typeof window.loginCheckSuccessful == 'undefined') {
  1031. e.preventDefault();
  1032. checkLogin($('input#nickname').val(),$('input#password').val(),function(data){
  1033. window.loginCheckSuccessful = true;
  1034. $('#form_login').submit();
  1035. });
  1036. }
  1037. });
  1038. /* ·
  1039. ·
  1040. · Logout
  1041. ·
  1042. · · · · · · · · · · · · · */
  1043. $('#logout').click(function(){
  1044. window.location.href =window.siteInstanceURL + 'main/logout';
  1045. });
  1046. /* ·
  1047. ·
  1048. · FAQ
  1049. ·
  1050. · · · · · · · · · · · · · */
  1051. $('#faq-link').click(function(){
  1052. popUpAction('popup-faq', window.siteTitle + ' ' + window.sL.FAQ,'<div id="faq-container"></div>',false);
  1053. getDoc('faq',function(faqHtml){
  1054. $('#faq-container').html(faqHtml);
  1055. });
  1056. });
  1057. /* ·
  1058. ·
  1059. · Terms
  1060. ·
  1061. · · · · · · · · · · · · · */
  1062. $('#tou-link,.tou-link').click(function(){
  1063. popUpAction('popup-terms', window.sL.showTerms,'<div id="terms-container"></div>',false);
  1064. if(window.customTermsOfUse) {
  1065. $('#terms-container').html(window.customTermsOfUse);
  1066. }
  1067. else {
  1068. getDoc('terms',function(termsHtml){
  1069. $('#terms-container').html(termsHtml);
  1070. });
  1071. }
  1072. });
  1073. /* ·
  1074. ·
  1075. · Classic Link, toggle setting in api and redirect to /all
  1076. ·
  1077. · · · · · · · · · · · · · */
  1078. $('#classic-link, #accessibility-toggle-link').click(function(){
  1079. getFromAPI('qvitter/toggle_qvitter.json',function(data){
  1080. if(data.success === true) {
  1081. window.location.href = window.siteInstanceURL + window.loggedIn.screen_name + '/all';
  1082. }
  1083. });
  1084. });
  1085. /* ·
  1086. ·
  1087. · Handling the language dropdown selection
  1088. ·
  1089. · · · · · · · · · · · · · */
  1090. $('.dropdown').click(function(){$(this).toggleClass('dropped')});
  1091. $('.dropdown').disableSelection();
  1092. $(document).bind('click', function (e) {
  1093. if(!$(e.target).is('#logo') && !$(e.target).is('#settingslink') && !$(e.target).is('.nav-session') && !$(e.target).is('.dropdown-toggle') && !$(e.target).is('.dropdown-toggle small') && !$(e.target).is('.dropdown-toggle span') && !$(e.target).is('.dropdown-toggle b')) {
  1094. $('.dropdown').removeClass('dropped');
  1095. $('.quitter-settings.dropdown-menu').removeClass('dropped');
  1096. }
  1097. });
  1098. $('.language-link').click(function(){
  1099. if(window.loggedIn === false) {
  1100. var selectedForUser = 'logged_out';
  1101. }
  1102. else {
  1103. var selectedForUser = window.loggedIn.id;
  1104. }
  1105. localStorageObjectCache_STORE('selectedLanguage',selectedForUser, $(this).attr('data-lang-code'));
  1106. location.reload(); // reload
  1107. });
  1108. /* ·
  1109. ·
  1110. · Show the logo menu dropdown on click
  1111. ·
  1112. · · · · · · · · · · · · · */
  1113. $('#settingslink').click(function(){
  1114. removeAllTooltips();
  1115. if(!$('.quitter-settings').hasClass('dropped')) { $('.quitter-settings').addClass('dropped'); }
  1116. else { $('.quitter-settings').removeClass('dropped'); }
  1117. });
  1118. /* ·
  1119. ·
  1120. · Show/hide the user menu dropdown on click
  1121. ·
  1122. · · · · · · · · · · · · · */
  1123. $('body').on('click','.user-menu-cog',function(e){
  1124. if(!$(e.target).is($(this))) {
  1125. // don't show/hide when clicking inside the menu
  1126. }
  1127. // hide
  1128. else if($(this).hasClass('dropped')) {
  1129. $(this).removeClass('dropped');
  1130. $(this).children('.dropdown-menu').remove();
  1131. }
  1132. // show
  1133. else {
  1134. $(this).addClass('dropped');
  1135. var userID = $(this).attr('data-user-id');
  1136. var userScreenName = $(this).attr('data-screen-name');
  1137. var silenced = $(this).hasClass('silenced');
  1138. var sandboxed = $(this).hasClass('sandboxed');
  1139. // menu
  1140. var menuArray = [];
  1141. // settings etc if it's me
  1142. if(userID == window.loggedIn.id) {
  1143. menuArray = loggedInUsersMenuArray();
  1144. }
  1145. // block etc if it not me
  1146. else {
  1147. if(userIsBlocked(userID)) {
  1148. menuArray.push({
  1149. type: 'function',
  1150. functionName: 'unblockUser',
  1151. functionArguments: {
  1152. userId: userID
  1153. },
  1154. label: window.sL.unblockUser.replace('{username}','@' + userScreenName)
  1155. });
  1156. }
  1157. else {
  1158. menuArray.push({
  1159. type: 'function',
  1160. functionName: 'blockUser',
  1161. functionArguments: {
  1162. userId: userID
  1163. },
  1164. label: window.sL.blockUser.replace('{username}','@' + userScreenName)
  1165. });
  1166. }
  1167. // mute profile pref
  1168. menuArray.push({
  1169. type: 'profile-prefs-toggle',
  1170. namespace: 'qvitter',
  1171. topic: 'mute:' + userID,
  1172. label: window.sL.muteUser,
  1173. enabledLabel: window.sL.unmuteUser,
  1174. tickDisabled: true,
  1175. callback: 'hideOrShowNoticesAfterMuteOrUnmute'
  1176. });
  1177. // moderator actions
  1178. menuArray = appendModeratorUserActionsToMenuArray(menuArray,userID,userScreenName,sandboxed,silenced);
  1179. }
  1180. var menu = $(getMenu(menuArray)).appendTo(this);
  1181. alignMenuToParent(menu,$(this));
  1182. }
  1183. });
  1184. // hide the stream menu when clicking outside it
  1185. $('body').on('click',function(e){
  1186. if(!$(e.target).is('.user-menu-cog') && $('.user-menu-cog').hasClass('dropped') && !$(e.target).closest('.user-menu-cog').length>0) {
  1187. $('.user-menu-cog').children('.dropdown-menu').remove();
  1188. $('.user-menu-cog').removeClass('dropped');
  1189. }
  1190. });
  1191. /* ·
  1192. ·
  1193. · Show/hide the stream menu dropdown on click
  1194. ·
  1195. · · · · · · · · · · · · · */
  1196. $('body').on('click','#stream-menu-cog',function(e){
  1197. if(!$(e.target).is('#stream-menu-cog') && $(e.target).closest('#stream-menu-cog').length>0) {
  1198. // don't show/hide when clicking inside the menu
  1199. }
  1200. // hide
  1201. else if($(this).hasClass('dropped')) {
  1202. $(this).removeClass('dropped');
  1203. $(this).children('.dropdown-menu').remove();
  1204. }
  1205. // show
  1206. else {
  1207. $(this).addClass('dropped');
  1208. var menu = $(streamObjectGetMenu(window.currentStreamObject)).appendTo(this);
  1209. alignMenuToParent(menu,$(this));
  1210. }
  1211. });
  1212. // hide the stream menu when clicking outside it
  1213. $('body').on('click',function(e){
  1214. if(!$(e.target).is('#stream-menu-cog') && $('#stream-menu-cog').hasClass('dropped') && !$(e.target).closest('#stream-menu-cog').length>0) {
  1215. $('#stream-menu-cog').children('.dropdown-menu').remove();
  1216. $('#stream-menu-cog').removeClass('dropped');
  1217. }
  1218. });
  1219. /* ·
  1220. ·
  1221. · Open a queet menu when clicking ellipsis button
  1222. ·
  1223. · · · · · · · · · · · · · */
  1224. $('body').on('click','.sm-ellipsis',function(e){
  1225. if(!$(e.target).is('.sm-ellipsis') && $(e.target).closest('.sm-ellipsis').length>0) {
  1226. // don't show/hide when clicking inside the menu
  1227. }
  1228. // hide
  1229. else if($(this).hasClass('dropped')) {
  1230. $(this).removeClass('dropped');
  1231. $(this).children('.dropdown-menu').remove();
  1232. }
  1233. // show
  1234. else {
  1235. $(this).addClass('dropped');
  1236. var closestStreamItem = $(this).closest('.queet').parent('.stream-item');
  1237. var streamItemUsername = closestStreamItem.attr('data-user-screen-name');
  1238. var streamItemUserID = closestStreamItem.attr('data-user-id');
  1239. var streamItemID = closestStreamItem.attr('data-quitter-id');
  1240. var streamItemUserSandboxed = closestStreamItem.hasClass('sandboxed');
  1241. var streamItemUserSilenced = closestStreamItem.hasClass('silenced');
  1242. // menu
  1243. var menuArray = [];
  1244. // delete my notice, or others notices for mods with rights
  1245. if(streamItemUserID == window.loggedIn.id || window.loggedIn.rights.delete_others_notice === true) {
  1246. menuArray.push({
  1247. type: 'function',
  1248. functionName: 'deleteQueet',
  1249. functionArguments: {
  1250. streamItemID: streamItemID
  1251. },
  1252. label: window.sL.deleteVerb
  1253. });
  1254. }
  1255. // block/unblock if it's not me
  1256. if(streamItemUserID != window.loggedIn.id) {
  1257. if(userIsBlocked(streamItemUserID)) {
  1258. menuArray.push({
  1259. type: 'function',
  1260. functionName: 'unblockUser',
  1261. functionArguments: {
  1262. userId: streamItemUserID
  1263. },
  1264. label: window.sL.unblockUser.replace('{username}',streamItemUsername)
  1265. });
  1266. }
  1267. else {
  1268. menuArray.push({
  1269. type: 'function',
  1270. functionName: 'blockUser',
  1271. functionArguments: {
  1272. userId: streamItemUserID
  1273. },
  1274. label: window.sL.blockUser.replace('{username}',streamItemUsername)
  1275. });
  1276. }
  1277. // mute profile pref
  1278. menuArray.push({
  1279. type: 'profile-prefs-toggle',
  1280. namespace: 'qvitter',
  1281. topic: 'mute:' + streamItemUserID,
  1282. label: window.sL.muteUser,
  1283. enabledLabel: window.sL.unmuteUser,
  1284. tickDisabled: true,
  1285. callback: 'hideOrShowNoticesAfterMuteOrUnmute'
  1286. });
  1287. }
  1288. // moderator actions
  1289. menuArray = appendModeratorUserActionsToMenuArray(menuArray,streamItemUserID,streamItemUsername,streamItemUserSandboxed,streamItemUserSilenced);
  1290. // add menu to DOM and align it
  1291. var menu = $(getMenu(menuArray)).appendTo(this);
  1292. alignMenuToParent(menu,$(this));
  1293. }
  1294. });
  1295. // remove the ellipsis menu when clicking outside it
  1296. $('body').on('click',function(e){
  1297. if(!$(e.target).is('.sm-ellipsis') && $('.sm-ellipsis.dropped').length>0 && !$(e.target).closest('.sm-ellipsis').length>0) {
  1298. $('.sm-ellipsis').children('.dropdown-menu').remove();
  1299. $('.sm-ellipsis').removeClass('dropped');
  1300. }
  1301. });
  1302. /* ·
  1303. ·
  1304. · When clicking a function row in a stream menu – invoke the function
  1305. ·
  1306. · · · · · · · · · · · · · */
  1307. $('body').on('click','.row-type-function',function(e){
  1308. var thisFunctionRow = $(this);
  1309. // don't invoke the function again if it's not finished last time
  1310. if(thisFunctionRow.hasClass('clicked')) {
  1311. return true;
  1312. }
  1313. thisFunctionRow.addClass('clicked');
  1314. var functionName = $(this).attr('data-function-name');
  1315. if(typeof $(this).attr('data-function-arguments') == 'undefined' || $(this).attr('data-function-arguments') == 'undefined') {
  1316. var functionArguments = false;
  1317. }
  1318. else {
  1319. var functionArguments = JSON.parse($(this).attr('data-function-arguments'));
  1320. }
  1321. window[functionName](functionArguments, function(success){
  1322. if(success) {
  1323. thisFunctionRow.removeClass('clicked');
  1324. }
  1325. });
  1326. });
  1327. /* ·
  1328. ·
  1329. · When toggeling a a profile pref in a dropdown menu
  1330. ·
  1331. · · · · · · · · · · · · · */
  1332. $('body').on('click','.row-type-profile-prefs-toggle',function(e){
  1333. var thisToggle = $(this);
  1334. // wait for last toggle to finish before toggeling again
  1335. if(thisToggle.hasClass('clicked')) {
  1336. return true;
  1337. }
  1338. if(thisToggle.attr('data-profile-pref-state') == 'disabled') {
  1339. var prefDataToSet = '1';
  1340. }
  1341. else if(thisToggle.attr('data-profile-pref-state') == 'enabled') {
  1342. var prefDataToSet = '0';
  1343. }
  1344. else { // invalid
  1345. return true;
  1346. }
  1347. thisToggle.addClass('clicked');
  1348. var prefNamespace = thisToggle.attr('data-profile-prefs-namespace');
  1349. var prefTopic = thisToggle.attr('data-profile-prefs-topic');
  1350. var prefLabel = thisToggle.attr('data-profile-prefs-label');
  1351. var prefEnabledLabel = thisToggle.attr('data-profile-prefs-enabled-label');
  1352. // only prefs in the 'qvitter' namespace allowed
  1353. if(prefNamespace != 'qvitter') {
  1354. return true;
  1355. }
  1356. // save pref to server
  1357. postSetProfilePref(prefNamespace,prefTopic,prefDataToSet,function(data){
  1358. if(data === false) { // error
  1359. showErrorMessage(window.sL.ERRORfailedSavingYourSetting + ' (' + prefTopic + ')');
  1360. }
  1361. else { // success
  1362. thisToggle.removeClass('clicked');
  1363. if(thisToggle.attr('data-profile-pref-state') == 'disabled') {
  1364. thisToggle.removeClass('disabled');
  1365. thisToggle.addClass('enabled');
  1366. thisToggle.attr('data-profile-pref-state','enabled');
  1367. if(prefEnabledLabel != 'undefined') {
  1368. thisToggle.html(prefEnabledLabel);
  1369. }
  1370. window.qvitterProfilePrefs[prefTopic] = '1';
  1371. }
  1372. else if(thisToggle.attr('data-profile-pref-state') == 'enabled') {
  1373. thisToggle.removeClass('enabled');
  1374. thisToggle.addClass('disabled');
  1375. thisToggle.attr('data-profile-pref-state','disabled');
  1376. if(prefEnabledLabel != 'undefined') {
  1377. thisToggle.html(prefLabel);
  1378. }
  1379. window.qvitterProfilePrefs[prefTopic] = '0';
  1380. }
  1381. // run callback
  1382. if(typeof thisToggle.attr('data-profile-pref-callback') != 'undefined'
  1383. && thisToggle.attr('data-profile-pref-state') != 'undefined'
  1384. && typeof window[thisToggle.attr('data-profile-pref-callback')] == 'function') {
  1385. window[thisToggle.attr('data-profile-pref-callback')]();
  1386. }
  1387. }
  1388. });
  1389. });
  1390. /* ·
  1391. ·
  1392. · Mark all notifications as seen
  1393. ·
  1394. · · · · · · · · · · · · · */
  1395. function markAllNotificationsAsSeen(arg,callback) {
  1396. display_spinner();
  1397. getFromAPI('qvitter/mark_all_notifications_as_seen.json',function(data){
  1398. if(data === false) {
  1399. showErrorMessage(window.sL.ERRORfailedMarkingAllNotificationsAsRead);
  1400. callback(true);
  1401. }
  1402. else {
  1403. helloAPI(function(){
  1404. $('.stream-item').removeClass('not-seen');
  1405. $('#new-queets-bar').trigger('click'); // show any hidden notifications (this will also remove the dropdown menu)
  1406. remove_spinner();
  1407. callback(true);
  1408. });
  1409. }
  1410. });
  1411. }
  1412. /* ·
  1413. ·
  1414. · Show or hide embedded content in timeline?
  1415. ·
  1416. · · · · · · · · · · · · · */
  1417. function showOrHideEmbeddedContentInTimelineFromProfilePref() {
  1418. if(typeof window.qvitterProfilePrefs['hide_embedded_in_timeline:' + window.currentStreamObject.path] != 'undefined') {
  1419. var showHide = window.qvitterProfilePrefs['hide_embedded_in_timeline:' + window.currentStreamObject.path];
  1420. if(parseInt(showHide,10) == 1) {
  1421. $('#feed-body').addClass('embedded-content-hidden-by-user');
  1422. return;
  1423. }
  1424. }
  1425. $('#feed-body').removeClass('embedded-content-hidden-by-user');
  1426. }
  1427. /* ·
  1428. ·
  1429. · Show or hide quotes in timeline?
  1430. ·
  1431. · · · · · · · · · · · · · */
  1432. function showOrHideQuotesInTimelineFromProfilePref() {
  1433. if(typeof window.qvitterProfilePrefs['hide_quotes_in_timeline:' + window.currentStreamObject.path] != 'undefined') {
  1434. var showHide = window.qvitterProfilePrefs['hide_quotes_in_timeline:' + window.currentStreamObject.path];
  1435. if(parseInt(showHide,10) == 1) {
  1436. $('#feed-body').addClass('quotes-hidden-by-user');
  1437. return;
  1438. }
  1439. }
  1440. $('#feed-body').removeClass('quotes-hidden-by-user');
  1441. }
  1442. /* ·
  1443. ·
  1444. · Show or hide notices from muted users in notifications?
  1445. ·
  1446. · · · · · · · · · · · · · */
  1447. function showOrHideNoticesFromMutedUsersInNotifications() {
  1448. if(typeof window.qvitterProfilePrefs['hide_notifications_from_muted_users'] != 'undefined') {
  1449. var showHide = window.qvitterProfilePrefs['hide_notifications_from_muted_users'];
  1450. if(parseInt(showHide,10) == 1) {
  1451. $('#feed-body').addClass('hide-notifications-from-muted-users');
  1452. return;
  1453. }
  1454. }
  1455. $('#feed-body').removeClass('hide-notifications-from-muted-users')
  1456. }
  1457. /* ·
  1458. ·
  1459. · When clicking an external follow button
  1460. ·
  1461. · · · · · · · · · · · · · */
  1462. $('body').on('click','.external-follow-button',function(event){
  1463. popUpAction('popup-external-follow', window.sL.userExternalFollow + ' ' + $('.profile-card-inner .screen-name').html(),'<form method="post" action="' + window.siteInstanceURL + 'main/ostatus"><input type="hidden" id="nickname" name="nickname" value="' + $('.profile-card-inner .screen-name').html().substring(1) + '"><input type="text" id="profile" name="profile" placeholder="' + window.sL.userExternalFollowHelp + '" /></form>','<div class="right"><button class="close">' + window.sL.cancelVerb + '</button><button class="primary">' + window.sL.userExternalFollow + '</button></div>');
  1464. $('#popup-external-follow form input#profile').focus();
  1465. $('#popup-external-follow button.primary').click(function(){
  1466. $('#popup-external-follow form').submit();
  1467. });
  1468. });
  1469. /* ·
  1470. ·
  1471. · When clicking an external join button
  1472. ·
  1473. · · · · · · · · · · · · · */
  1474. $('body').on('click','.external-member-button',function(event){
  1475. popUpAction('popup-external-join', window.sL.joinExternalGroup + ' ' + $('.profile-card-inner .screen-name').html(),'<form method="post" action="' + window.siteInstanceURL + 'main/ostatus"><input type="hidden" id="group" name="group" value="' + $('.profile-card-inner .screen-name').html().substring(1) + '"><input type="text" id="profile" name="profile" placeholder="' + window.sL.userExternalFollowHelp + '" /></form>','<div class="right"><button class="close">' + window.sL.cancelVerb + '</button><button class="primary">' + window.sL.userExternalFollow + '</button></div>');
  1476. $('#popup-external-join form input#profile').focus();
  1477. $('#popup-external-join button.primary').click(function(){
  1478. $('#popup-external-join form').submit();
  1479. });
  1480. });
  1481. /* ·
  1482. ·
  1483. · When clicking a follow/block button
  1484. ·
  1485. · · · · · · · · · · · · · */
  1486. $('body').on('click','.qvitter-follow-button',function(event){
  1487. if($(this).hasClass('disabled')) {
  1488. return true;
  1489. }
  1490. $(this).addClass('disabled');
  1491. var user_id = $(this).attr('data-follow-user-id');
  1492. // if we have a local id, it's straightforward, but we could be handling an unfollow
  1493. if(typeof user_id != 'undefined') {
  1494. // unblock?
  1495. if($(this).hasClass('blocking')) {
  1496. unblockUser({userId: user_id, blockButton_jQueryElement: $(this)});
  1497. return true;
  1498. }
  1499. // follow or unfollow?
  1500. if($(this).hasClass('following')) {
  1501. var followOrUnfollow = 'unfollow';
  1502. }
  1503. else {
  1504. var followOrUnfollow = 'follow';
  1505. }
  1506. // post to api
  1507. display_spinner();
  1508. APIFollowOrUnfollowUser(followOrUnfollow,user_id, this, function(data,this_element) {
  1509. remove_spinner();
  1510. $(this_element).removeClass('disabled');
  1511. if(data) {
  1512. if(data.following) {
  1513. $(this_element).addClass('following');
  1514. $('#user-following strong').html(parseInt($('#user-following strong').html(),10)+1);
  1515. appendUserToMentionsSuggestionsArray(data);
  1516. }
  1517. else {
  1518. $(this_element).removeClass('following');
  1519. $('#user-following strong').html(parseInt($('#user-following strong').html(),10)-1);
  1520. }
  1521. }
  1522. });
  1523. return true;
  1524. }
  1525. // if there's no local user id, we have to take a detour
  1526. $.ajax({ url: window.siteInstanceURL + 'main/ostatussub',
  1527. type: "POST",
  1528. data: {
  1529. token: window.commonSessionToken,
  1530. profile: $(this).attr('data-follow-user'),
  1531. submit: 'Confirm'
  1532. },
  1533. error: function(data){ console.log('error'); console.log(data); },
  1534. success: function(data) {
  1535. // reload page on success
  1536. // since ostatussub doesn't have an api, there's no good way to get the local user id here,
  1537. // and change the button to an unsubscribe button.
  1538. window.location.replace(window.siteInstanceURL + window.loggedIn.screen_name + '/subscriptions');
  1539. }
  1540. });
  1541. });
  1542. /* ·
  1543. ·
  1544. · When clicking a join group button
  1545. ·
  1546. · · · · · · · · · · · · · */
  1547. $('body').on('click','.member-button',function(event){
  1548. if(!$(this).hasClass('disabled')) {
  1549. $(this).addClass('disabled');
  1550. // get group id
  1551. var group_id = $(this).attr('data-group-id');
  1552. // join or leave?
  1553. if($(this).hasClass('member')) {
  1554. var joinOrLeave = 'leave';
  1555. }
  1556. else {
  1557. var joinOrLeave = 'join';
  1558. }
  1559. // post to api
  1560. display_spinner();
  1561. APIJoinOrLeaveGroup(joinOrLeave,group_id, this, function(data,this_element) {
  1562. remove_spinner();
  1563. $(this_element).removeClass('disabled');
  1564. if(data) {
  1565. if(data.member) {
  1566. $(this_element).addClass('member');
  1567. $('.profile-card .member-stats strong').html(parseInt($('.profile-card .member-stats strong').html(),10)+1);
  1568. $('#user-groups strong').html(parseInt($('#user-groups strong').html(),10)+1);
  1569. // add group to mention suggestion array
  1570. if(data.homepage_logo === null) {
  1571. data.homepage_logo = window.defaultAvatarStreamSize;
  1572. }
  1573. var groupmembershipObject = { avatar:data.homepage_logo, id:data.id, name:data.fullname, url:data.url, username:data.nickname };
  1574. window.groupMemberships.push(groupmembershipObject);
  1575. }
  1576. else if(data.member === false) {
  1577. $(this_element).removeClass('member');
  1578. $('.profile-card .member-stats strong').html(parseInt($('.profile-card .member-stats strong').html(),10)-1);
  1579. $('#user-groups strong').html(parseInt($('#user-groups strong').html(),10)-1);
  1580. // remove group from mention suggestion array, if it's there
  1581. var groupToRemove = false;
  1582. $.each(window.groupMemberships,function(k,v) {
  1583. if(v.id == data.id) {
  1584. groupToRemove = k;
  1585. }
  1586. });
  1587. if(groupToRemove) {
  1588. console.log('remove at' + groupToRemove);
  1589. window.groupMemberships.splice(groupToRemove,1);
  1590. }
  1591. }
  1592. }
  1593. });
  1594. }
  1595. });
  1596. /* ·
  1597. ·
  1598. · Go to profile page when clicking the small user header in left column
  1599. ·
  1600. · · · · · · · · · · · · · */
  1601. $('#user-header').on('click',function(e){
  1602. // not if we're clicking the mini-logged-in-user-cog-wheel
  1603. if($(e.target).is('#mini-logged-in-user-cog-wheel')) {
  1604. return;
  1605. }
  1606. setNewCurrentStream(pathToStreamRouter(window.loggedIn.screen_name),true,false);
  1607. });
  1608. /* ·
  1609. ·
  1610. · Searching
  1611. ·
  1612. · · · · · · · · · · · · · */
  1613. $('#search-query').on('keyup',function(e) { if(e.keyCode==13) { showSearchStream(); }}); // on enter in input field
  1614. $('button.icon.nav-search').on('click',function(e) { showSearchStream();}); // on click on magnifying glass
  1615. function showSearchStream() {
  1616. var path = 'search/notice?q=' + encodeURIComponent(replaceHtmlSpecialChars($('#search-query').val()));
  1617. setNewCurrentStream(pathToStreamRouter(path),true,false);
  1618. }
  1619. /* ·
  1620. · <o
  1621. · Hijack all links and look for local users, tags, searches and groups. (//
  1622. ·
  1623. · If found, select that stream and prevent links default behaviour
  1624. ·
  1625. · · · · · · · · · · · · · */
  1626. $('body').on('click','a', function(e) {
  1627. // don't hijack if metakeys are pressed!
  1628. if(e.ctrlKey || e.altKey || e.shiftKey || e.metaKey) {
  1629. return;
  1630. }
  1631. // don't hijack if this is an anchor link in the faq
  1632. if($(this).closest('.modal-container').attr('id') == 'popup-faq' && $(this).attr('href').indexOf('#') > -1) {
  1633. return;
  1634. }
  1635. // don't hijack and don't follow link if this is the bookmark stream icon, prevent link but don't set a new currentstream
  1636. if($(e.target).is('i.chev-right')) {
  1637. e.preventDefault();
  1638. return;
  1639. }
  1640. // don't hijack links with donthijack attribute
  1641. if(!!$(this).attr('donthijack') || $(this).attr('donthijack') == '') {
  1642. return;
  1643. }
  1644. // all links opens in new tab
  1645. $(this).attr('target','_blank');
  1646. // only proceed if we really have a href attribute
  1647. if(typeof $(this).attr('href') == 'undefined') {
  1648. return;
  1649. }
  1650. // profile picture
  1651. if ($(this).hasClass('profile-picture')) {
  1652. e.preventDefault();
  1653. if($(this).closest('.modal-container').attr('id') != 'edit-profile-popup') { // no popup if we're editing our profile
  1654. popUpAction('popup-profile-picture', $('.profile-card-inner .screen-name').html(),'<img style="width:100%;display:block;" src="' + $(this).attr('href') + '" />',false);
  1655. $('.hover-card,.hover-card-caret').remove();
  1656. }
  1657. }
  1658. // hijack link if we find a matching link that qvitter can handle
  1659. else {
  1660. var hrefAttr = $(this).attr('href');
  1661. // this might be a remote profile that we want to reroute to a local instance/user/id url, let's check our cache
  1662. if(typeof window.convertStatusnetProfileUrlToUserArrayCacheKey[hrefAttr] != 'undefined') {
  1663. if(typeof window.userArrayCache[window.convertStatusnetProfileUrlToUserArrayCacheKey[hrefAttr]] != 'undefined') {
  1664. var cachedUserArray = window.userArrayCache[window.convertStatusnetProfileUrlToUserArrayCacheKey[hrefAttr]];
  1665. if(cachedUserArray.local.is_local === false) {
  1666. hrefAttr = window.siteInstanceURL + 'user/' + cachedUserArray.local.id;
  1667. }
  1668. }
  1669. }
  1670. else if(typeof window.convertUriToUserArrayCacheKey[hrefAttr] != 'undefined') {
  1671. if(typeof window.userArrayCache[window.convertUriToUserArrayCacheKey[hrefAttr]] != 'undefined') {
  1672. var cachedUserArray = window.userArrayCache[window.convertUriToUserArrayCacheKey[hrefAttr]];
  1673. if(cachedUserArray.local.is_local === false) {
  1674. hrefAttr = window.siteInstanceURL + 'user/' + cachedUserArray.local.id;
  1675. }
  1676. }
  1677. }
  1678. var streamObject = URLtoStreamRouter(hrefAttr);
  1679. if(streamObject && streamObject.stream) {
  1680. e.preventDefault();
  1681. // if this is a user/{id} type link but we know the nickname already
  1682. if(streamObject.name == 'profile by id' && streamObject.nickname !== false) {
  1683. setNewCurrentStream(pathToStreamRouter(streamObject.nickname),true,streamObject.id);
  1684. }
  1685. // same with group/{id}/id links
  1686. else if(streamObject.name == 'group notice stream by id') {
  1687. // we might be member of the group and thereby already know its nickname
  1688. if (typeof window.groupMemberships != 'undefined' && typeof window.groupMemberships[streamObject.id] != 'undefined') {
  1689. setNewCurrentStream(pathToStreamRouter('group/' + window.groupMemberships[streamObject.id].username),true,streamObject.id);
  1690. }
  1691. // if the text() of the clicked element looks like a group nickname, use that (but send id to setNewCurrentStream() in case this is bad data)
  1692. else if(/^![a-zA-Z0-9]+$/.test($(e.target).text()) || /^[a-zA-Z0-9]+$/.test($(e.target).text())) {
  1693. var nickname = $(e.target).text();
  1694. if(nickname.indexOf('!') == 0) {
  1695. nickname = nickname.substring(1); // remove any starting !
  1696. }
  1697. setNewCurrentStream(pathToStreamRouter('group/' + nickname),true,streamObject.id);
  1698. }
  1699. // if we can't figure out or guess a nickname, query the server for it
  1700. else {
  1701. getNicknameByGroupIdFromAPI(streamObject.id,function(nickname) {
  1702. if(nickname) {
  1703. setNewCurrentStream(pathToStreamRouter('group/' + nickname),true,false);
  1704. }
  1705. else {
  1706. alert('group not found');
  1707. }
  1708. });
  1709. }
  1710. }
  1711. // all other links that qvitter can handle
  1712. else {
  1713. setNewCurrentStream(streamObject,true,false);
  1714. }
  1715. }
  1716. }
  1717. });
  1718. /* ·
  1719. ·
  1720. · When user clicks the bookmark icon to bookmark
  1721. ·
  1722. · · · · · · · · · · · · · */
  1723. $('body').on('click','#history-container .chev-right',function(event){
  1724. var thisStreamLink = $(this).parent('.stream-selection');
  1725. thisStreamLink.slideUp(300,function(){
  1726. $('#bookmark-container').append(thisStreamLink.outerHTML());
  1727. $('#bookmark-container').children().last().children('.chev-right').attr('data-tooltip',window.sL.tooltipRemoveBookmark);
  1728. $('#bookmark-container').children().last().fadeIn(300,function(){
  1729. thisStreamLink.remove();
  1730. updateHistoryLocalStorage();
  1731. saveAllBookmarks();
  1732. });
  1733. });
  1734. });
  1735. /* ·
  1736. ·
  1737. · When user clicks the x to remove a bookmark
  1738. ·
  1739. · · · · · · · · · · · · · */
  1740. $('body').on('click','#bookmark-container .chev-right',function(event){
  1741. $(this).parent('.stream-selection').remove();
  1742. saveAllBookmarks();
  1743. });
  1744. /* ·
  1745. ·
  1746. · When sorting the bookmark menu
  1747. ·
  1748. · · · · · · · · · · · · · */
  1749. $('#bookmark-container').on("sortupdate", function() {
  1750. saveAllBookmarks();
  1751. });
  1752. /* ·
  1753. ·
  1754. · When clearing the browsing history
  1755. ·
  1756. · · · · · · · · · · · · · */
  1757. $('#clear-history').on('click', function() {
  1758. $('#history-container').empty();
  1759. updateHistoryLocalStorage();
  1760. });
  1761. /* ·
  1762. ·
  1763. · Load more from the current stream when scroll is 1000px from bottom
  1764. ·
  1765. · The search API is crap and don't do max_id and last_id, so we have to do pages there...
  1766. ·
  1767. · · · · · · · · · · · · · */
  1768. $(window).scroll(function() {
  1769. if($(window).scrollTop() + $(window).height() > $(document).height() - 1000) {
  1770. // not if we're already loading or if no stream is set yet
  1771. if(!$('body').hasClass('loading-older') && typeof window.currentStreamObject != "undefined" && $('#feed-body').attr('data-end-reached') != 'true') {
  1772. $('body').addClass('loading-older');
  1773. // remove loading class in 10 seconds, i.e. try again if failed to load within 10 s
  1774. if(window.currentStreamObject.name != 'search') {
  1775. setTimeout(function(){$('body').removeClass('loading-older');},10000);
  1776. }
  1777. var lastStreamItemId = $('#feed-body').children('.stream-item').last().attr('id');
  1778. // if this is a stream that uses 'page' for paging, i.e. search or users lists,
  1779. // we need page and rpp vars (page number is stored in an attribute in feed-body)
  1780. if(window.currentStreamObject.maxIdOrPage == 'page') {
  1781. if(typeof $('#feed-body').attr('data-search-page-number') != 'undefined') {
  1782. var searchPage = parseInt($('#feed-body').attr('data-search-page-number'),10);
  1783. }
  1784. else {
  1785. var searchPage=2;
  1786. }
  1787. var nextPage = searchPage+1;
  1788. var getVars = qOrAmp(window.currentStreamObject.stream) + 'rpp=20&page=' + searchPage; // search uses 'rpp' var and others 'count' for paging, though we can add rrp to others aswell without any problem
  1789. }
  1790. // normal streams
  1791. else {
  1792. var getVars = qOrAmp(window.currentStreamObject.stream) + 'max_id=' + ($('#feed-body').children('.stream-item').last().attr('data-quitter-id-in-stream'));
  1793. }
  1794. display_spinner('#footer-spinner-container');
  1795. getFromAPI(window.currentStreamObject.stream + getVars,function(data){
  1796. // if data returned an empty array, we have probably reached the bottom
  1797. if(data.length == 0) {
  1798. $('#feed-body').attr('data-end-reached',true);
  1799. }
  1800. else if(data) {
  1801. addToFeed(data, lastStreamItemId,'visible');
  1802. $('body').removeClass('loading-older');
  1803. // if this is search our group users lists, we remember page number (if we got any users)
  1804. if(window.currentStreamObject.maxIdOrPage == 'page') {
  1805. $('#feed-body').attr('data-search-page-number',nextPage);
  1806. }
  1807. }
  1808. remove_spinner();
  1809. });
  1810. }
  1811. }
  1812. });
  1813. /* ·
  1814. ·
  1815. · Updates all queets' times/dates
  1816. ·
  1817. · · · · · · · · · · · · · */
  1818. var updateTimesInterval=self.setInterval(function(){
  1819. updateAllQueetsTimes();
  1820. },10000);
  1821. /* ·
  1822. ·
  1823. · Check for new queets
  1824. ·
  1825. · · · · · · · · · · · · · */
  1826. var checkForNewQueetsInterval=window.setInterval(function(){checkForNewQueets()},window.timeBetweenPolling);
  1827. function checkForNewQueets() {
  1828. // no new requests if requests are very slow, e.g. searches
  1829. if(!$('body').hasClass('loading-newer')) {
  1830. $('body').addClass('loading-newer');
  1831. // only if logged in and only for notice or notification streams
  1832. if(window.loggedIn && (window.currentStreamObject.type == 'notices' || window.currentStreamObject.type == 'notifications')) {
  1833. var lastId = $('#feed-body').children('.stream-item').not('.temp-post').not('.posted-from-form').attr('data-quitter-id-in-stream');
  1834. var addThisStream = window.currentStreamObject.stream;
  1835. getFromAPI(addThisStream + qOrAmp(window.currentStreamObject.stream) + 'since_id=' + lastId,function(data){
  1836. $('body').removeClass('loading-newer');
  1837. if(data) {
  1838. if(addThisStream == window.currentStreamObject.stream) {
  1839. addToFeed(data, false, 'hidden');
  1840. // say hello to the api if this is notifications stream, to
  1841. // get correct unread notifcation count
  1842. if(window.currentStreamObject.name == 'notifications') {
  1843. helloAPI();
  1844. }
  1845. // if we have hidden items, show new-queets-bar
  1846. maybeShowTheNewQueetsBar()
  1847. }
  1848. }
  1849. });
  1850. }
  1851. }
  1852. }
  1853. /* ·
  1854. ·
  1855. · Show hidden queets when user clicks on new-queets-bar
  1856. ·
  1857. · · · · · · · · · · · · · */
  1858. $('body').on('click','#new-queets-bar',function(){
  1859. if(window.currentStreamObject.name == 'notifications') {
  1860. document.title = window.siteTitle;
  1861. }
  1862. var hiddenStreamItems = $('.stream-item.hidden');
  1863. hiddenStreamItems.css('opacity','0')
  1864. hiddenStreamItems.animate({opacity:'1'}, 200);
  1865. hiddenStreamItems.addClass('visible');
  1866. hiddenStreamItems.removeClass('hidden');
  1867. $('#new-queets-bar').parent().addClass('hidden');
  1868. // say hello to the api if this is notifications stream, to
  1869. // get correct unread notifcation count
  1870. if(window.currentStreamObject.name == 'notifications') {
  1871. helloAPI();
  1872. }
  1873. });
  1874. /* ·
  1875. ·
  1876. · Expand and de-expand queets when clicking anywhere but on a few element types
  1877. ·
  1878. · · · · · · · · · · · · · */
  1879. $('body').on('click','.queet',function (event) {
  1880. if(typeof $(this).attr('href') == 'undefined'
  1881. && $(event.target).closest('a').length == 0
  1882. && !$(event.target).is('\
  1883. a,\
  1884. video,\
  1885. .cm-mention,\
  1886. .cm-tag,\
  1887. .cm-group,\
  1888. .cm-url,\
  1889. pre,\
  1890. img,\
  1891. .name,\
  1892. .queet-box,\
  1893. .syntax-two,\
  1894. button,\
  1895. .show-full-conversation,\
  1896. span.mention,\
  1897. .action-reply-container a span,\
  1898. .action-reply-container a b,\
  1899. .action-rt-container a span,\
  1900. .action-rt-container a b,\
  1901. .action-del-container a span,\
  1902. .action-del-container a b,\
  1903. .action-fav-container a span,\
  1904. .action-fav-container a b,\
  1905. .action-ellipsis-container a span,\
  1906. .action-ellipsis-container a b,\
  1907. span.group,\
  1908. .longdate,\
  1909. .screen-name,\
  1910. .discard-error-message')
  1911. && !$(this).parent('.stream-item').hasClass('user')) { // not if user stream
  1912. expand_queet($(this).parent());
  1913. }
  1914. });
  1915. /* ·
  1916. ·
  1917. · Image popups
  1918. ·
  1919. · · · · · · · · · · · · · */
  1920. $('body').on('click','.stream-item .queet img.attachment-thumb',function (event) {
  1921. event.preventDefault();
  1922. // don't do anything if we are in the middle of collapsing
  1923. if($(this).closest('.stream-item').hasClass('collapsing')) {
  1924. // no action
  1925. }
  1926. // if the stream item isn't expanded, expand that
  1927. else if(!$(this).closest('.stream-item').hasClass('expanded')) {
  1928. expand_queet($(this).closest('.stream-item'));
  1929. }
  1930. // otherwise we open the popup
  1931. else {
  1932. var thisAttachmentThumbSrc = $(this).attr('src');
  1933. var parentStreamItem = $(this).closest('.stream-item');
  1934. var $parentStreamItemClone = $('<div/>').append(parentStreamItem.outerHTML());
  1935. var $queetThumbsClone = $('<div/>').append($parentStreamItemClone.children('.stream-item').children('.queet').find('.queet-thumbs').outerHTML());
  1936. // cleaned version of the stream item to show in the footer
  1937. cleanStreamItemsFromClassesAndConversationElements($parentStreamItemClone);
  1938. $parentStreamItemClone.find('.context,.stream-item-footer').remove();
  1939. var parentStreamItemHTMLWithoutFooter = $parentStreamItemClone.outerHTML();
  1940. $thumbToDisplay = $queetThumbsClone.find('img.attachment-thumb[src="' + thisAttachmentThumbSrc + '"]');
  1941. $thumbToDisplay.parent().addClass('display-this-thumb');
  1942. // "play" all animated gifs and add youtube iframes to all youtube and vimeo videos
  1943. $.each($queetThumbsClone.find('img.attachment-thumb'),function(){
  1944. if($(this).attr('data-mime-type') == 'image/gif'
  1945. && $(this).parent().hasClass('play-button')) {
  1946. $(this).attr('src',$(this).attr('data-full-image-url'));
  1947. $(this).parent('.thumb-container').css('background-image','url(\'' + $(this).attr('data-full-image-url') + '\')');
  1948. }
  1949. else if($(this).parent().hasClass('host-youtube-com')){
  1950. $(this).parent().prepend('<iframe width="510" height="315" src="' + youTubeEmbedLinkFromURL($(this).attr('data-full-image-url'), $(this).parent().hasClass('display-this-thumb')) + '" frameborder="0" allowscriptaccess="always" allowfullscreen></iframe>');
  1951. }
  1952. else if($(this).parent().hasClass('host-vimeo-com')){
  1953. $(this).parent().prepend('<iframe src="' + vimeoEmbedLinkFromURL($(this).attr('data-full-image-url'), $(this).parent().hasClass('display-this-thumb')) + '" width="510" height="315" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>');
  1954. }
  1955. });
  1956. // navigation buttons
  1957. var imgNum = parentStreamItem.children('.queet').find('.attachment-thumb').length;
  1958. if(imgNum > 1) {
  1959. $queetThumbsClone.find('.queet-thumbs').before('<div class="prev-thumb"></div>');
  1960. $queetThumbsClone.find('.queet-thumbs').after('<div class="next-thumb"></div>');
  1961. }
  1962. if(parentStreamItem.hasClass('expanded')) {
  1963. var calculatedDimensions = calculatePopUpAndImageDimensions($thumbToDisplay);
  1964. var $thisImgInQueetThumbsClone = $queetThumbsClone.find('img[src="' + $thumbToDisplay.attr('src') + '"]');
  1965. // set dimensions
  1966. $thisImgInQueetThumbsClone.width(calculatedDimensions.displayImgWidth);
  1967. $thisImgInQueetThumbsClone.parent('.thumb-container').width(calculatedDimensions.displayImgWidth);
  1968. $thisImgInQueetThumbsClone.parent('.thumb-container').children('iframe').attr('width',calculatedDimensions.displayImgWidth);
  1969. $thisImgInQueetThumbsClone.parent('.thumb-container').children('iframe').attr('height',calculatedDimensions.displayImgHeight);
  1970. // open popup
  1971. popUpAction('queet-thumb-popup', '', '' + $queetThumbsClone.outerHTML() + '', parentStreamItemHTMLWithoutFooter, calculatedDimensions.popUpWidth);
  1972. disableOrEnableNavigationButtonsInImagePopup($('#queet-thumb-popup'));
  1973. // for some reason vimeo autoplays when we have a #t=x start time, so stop any vimeo videos running in the background (delay so it has time to load)
  1974. setTimeout(function(){
  1975. $.each($('#queet-thumb-popup').find('.thumb-container.host-vimeo-com:not(.display-this-thumb)').children('iframe'),function(){
  1976. console.log($(this).attr('src'));
  1977. this.contentWindow.postMessage('{"method": "pause"}', '*');
  1978. });
  1979. },1000);
  1980. }
  1981. }
  1982. });
  1983. // popups can be max 900px wide, and should not be higher than the window, so we need to do some calculating
  1984. function calculatePopUpAndImageDimensions(img) {
  1985. var img_src = img.attr('src');
  1986. // trick to get width and height, we can't go with what gnusocial tells us, because
  1987. // gnusocial doesn't (always?) report width and height after proper orientation
  1988. $('body').prepend('<div id="img-dimension-check" style="opacity:0;"><img src="' + img_src + '" /></div>');
  1989. var imgWidth = $('#img-dimension-check img').width();
  1990. var imgHeight = $('#img-dimension-check img').height();
  1991. $('#img-dimension-check').remove();
  1992. // e.g. svg's may not have dimensions set, in that case we just make them small
  1993. if(typeof imgWidth == 'undefined' && typeof imgHeight == 'undefined') {
  1994. return {popUpWidth: 540, displayImgWidth: 540};
  1995. }
  1996. var thisImgWidth = parseInt(imgWidth,10);
  1997. var thisImgHeight = parseInt(imgHeight,10);
  1998. var maxImageHeight = $(window).height() - 120; // 120 being a little more than a short queet in the footer
  1999. if(thisImgWidth < 540) {
  2000. var displayImgWidth = thisImgWidth;
  2001. var popUpWidth = 540;
  2002. if(thisImgHeight > maxImageHeight) {
  2003. displayImgWidth = Math.round(maxImageHeight/thisImgHeight*displayImgWidth);
  2004. }
  2005. }
  2006. else if(thisImgWidth < 900) {
  2007. var displayImgWidth = thisImgWidth;
  2008. if(thisImgHeight > maxImageHeight) {
  2009. displayImgWidth = Math.round(maxImageHeight/thisImgHeight*displayImgWidth);
  2010. if(displayImgWidth < 540) {
  2011. var popUpWidth = 540;
  2012. }
  2013. else {
  2014. var popUpWidth = displayImgWidth;
  2015. }
  2016. }
  2017. else {
  2018. var popUpWidth = displayImgWidth;
  2019. }
  2020. }
  2021. else {
  2022. var displayImgWidth = 900;
  2023. var displayImgHeight = 900/thisImgWidth*thisImgHeight;
  2024. if(displayImgHeight > maxImageHeight) {
  2025. displayImgWidth = Math.round(maxImageHeight*displayImgWidth/displayImgHeight);
  2026. if(displayImgWidth < 540) {
  2027. var popUpWidth = 540;
  2028. }
  2029. else if(displayImgWidth < 900) {
  2030. var popUpWidth = displayImgWidth;
  2031. }
  2032. else {
  2033. var popUpWidth = 900;
  2034. }
  2035. }
  2036. else {
  2037. var popUpWidth = 900;
  2038. }
  2039. }
  2040. // vimeo can't be too small, or the design freaks out
  2041. if(img.parent('.thumb-container').hasClass('host-vimeo-com') && displayImgWidth < 540) {
  2042. displayImgWidth = 540;
  2043. displayImgHeight = 305;
  2044. }
  2045. return {popUpWidth: popUpWidth, displayImgWidth: displayImgWidth, displayImgHeight: displayImgHeight };
  2046. }
  2047. // switch to next image when clicking the image in the popup
  2048. $('body').on('click','#queet-thumb-popup .attachment-thumb',function (event) {
  2049. event.preventDefault();
  2050. var nextImage = $(this).parent().next().children('.attachment-thumb');
  2051. if(nextImage.length>0) {
  2052. // start and stop youtube videos, if any
  2053. $.each($(this).parent('.host-youtube-com').children('iframe'),function(){
  2054. this.contentWindow.postMessage('{"event":"command","func":"' + 'stopVideo' + '","args":""}', '*');
  2055. });
  2056. $.each(nextImage.parent('.host-youtube-com').children('iframe'),function(){
  2057. this.contentWindow.postMessage('{"event":"command","func":"' + 'playVideo' + '","args":""}', '*');
  2058. });
  2059. // start stop vimeo
  2060. $.each($(this).parent('.host-vimeo-com').children('iframe'),function(){
  2061. this.contentWindow.postMessage('{"method": "pause"}', '*');
  2062. });
  2063. $.each(nextImage.parent('.host-vimeo-com').children('iframe'),function(){
  2064. this.contentWindow.postMessage('{"method": "play"}', '*');
  2065. });
  2066. // set dimensions of next image and the popup
  2067. var calculatedDimensions = calculatePopUpAndImageDimensions(nextImage);
  2068. nextImage.width(calculatedDimensions.displayImgWidth);
  2069. nextImage.parent('.thumb-container').width(calculatedDimensions.displayImgWidth);
  2070. nextImage.parent('.thumb-container').children('iframe').attr('width',calculatedDimensions.displayImgWidth);
  2071. nextImage.parent('.thumb-container').children('iframe').attr('height',calculatedDimensions.displayImgHeight);
  2072. $('#queet-thumb-popup .modal-draggable').width(calculatedDimensions.popUpWidth);
  2073. // switch image
  2074. $(this).parent().removeClass('display-this-thumb');
  2075. $(this).parent().next().addClass('display-this-thumb');
  2076. disableOrEnableNavigationButtonsInImagePopup($('#queet-thumb-popup'));
  2077. centerPopUp($('#queet-thumb-popup .modal-draggable'));
  2078. }
  2079. });
  2080. // navigation buttons in image popup
  2081. $('body').on('click','#queet-thumb-popup .next-thumb',function (event) {
  2082. $(this).parent().find('.display-this-thumb').children('img').trigger('click');
  2083. });
  2084. $('body').on('click','#queet-thumb-popup .prev-thumb',function (event) {
  2085. var prevImage = $(this).parent().find('.display-this-thumb').prev().children('img');
  2086. if(prevImage.length>0) {
  2087. // start and stop youtube videos, if any
  2088. $.each($(this).parent().find('.display-this-thumb.host-youtube-com').children('iframe'),function(){
  2089. this.contentWindow.postMessage('{"event":"command","func":"' + 'stopVideo' + '","args":""}', '*');
  2090. });
  2091. $.each(prevImage.parent('.host-youtube-com').children('iframe'),function(){
  2092. this.contentWindow.postMessage('{"event":"command","func":"' + 'playVideo' + '","args":""}', '*');
  2093. });
  2094. // start stop vimeo
  2095. $.each($(this).parent().find('.display-this-thumb.host-vimeo-com').children('iframe'),function(){
  2096. this.contentWindow.postMessage('{"method": "pause"}', '*');
  2097. });
  2098. $.each(prevImage.parent('.host-vimeo-com').children('iframe'),function(){
  2099. this.contentWindow.postMessage('{"method": "play"}', '*');
  2100. });
  2101. // set dimensions of next image and the popup
  2102. var calculatedDimensions = calculatePopUpAndImageDimensions(prevImage);
  2103. prevImage.width(calculatedDimensions.displayImgWidth);
  2104. prevImage.parent('.thumb-container').width(calculatedDimensions.displayImgWidth);
  2105. prevImage.parent('.thumb-container').children('iframe').attr('width',calculatedDimensions.displayImgWidth);
  2106. prevImage.parent('.thumb-container').children('iframe').attr('height',calculatedDimensions.displayImgHeight);
  2107. $('#queet-thumb-popup .modal-draggable').width(calculatedDimensions.popUpWidth);
  2108. // switch image
  2109. $(this).parent().find('.display-this-thumb').removeClass('display-this-thumb');
  2110. prevImage.parent().addClass('display-this-thumb');
  2111. disableOrEnableNavigationButtonsInImagePopup($('#queet-thumb-popup'));
  2112. centerPopUp($('#queet-thumb-popup .modal-draggable'));
  2113. }
  2114. });
  2115. function disableOrEnableNavigationButtonsInImagePopup(popUp) {
  2116. if(popUp.find('.display-this-thumb').prev().length < 1) {
  2117. popUp.find('.prev-thumb').addClass('disabled');
  2118. }
  2119. else {
  2120. popUp.find('.prev-thumb').removeClass('disabled');
  2121. }
  2122. if(popUp.find('.display-this-thumb').next().length < 1) {
  2123. popUp.find('.next-thumb').addClass('disabled');
  2124. }
  2125. else {
  2126. popUp.find('.next-thumb').removeClass('disabled');
  2127. }
  2128. }
  2129. /* ·
  2130. ·
  2131. · Collapse all open conversations and the welcome text on esc or when clicking the margin
  2132. ·
  2133. · · · · · · · · · · · · · */
  2134. $('body').click(function(event){
  2135. if($(event.target).is('body') || $(event.target).is('#page-container')) {
  2136. $('.front-welcome-text.expanded > .show-full-welcome-text').trigger('click');
  2137. $.each($('.stream-item.expanded'),function(){
  2138. expand_queet($(this), false);
  2139. });
  2140. }
  2141. });
  2142. $(document).keyup(function(e){
  2143. if(e.keyCode==27) { // esc
  2144. $('.front-welcome-text.expanded > .show-full-welcome-text').trigger('click');
  2145. $.each($('.stream-item.expanded'),function(){
  2146. expand_queet($(this), false);
  2147. });
  2148. }
  2149. });
  2150. /* ·
  2151. ·
  2152. · When clicking the delete-button
  2153. ·
  2154. · · · · · · · · · · · · · */
  2155. function deleteQueet(arg) {
  2156. var this_stream_item = $('.stream-item[data-quitter-id="' + arg.streamItemID + '"]');
  2157. // don't do anything if this is a queet being posted
  2158. if(this_stream_item.hasClass('temp-post')) {
  2159. return;
  2160. }
  2161. var this_qid = this_stream_item.attr('data-quitter-id');
  2162. var $queetHtml = $(this_stream_item.outerHTML());
  2163. $queetHtml.children('.stream-item.conversation').remove();
  2164. $queetHtml.find('.context,.stream-item-footer,.inline-reply-queetbox,.expanded-content').remove();
  2165. var queetHtmlWithoutFooterAndConversation = $queetHtml.outerHTML();
  2166. popUpAction('popup-delete-' + this_qid, window.sL.deleteConfirmation,queetHtmlWithoutFooterAndConversation,'<div class="right"><button class="close">' + window.sL.cancelVerb + '</button><button class="primary">' + window.sL.deleteVerb + '</button></div>');
  2167. $('#popup-delete-' + this_qid + ' button.primary').on('click',function(){
  2168. display_spinner();
  2169. $('.modal-container').remove();
  2170. // delete
  2171. postActionToAPI('statuses/destroy/' + this_qid + '.json', function(data) {
  2172. if(data) {
  2173. remove_spinner();
  2174. window.knownDeletedNotices[$('.stream-item[data-quitter-id="' + this_qid + '"]').attr('data-uri')] = true;
  2175. slideUpAndRemoveStreamItem($('.stream-item[data-quitter-id="' + this_qid + '"]'));
  2176. }
  2177. else {
  2178. remove_spinner();
  2179. }
  2180. });
  2181. });
  2182. }
  2183. /* ·
  2184. ·
  2185. · When clicking the requeet-button
  2186. ·
  2187. · · · · · · · · · · · · · */
  2188. $('body').on('click','.action-rt-container .icon:not(.is-mine)',function(){
  2189. var this_stream_item = $(this).closest('.stream-item');
  2190. var this_queet = this_stream_item.children('.queet');
  2191. var this_action = $(this).closest('li');
  2192. // requeet
  2193. if(!this_action.children('.with-icn').hasClass('done')) {
  2194. // update the repeat count immediately
  2195. var newRqNum = parseInt(this_queet.find('.action-rq-num').html(),10)+1;
  2196. this_queet.find('.action-rq-num').html(newRqNum);
  2197. this_queet.find('.action-rq-num').attr('data-rq-num',newRqNum);
  2198. this_action.children('.with-icn').addClass('done');
  2199. this_stream_item.addClass('requeeted');
  2200. // requeet animation
  2201. this_action.children('.with-icn').children('.sm-rt').addClass('rotate');
  2202. // remove the fav and rq cache for this queet, to avoid number flickering
  2203. localStorageObjectCache_STORE('favsAndRequeets',this_stream_item.attr('data-quitter-id'), false);
  2204. // post requeet
  2205. postActionToAPI('statuses/retweet/' + this_stream_item.attr('data-quitter-id') + '.json', function(data) {
  2206. if(data) {
  2207. // success
  2208. this_stream_item.attr('data-requeeted-by-me-id',data.id);
  2209. getFavsAndRequeetsForQueet(this_stream_item, this_stream_item.attr('data-quitter-id'));
  2210. // mark all instances of this notice as repeated
  2211. $('.stream-item[data-quitter-id="' + data.retweeted_status.id + '"]').addClass('requeeted');
  2212. $('.stream-item[data-quitter-id="' + data.retweeted_status.id + '"]').attr('data-requeeted-by-me-id',data.id);
  2213. $('.stream-item[data-quitter-id="' + data.retweeted_status.id + '"]').children('.queet').find('.action-rt-container').children('.with-icn').addClass('done');
  2214. }
  2215. else {
  2216. // error
  2217. this_action.children('.with-icn').removeClass('done');
  2218. this_action.find('.with-icn b').html(window.sL.requeetVerb);
  2219. this_stream_item.removeClass('requeeted');
  2220. }
  2221. });
  2222. }
  2223. // un-requeet
  2224. else if(this_action.children('.with-icn').hasClass('done')) {
  2225. display_spinner();
  2226. var myRequeetID = this_stream_item.attr('data-requeeted-by-me-id');
  2227. // display button as unrepeated
  2228. this_action.children('.with-icn').removeClass('done');
  2229. this_action.find('.with-icn b').html(window.sL.requeetVerb);
  2230. this_stream_item.removeClass('requeeted');
  2231. // post unrequeet
  2232. postActionToAPI('statuses/destroy/' + myRequeetID + '.json', function(data) {
  2233. if(data) {
  2234. // remove my repeat-notice from the feed, if it's there
  2235. slideUpAndRemoveStreamItem($('.stream-item[data-quitter-id-in-stream="' + myRequeetID + '"]'));
  2236. // mark all instances of this notice as non-repeated
  2237. $('.stream-item[data-quitter-id="' + this_stream_item.attr('data-quitter-id') + '"]').removeClass('requeeted');
  2238. $('.stream-item[data-quitter-id="' + this_stream_item.attr('data-quitter-id') + '"]').removeAttr('data-requeeted-by-me-id');
  2239. $('.stream-item[data-quitter-id="' + this_stream_item.attr('data-quitter-id') + '"]').children('.queet').find('.action-rt-container').children('.with-icn').removeClass('done');
  2240. getFavsAndRequeetsForQueet(this_stream_item, this_stream_item.attr('data-quitter-id'));
  2241. remove_spinner();
  2242. }
  2243. else {
  2244. remove_spinner();
  2245. this_action.children('.with-icn').addClass('done');
  2246. this_action.find('.with-icn b').html(window.sL.requeetedVerb);
  2247. this_stream_item.addClass('requeeted');
  2248. }
  2249. });
  2250. }
  2251. });
  2252. /* ·
  2253. ·
  2254. · When clicking the fav-button
  2255. ·
  2256. · · · · · · · · · · · · · */
  2257. $('body').on('click','.action-fav-container',function(){
  2258. var this_stream_item = $(this).closest('.stream-item');
  2259. var this_queet = this_stream_item.children('.queet');
  2260. // don't do anything if this is a queet being posted
  2261. if(this_stream_item.hasClass('temp-post')) {
  2262. return;
  2263. }
  2264. var this_action = $(this);
  2265. // fav
  2266. if(!this_action.children('.with-icn').hasClass('done')) {
  2267. // update the fav count immediately
  2268. var newFavNum = parseInt(this_queet.find('.action-fav-num').html(),10)+1;
  2269. this_queet.find('.action-fav-num').html(newFavNum);
  2270. this_queet.find('.action-fav-num').attr('data-fav-num',newFavNum);
  2271. this_action.children('.with-icn').addClass('done');
  2272. this_stream_item.addClass('favorited');
  2273. // fav animation
  2274. this_action.children('.with-icn').children('.sm-fav').addClass('pulse');
  2275. // remove the fav and rq cache for this queet, to avoid number flickering
  2276. localStorageObjectCache_STORE('favsAndRequeets',this_stream_item.attr('data-quitter-id'), false);
  2277. // post fav
  2278. postActionToAPI('favorites/create/' + this_stream_item.attr('data-quitter-id') + '.json', function(data) {
  2279. if(data) {
  2280. // success
  2281. getFavsAndRequeetsForQueet(this_stream_item, this_stream_item.attr('data-quitter-id'));
  2282. // mark all instances of this notice as favorited
  2283. $('.stream-item[data-quitter-id="' + this_stream_item.attr('data-quitter-id') + '"]').addClass('favorited');
  2284. $('.stream-item[data-quitter-id="' + this_stream_item.attr('data-quitter-id') + '"]').children('.queet').find('.action-fav-container').children('.with-icn').addClass('done');
  2285. }
  2286. else {
  2287. // error
  2288. this_action.children('.with-icn').removeClass('done');
  2289. this_action.find('.with-icn b').html(window.sL.favoriteVerb);
  2290. this_stream_item.removeClass('favorited');
  2291. }
  2292. });
  2293. }
  2294. // unfav
  2295. else {
  2296. // update the fav count immediately
  2297. var newFavNum = Math.max(0, parseInt(this_queet.find('.action-fav-num').html(),10)-1);
  2298. this_queet.find('.action-fav-num').html(newFavNum);
  2299. this_queet.find('.action-fav-num').attr('data-fav-num',newFavNum);
  2300. this_action.children('.with-icn').removeClass('done');
  2301. this_action.find('.with-icn b').html(window.sL.favoriteVerb);
  2302. this_stream_item.removeClass('favorited');
  2303. // remove the fav and rq cache for this queet, to avoid number flickering
  2304. localStorageObjectCache_STORE('favsAndRequeets',this_stream_item.attr('data-quitter-id'), false);
  2305. // post unfav
  2306. postActionToAPI('favorites/destroy/' + this_stream_item.attr('data-quitter-id') + '.json', function(data) {
  2307. if(data) {
  2308. // success
  2309. getFavsAndRequeetsForQueet(this_stream_item, this_stream_item.attr('data-quitter-id'));
  2310. // mark all instances of this notice as non-favorited
  2311. $('.stream-item[data-quitter-id="' + this_stream_item.attr('data-quitter-id') + '"]').removeClass('favorited');
  2312. $('.stream-item[data-quitter-id="' + this_stream_item.attr('data-quitter-id') + '"]').children('.queet').find('.action-fav-container').children('.with-icn').removeClass('done');
  2313. }
  2314. else {
  2315. // error
  2316. this_action.children('.with-icn').addClass('done');
  2317. this_action.find('.with-icn b').html(window.sL.favoritedVerb);
  2318. this_stream_item.addClass('favorited');
  2319. }
  2320. });
  2321. }
  2322. });
  2323. /* ·
  2324. ·
  2325. · When clicking the reply-button
  2326. ·
  2327. · · · · · · · · · · · · · */
  2328. $('body').on('click','.action-reply-container',function(){
  2329. var this_stream_item = $(this).closest('.stream-item');
  2330. // don't do anything if this is a queet being posted
  2331. if(this_stream_item.hasClass('temp-post')) {
  2332. return;
  2333. }
  2334. var this_stream_item_id = this_stream_item.attr('data-quitter-id');
  2335. this_stream_item.addClass('replying-to');
  2336. // grabbing the stream-item and view it in the popup, stripped of conversation footer, reply box and other sruff
  2337. var streamItemHTML = $('<div/>').html(this_stream_item.outerHTML());
  2338. cleanStreamItemsFromClassesAndConversationElements(streamItemHTML);
  2339. streamItemHTML.find('.context,.stream-item-footer').remove();
  2340. var streamItemHTMLWithoutFooter = streamItemHTML.outerHTML();
  2341. popUpAction('popup-reply-' + this_stream_item_id, window.sL.replyTo + ' ' + this_stream_item.children('.queet').find('.screen-name').html(),replyFormHtml(this_stream_item,this_stream_item_id),streamItemHTMLWithoutFooter);
  2342. $('#popup-reply-' + this_stream_item_id).find('.modal-body').find('.queet-box').trigger('click'); // expand
  2343. // fix the width of the queet box, otherwise the syntax highlighting break
  2344. var queetBox = $('#popup-reply-' + this_stream_item_id).find('.modal-body').find('.inline-reply-queetbox');
  2345. var queetBoxWidth = queetBox.width()-20;
  2346. queetBox.children('.queet-box-syntax, .syntax-middle, .syntax-two').width(queetBoxWidth);
  2347. maybePrefillQueetBoxWithCachedText(queetBox.children('.queet-box'));
  2348. });
  2349. /* ·
  2350. ·
  2351. · When clicking the compose button
  2352. ·
  2353. · · · · · · · · · · · · · */
  2354. $('body').on('click','#top-compose',function(){
  2355. popUpAction('popup-compose', window.sL.compose,queetBoxPopUpHtml(),false);
  2356. var queetBoxWidth = $('#popup-compose').find('.inline-reply-queetbox').width()-20;
  2357. $('#popup-compose').find('.queet-box-syntax').width(queetBoxWidth);
  2358. $('#popup-compose').find('.syntax-middle').width(queetBoxWidth);
  2359. $('#popup-compose').find('.syntax-two').width(queetBoxWidth);
  2360. $('#popup-compose').find('.queet-box').trigger('click');
  2361. maybePrefillQueetBoxWithCachedText($('#popup-compose').find('.queet-box'));
  2362. });
  2363. /* ·
  2364. ·
  2365. · Close popups
  2366. ·
  2367. · · · · · · · · · · · · · */
  2368. $('body').on('click','.modal-container button.close',function(){
  2369. $('.stream-item').removeClass('replying-to');
  2370. $('.modal-container').remove();
  2371. });
  2372. $('body').on('click','.modal-close',function(){
  2373. $('.stream-item').removeClass('replying-to');
  2374. $('.modal-container').remove();
  2375. });
  2376. $('body').on('click','.modal-container',function(e){
  2377. if($(e.target).is('.modal-container')) {
  2378. $('.stream-item').removeClass('replying-to');
  2379. $('.modal-container').remove();
  2380. abortEditProfile();
  2381. }
  2382. });
  2383. $(document).keyup(function(e){
  2384. if(e.keyCode==27) {
  2385. $('.stream-item').removeClass('replying-to');
  2386. $('.modal-container').remove();
  2387. $('*').blur();
  2388. abortEditProfile();
  2389. }
  2390. });
  2391. /* ·
  2392. ·
  2393. · Post queets, inline and popup replies
  2394. ·
  2395. · · · · · · · · · · · · · */
  2396. $('body').on('click', '.queet-toolbar button',function () {
  2397. if($(this).hasClass('enabled')) {
  2398. // set temp post id
  2399. if($('.temp-post').length == 0) {
  2400. var tempPostId = 'stream-item-temp-post-i';
  2401. }
  2402. else {
  2403. var tempPostId = $('.temp-post').attr('id') + 'i';
  2404. }
  2405. var queetBox = $(this).parent().parent().siblings('.queet-box');
  2406. var queetBoxID = queetBox.attr('id');
  2407. // jquery's .text() function is not consistent in converting <br>:s to \n:s,
  2408. // so we do this detour to make sure line breaks are preserved.
  2409. // In firefox (and maybe some other browsers), queetBox.html() may have <div>s
  2410. // and may or may not have <br> in them.
  2411. // To deal with this, remove any <br>s right before </div> and then add ones.
  2412. queetBox.html(queetBox.html().replace(/(<br>)*<\/div>/g, '<br></div>').replace(/({|})/g, '!$1').replace(/<br>/g, '{{{lb}}}'));
  2413. var queetText = $.trim(queetBox.text().replace(/^\s+|\s+$/g, '').replace(/\n/g, ''));
  2414. queetText = queetText.replace(/{{{lb}}}/g, "\n").replace(/!({|})/g, '$1');
  2415. var queetTempText = replaceHtmlSpecialChars(queetText.replace(/\n/g,'<br>')); // no xss
  2416. queetTempText = queetTempText.replace(/&lt;br&gt;/g,'<br>'); // but preserve line breaks
  2417. var queetHtml = '<div id="' + tempPostId + '" class="stream-item conversation temp-post" style="opacity:1"><div class="queet"><span class="dogear"></span><div class="queet-content"><div class="stream-item-header"><a class="account-group"><img class="avatar" src="' + $('#user-avatar').attr('src') + '" /><strong class="name">' + $('#user-name').html() + '</strong> <span class="screen-name">@' + $('#user-screen-name').html() + '</span></a><small class="created-at"> ' + window.sL.posting + '</small></div><div class="queet-text">' + queetTempText + '</div><div class="stream-item-footer"><ul class="queet-actions"><li class="action-reply-container"><a class="with-icn"><span class="icon sm-reply" title="' + window.sL.replyVerb + '"></span></a></li><li class="action-del-container"><a class="with-icn"><span class="icon sm-trash" title="' + window.sL.deleteVerb + '"></span></a></li></i></li><li class="action-fav-container"><a class="with-icn"><span class="icon sm-fav" title="' + window.sL.favoriteVerb + '"></span></a></li></ul></div></div></div></div>';
  2418. queetHtml = detectRTL(queetHtml);
  2419. // popup reply
  2420. if($('.modal-container').find('.toolbar-reply button').length>0){
  2421. var in_reply_to_status_id = $('.modal-container').attr('id').substring(12);
  2422. }
  2423. // if this is a inline reply
  2424. else if(queetBox.parent().hasClass('inline-reply-queetbox')) {
  2425. var in_reply_to_status_id = queetBox.closest('.stream-item').attr('data-quitter-id');
  2426. }
  2427. // not a reply
  2428. else {
  2429. var in_reply_to_status_id = false;
  2430. }
  2431. // remove any popups
  2432. $('.modal-container').remove();
  2433. // try to find a queet to add the temp queet to:
  2434. var tempQueetInsertedInConversation = false;
  2435. // if the queet is in conversation, add it to parent's conversation
  2436. if($('.stream-item.replying-to').length > 0 && $('.stream-item.replying-to').hasClass('conversation')) {
  2437. var insertedTempQueet = $(queetHtml).appendTo($('.stream-item.replying-to').parent());
  2438. findAndMarkLastVisibleInConversation($('.stream-item.replying-to').parent());
  2439. insertedTempQueet.parent().children('.view-more-container-bottom').remove(); // remove any view-more-container-bottom:s, they only cause trouble at this point
  2440. tempQueetInsertedInConversation = true;
  2441. }
  2442. // if the queet is expanded, add it to its conversation
  2443. else if($('.stream-item.replying-to').length > 0 && $('.stream-item.replying-to').hasClass('expanded')) {
  2444. var insertedTempQueet = $(queetHtml).appendTo($('.stream-item.replying-to'));
  2445. findAndMarkLastVisibleInConversation($('.stream-item.replying-to'));
  2446. insertedTempQueet.parent().children('.view-more-container-bottom').remove(); // remove any view-more-container-bottom:s, they only cause trouble at this point
  2447. tempQueetInsertedInConversation = true;
  2448. }
  2449. // maybe the replying-to class is missing but we still have a suiting place to add it
  2450. else if($('.stream-item.expanded[data-quitter-id="' + in_reply_to_status_id + '"]').length > 0) {
  2451. var insertedTempQueet = $(queetHtml).appendTo($('.stream-item.expanded[data-quitter-id="' + in_reply_to_status_id + '"]'));
  2452. findAndMarkLastVisibleInConversation($('.stream-item.expanded[data-quitter-id="' + in_reply_to_status_id + '"]'));
  2453. insertedTempQueet.parent().children('.view-more-container-bottom').remove(); // remove any view-more-container-bottom:s, they only cause trouble at this point
  2454. tempQueetInsertedInConversation = true;
  2455. }
  2456. // if we can't find a proper place, add it to top and remove conversation class
  2457. // if this is either 1) our home/all feed, 2) our user timeline or 3) whole site or 4) whole network
  2458. else if(window.currentStreamObject.name == 'friends timeline'
  2459. || window.currentStreamObject.name == 'my profile'
  2460. || window.currentStreamObject.name == 'public timeline'
  2461. || window.currentStreamObject.name == 'public and external timeline') {
  2462. var insertedTempQueet = $(queetHtml).prependTo('#feed-body');
  2463. insertedTempQueet.removeClass('conversation');
  2464. }
  2465. // don't add it to the current stream, open a popup instead, without conversation class
  2466. else {
  2467. popUpAction('popup-sending','','',false);
  2468. var insertedTempQueet = $(queetHtml).prependTo($('#popup-sending').find('.modal-body'));
  2469. insertedTempQueet.removeClass('conversation');
  2470. }
  2471. // maybe post queet in groups
  2472. var postToGroups = '';
  2473. var postToGropsArray = new Array();
  2474. $.each(queetBox.siblings('.post-to-group'),function(){
  2475. postToGropsArray.push($(this).data('group-id'));
  2476. });
  2477. if(postToGropsArray.length > 0) {
  2478. postToGroups = postToGropsArray.join(':');
  2479. }
  2480. // remove any post-to-group-divs
  2481. queetBox.siblings('.post-to-group').remove();
  2482. // remove any replying-to classes
  2483. $('.stream-item').removeClass('replying-to');
  2484. // null reply box
  2485. collapseQueetBox(queetBox);
  2486. // check for new queets (one second from) NOW
  2487. setTimeout('checkForNewQueets()', 1000);
  2488. // post queet
  2489. postQueetToAPI(queetText, in_reply_to_status_id, postToGroups, function(data){ if(data) {
  2490. var queetHtml = buildQueetHtml(data, data.id, 'visible posted-from-form', false, tempQueetInsertedInConversation);
  2491. // while we were waiting for our posted queet to arrive here, it may have already
  2492. // arrived in the automatic update of the feed, so if it's already there, we
  2493. // replace it (but not if the temp queet is inserted in a conversation of course, or if
  2494. // the user has had time to expand it)
  2495. var alredyArrived = $('#feed-body > .stream-item[data-quitter-id-in-stream=' + data.id + ']');
  2496. if(alredyArrived.length > 0 && tempQueetInsertedInConversation === false) {
  2497. if(!alredyArrived.hasClass('expanded')) {
  2498. alredyArrived.replaceWith(queetHtml);
  2499. }
  2500. }
  2501. else {
  2502. var newInsertedQueet = $(queetHtml).insertBefore(insertedTempQueet);
  2503. findAndMarkLastVisibleInConversation(insertedTempQueet.parent());
  2504. // make ranting easier, move the reply-form to this newly created notice
  2505. // if we have not started writing in it, or if it's missing
  2506. // only if this is an expanded conversation
  2507. // and only if we're ranting, i.e. no replies the queetbox
  2508. var parentQueetBox = insertedTempQueet.parent().find('.inline-reply-queetbox');
  2509. if(parentQueetBox.length == 0
  2510. || parentQueetBox.children('.syntax-middle').css('display') == 'none') {
  2511. if(insertedTempQueet.parent().hasClass('expanded') || insertedTempQueet.parent().hasClass('conversation')) {
  2512. if(parentQueetBox.children('.queet-box').attr('data-replies-text') == '') {
  2513. insertedTempQueet.parent().find('.inline-reply-queetbox').remove();
  2514. newInsertedQueet.children('.queet').append(replyFormHtml(newInsertedQueet,newInsertedQueet.attr('data-quitter-id')));
  2515. }
  2516. }
  2517. }
  2518. }
  2519. // remove temp queet
  2520. insertedTempQueet.remove();
  2521. // clear queetbox input cache
  2522. localStorageObjectCache_STORE('queetBoxInput',queetBox.attr('id'),false);
  2523. // queet count
  2524. $('#user-queets strong').html(parseInt($('#user-queets strong').html(),10)+1);
  2525. // fadeout any posting-popups
  2526. setTimeout(function(){
  2527. $('#popup-sending').fadeOut(1000, function(){
  2528. $('#popup-sending').remove();
  2529. });
  2530. },100);
  2531. }});
  2532. }
  2533. });
  2534. /* ·
  2535. ·
  2536. · Count chars in queet box on keyup, also check for any attachments to show/hide
  2537. ·
  2538. · · · · · · · · · · · · · */
  2539. $('body').on('keyup input paste','.queet-box-syntax',function () {
  2540. countCharsInQueetBox($(this),$(this).siblings('.queet-toolbar').find('.queet-counter'),$(this).siblings('.queet-toolbar').find('.queet-button button'));
  2541. var attachments = $(this).siblings('.upload-image-container');
  2542. $.each(attachments,function(k,attachment){
  2543. var attachmentShorturl = $(attachment).children('img').attr('data-shorturl');
  2544. if($(attachment).siblings('.queet-box-syntax').text().indexOf(attachmentShorturl) > -1) {
  2545. $(attachment).show();
  2546. }
  2547. else {
  2548. $(attachment).hide();
  2549. }
  2550. });
  2551. });
  2552. /* ·
  2553. ·
  2554. · Middle button expands queet box
  2555. ·
  2556. · · · · · · · · · · · · · */
  2557. $('body').on('mousedown','.queet-box-syntax',function (e) {
  2558. if( e.which == 2 ) {
  2559. e.preventDefault();
  2560. $(this).trigger('click');
  2561. }
  2562. });
  2563. /* ·
  2564. ·
  2565. · Shorten URL's
  2566. ·
  2567. · · · · · · · · · · · · · */
  2568. $('body').on('click','button.shorten',function () {
  2569. shortenUrlsInBox($(this));
  2570. });
  2571. /* ·
  2572. ·
  2573. · Reload current stream
  2574. ·
  2575. · · · · · · · · · · · · · */
  2576. $('body').on('click','.reload-stream',function () {
  2577. reloadCurrentStream();
  2578. });
  2579. // can be used a callback too, e.g. from profile pref toggles
  2580. function reloadCurrentStream() {
  2581. setNewCurrentStream(URLtoStreamRouter(window.location.href),false,false,false);
  2582. }
  2583. /* ·
  2584. ·
  2585. · Reload current stream and clear cache
  2586. ·
  2587. · · · · · · · · · · · · · */
  2588. function reloadCurrentStreamAndClearCache() {
  2589. $('#feed-body').empty();
  2590. rememberStreamStateInLocalStorage();
  2591. // reload
  2592. reloadCurrentStream();
  2593. }
  2594. /* ·
  2595. ·
  2596. · Expand/collapse queet box on click and blur
  2597. ·
  2598. · · · · · · · · · · · · · */
  2599. $('body').on('click contextmenu','.queet-box-syntax',function () {
  2600. if($(this).html() == decodeURIComponent($(this).attr('data-start-text'))) {
  2601. $(this).attr('contenteditable','true');
  2602. $(this).focus();
  2603. $(this).siblings('.syntax-middle').html('&nbsp;');
  2604. $(this).siblings('.syntax-two').html('&nbsp;');
  2605. $(this).siblings('.queet-toolbar').css('display','block');
  2606. $(this).siblings('.syntax-middle').css('display','block');
  2607. $(this).siblings('.mentions-suggestions').css('display','block');
  2608. $(this).siblings('.syntax-two').css('display','block');
  2609. $(this).siblings('.queet-toolbar').find('.queet-button button').addClass('disabled');
  2610. countCharsInQueetBox($(this),$(this).siblings('.queet-toolbar .queet-counter'),$(this).siblings('.queet-toolbar button'));
  2611. $(this)[0].addEventListener("paste", stripHtmlFromPaste);
  2612. if(typeof $(this).attr('data-replies-text') != 'undefined') {
  2613. $(this).html(decodeURIComponent($(this).attr('data-replies-text')));
  2614. var repliesLen = decodeURIComponent($(this).attr('data-replies-text')).replace('&nbsp;',' ').length;
  2615. setSelectionRange($(this)[0], repliesLen, repliesLen);
  2616. }
  2617. else {
  2618. $(this).html('');
  2619. }
  2620. $(this).trigger('input');
  2621. $(this).closest('.stream-item').addClass('replying-to');
  2622. }
  2623. });
  2624. $('body').on('mousedown','.syntax-two',function () {
  2625. $(this).addClass('clicked');
  2626. });
  2627. $('body').on('blur','.queet-box-syntax',function (e) {
  2628. // empty the mention suggestions on blur, timeout because we want to capture clicks in .mentions-suggestions
  2629. setTimeout(function(){
  2630. $(this).siblings('.mentions-suggestions').empty();
  2631. },10);
  2632. // don't collapse if a toolbar button has been clicked
  2633. var clickedToolbarButtons = $(this).siblings('.queet-toolbar').find('button.clicked');
  2634. if(clickedToolbarButtons.length>0) {
  2635. clickedToolbarButtons.removeClass('clicked');
  2636. return true;
  2637. }
  2638. // don't collapse if an error message discard button has been clicked
  2639. if($(this).siblings('.error-message').children('.discard-error-message').length>0) {
  2640. return true;
  2641. }
  2642. // don't collapse if we're clicking around inside queet-box
  2643. var syntaxTwoBox = $(this).siblings('.syntax-two');
  2644. if(syntaxTwoBox.hasClass('clicked')) {
  2645. syntaxTwoBox.removeClass('clicked');
  2646. return true;
  2647. }
  2648. // don't collapse if we're in a modal
  2649. if($(this).parent().parent().hasClass('modal-body')) {
  2650. return true;
  2651. }
  2652. // collapse if nothing is change
  2653. if($(this).attr('data-replies-text') != 'undefined') {
  2654. var $startText = $('<div/>').append(decodeURIComponent($(this).attr('data-replies-text')));
  2655. if($.trim($startText.text()) == $.trim($(this).text()) || $(this).html().length == 0 || $(this).html() == '<br>' || $(this).html() == '<br />' || $(this).html() == '&nbsp;' || $(this).html() == '&nbsp;<br>') {
  2656. collapseQueetBox($(this));
  2657. }
  2658. }
  2659. // collapse if empty
  2660. else if($(this).html().length == 0 || $(this).html() == '<br>' || $(this).html() == '<br />' || $(this).html() == '&nbsp;' || $(this).html() == '&nbsp;<br>') {
  2661. collapseQueetBox($(this));
  2662. }
  2663. });
  2664. function collapseQueetBox(qB) {
  2665. qB.closest('.stream-item').removeClass('replying-to');
  2666. qB.siblings('.upload-image-container').remove();
  2667. qB.siblings('.syntax-middle').css('display','none');
  2668. qB.siblings('.syntax-two').css('display','none');
  2669. qB.siblings('.mentions-suggestions').css('display','none');
  2670. qB.attr('contenteditable','false');
  2671. qB.html(decodeURIComponent(qB.attr('data-start-text')));
  2672. qB.siblings('.queet-toolbar').find('button').removeClass('enabled');
  2673. qB.siblings('.queet-toolbar').css('display','none');
  2674. qB.removeAttr('style');
  2675. qB[0].removeEventListener("paste", stripHtmlFromPaste);
  2676. }
  2677. /* ·
  2678. ·
  2679. · Syntax highlighting in queetbox
  2680. ·
  2681. · · · · · · · · · · · · · */
  2682. // transfer focus and position/selection to background div
  2683. $('body').on('mouseup', 'div.syntax-two', function(e){
  2684. // don't transfer rightclicks, instead wait for oninput and transfer after
  2685. // this makes spell checker work
  2686. if( e.which == 3 ) {
  2687. $(this)[0].oninput = function() {
  2688. $(this).siblings('div.queet-box-syntax').html($(this).html());
  2689. $(this).trigger('mouseup'); // transfer focus
  2690. }
  2691. }
  2692. else {
  2693. $(this).removeClass('clicked');
  2694. var caretPos = getSelectionInElement($(this)[0]);
  2695. var thisQueetBox = $(this).siblings('div.queet-box-syntax');
  2696. thisQueetBox.focus();
  2697. setSelectionRange(thisQueetBox[0], caretPos[0], caretPos[1]);
  2698. // fixes problem with caret not showing after delete, unfocus and refocus
  2699. if(thisQueetBox.html() == '<br>') {
  2700. thisQueetBox.html(' ');
  2701. }
  2702. }
  2703. });
  2704. // strip html from paste
  2705. function stripHtmlFromPaste(e) {
  2706. e.preventDefault();
  2707. var text = replaceHtmlSpecialChars(e.clipboardData.getData("text/plain"));
  2708. text = text.replace(/\n/g,'<br>').replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;'); // keep line-breaks and tabs
  2709. document.execCommand("insertHTML", false, text);
  2710. }
  2711. // sync divs
  2712. $('body').on('keyup paste input', 'div.queet-box-syntax', function() {
  2713. var currentVal = $(this).html();
  2714. currentVal = currentVal.replace(/<br>$/, '').replace(/&nbsp;$/, '').replace(/ $/, ''); // fix
  2715. $(this).siblings('.syntax-two').html(currentVal);
  2716. // loop through the regexps and highlight
  2717. $.each(window.syntaxHighlightingRegexps,function(k,v){
  2718. var i = 0;
  2719. while(currentVal.match(v) && i < 100) { // 100 matches is enough, we don't want to get caught in an infinite loop here
  2720. var currentMatch = currentVal.match(v);
  2721. // too small match, probably a single ! or something, just replace that with its html code
  2722. if($.trim(currentMatch[0]).length < 2) {
  2723. currentVal = currentVal.replace(currentMatch[0],currentMatch[0].replace('#','&#35;').replace('@','&#64;').replace('.','&#046;').replace('!','&#33;'));
  2724. }
  2725. // long enough match, create a mention span
  2726. else {
  2727. // don't include ending char, if any of these (but tags can contain and end with . and -)
  2728. if(currentMatch[0].slice(-1) == '<'
  2729. || currentMatch[0].slice(-1) == '&'
  2730. || currentMatch[0].slice(-1) == '?'
  2731. || currentMatch[0].slice(-1) == '!'
  2732. || currentMatch[0].slice(-1) == ' '
  2733. || (currentMatch[0].slice(-1) == '-' && k != 'tag')
  2734. || currentMatch[0].slice(-1) == ':'
  2735. || (currentMatch[0].slice(-1) == '.' && k != 'tag')
  2736. || currentMatch[0].slice(-1) == ','
  2737. || currentMatch[0].slice(-1) == ')'
  2738. || currentMatch[0].slice(-1) == '\'') {
  2739. currentMatch[0] = currentMatch[0].slice(0,-1);
  2740. }
  2741. // don't include these start strings
  2742. if(currentMatch[0].substring(0,1) == ' '
  2743. || currentMatch[0].substring(0,1) == '(') {
  2744. currentMatch[0] = currentMatch[0].substring(1);
  2745. }
  2746. else if(currentMatch[0].substring(0,6) == '&nbsp;') {
  2747. currentMatch[0] = currentMatch[0].substring(6);
  2748. }
  2749. currentVal = currentVal.replace(currentMatch[0],'<span class="' + k + '">' + currentMatch[0].replace('#','&#35;').replace('@','&#64;').replace('.','&#046;').replace('!','&#33;') + '</span>')
  2750. }
  2751. i++;
  2752. }
  2753. });
  2754. // safari fix
  2755. if(typeof bowser.safari != 'undefined') {
  2756. currentVal = currentVal.replace(/&nbsp;<span/g,' <span');
  2757. }
  2758. $(this).siblings('.syntax-middle').html(currentVal);
  2759. });
  2760. /* ·
  2761. ·
  2762. · Auto suggest mentions in queet-box
  2763. ·
  2764. · · · · · · · · · · · · · */
  2765. // navigate in mentions with mouse
  2766. $('body').on('mouseenter', '.mentions-suggestions > div', function(){
  2767. $('.mentions-suggestions > div').removeClass('selected');
  2768. $(this).addClass('selected');
  2769. }).on('mouseleave', '.mentions-suggestions > div', function(){
  2770. $(this).removeClass('selected');
  2771. });
  2772. $('body').on('click', '.mentions-suggestions > div', function(){
  2773. $(this).parent().siblings('.queet-box-syntax').focus();
  2774. $(this).siblings().removeClass('selected');
  2775. $(this).addClass('selected');
  2776. useSelectedMention($(this).parent().siblings('.queet-box-syntax'));
  2777. });
  2778. // navigate in mentions with keyboard
  2779. $('body').on('keydown', '.queet-box-syntax', function(e) {
  2780. if($(this).siblings('.mentions-suggestions').children('div').length > 0) {
  2781. // enter or tab
  2782. if (!e.ctrlKey && (e.keyCode == '13' || e.keyCode == '9')) {
  2783. e.preventDefault();
  2784. useSelectedMention($(this));
  2785. }
  2786. // downkey
  2787. else if (e.keyCode == '40') {
  2788. e.preventDefault();
  2789. if($(this).siblings('.mentions-suggestions').children('div.selected').length > 0) {
  2790. var selected = $(this).siblings('.mentions-suggestions').children('div.selected');
  2791. selected.removeClass('selected');
  2792. selected.next().addClass('selected');
  2793. }
  2794. else {
  2795. $(this).siblings('.mentions-suggestions').children('div').first().addClass('selected');
  2796. }
  2797. }
  2798. // upkey
  2799. else if (e.keyCode == '38') {
  2800. e.preventDefault();
  2801. if($(this).siblings('.mentions-suggestions').children('div.selected').length > 0) {
  2802. var selected = $(this).siblings('.mentions-suggestions').children('div.selected');
  2803. selected.removeClass('selected');
  2804. selected.prev().addClass('selected');
  2805. }
  2806. else {
  2807. $(this).siblings('.mentions-suggestions').children('div').last().addClass('selected');
  2808. }
  2809. }
  2810. }
  2811. });
  2812. function useSelectedMention(queetBox){
  2813. // use selected
  2814. if(queetBox.siblings('.mentions-suggestions').children('div.selected').length > 0) {
  2815. var selectedSuggestion = queetBox.siblings('.mentions-suggestions').children('div.selected');
  2816. }
  2817. // if none selected, take top suggestion
  2818. else {
  2819. var selectedSuggestion = queetBox.siblings('.mentions-suggestions').children('div').first();
  2820. }
  2821. var username = selectedSuggestion.children('span').html();
  2822. var name = selectedSuggestion.children('strong').html();
  2823. // if this is a group, we remember its id, the user might be member of multiple groups with the same username
  2824. if(selectedSuggestion.hasClass('group-suggestion')) {
  2825. var groupId = selectedSuggestion.data('group-id');
  2826. if(queetBox.siblings('.post-to-group[data-group-id="' + groupId + '"]').length < 1) {
  2827. if(queetBox.siblings('.post-to-group').length>0) {
  2828. var addAfter = queetBox.siblings('.post-to-group').last();
  2829. }
  2830. else {
  2831. var addAfter = queetBox;
  2832. }
  2833. addAfter.after('<div class="post-to-group" data-group-username="' + username + '" data-group-id="' + groupId + '">' + name + '</div>');
  2834. }
  2835. }
  2836. // replace the halfwritten username with the one we want
  2837. deleteBetweenCharacterIndices(queetBox[0], window.lastMention.mentionPos+1, window.lastMention.cursorPos);
  2838. var range = createRangeFromCharacterIndices(queetBox[0], window.lastMention.mentionPos+1, window.lastMention.mentionPos+1);
  2839. range.insertNode(document.createTextNode(username + '\u00a0')); // non-breaking-space, to prevent collapse
  2840. // put caret after
  2841. setSelectionRange(queetBox[0], window.lastMention.mentionPos+username.length+2, window.lastMention.mentionPos+username.length+2);
  2842. queetBox.siblings('.mentions-suggestions').empty();
  2843. queetBox.trigger('input'); // avoid some flickering
  2844. }
  2845. // check for removed group mentions
  2846. $('body').on('keyup', 'div.queet-box-syntax', function(e) {
  2847. var groupMentions = $(this).siblings('.post-to-group');
  2848. var queetBoxGroups = $(this).siblings('.syntax-middle').find('.group');
  2849. var queetBoxGroupsString = '';
  2850. $.each(queetBoxGroups,function(){
  2851. queetBoxGroupsString = queetBoxGroupsString + $(this).html() + ':';
  2852. });
  2853. $.each(groupMentions,function(){
  2854. if(queetBoxGroupsString.indexOf('!' + $(this).data('group-username') + ':') == -1) {
  2855. $(this).remove();
  2856. }
  2857. });
  2858. });
  2859. // check for user mentions
  2860. window.lastMention = new Object();
  2861. $('body').on('keyup', 'div.queet-box-syntax', function(e) {
  2862. var queetBox = $(this);
  2863. var cursorPosArray = getSelectionInElement(queetBox[0]);
  2864. var cursorPos = cursorPosArray[0];
  2865. // add space before linebreaks (to separate mentions in beginning of new lines when .text():ing later)
  2866. if(e.keyCode == '13') {
  2867. e.preventDefault();
  2868. var range = createRangeFromCharacterIndices(queetBox[0], cursorPos, cursorPos);
  2869. range.insertNode(document.createTextNode(" \n"));
  2870. }
  2871. else if(e.keyCode != '40' && e.keyCode != '38' && e.keyCode != '13' && e.keyCode != '9') {
  2872. var contents = queetBox.text().substring(0,cursorPos);
  2873. var mentionPos = contents.lastIndexOf('@');
  2874. var check_contents = contents.substring(mentionPos - 1, cursorPos);
  2875. var regex = /(^|\s|\.|\n)(@)[a-zA-Z0-9]+/;
  2876. var match = check_contents.match(regex);
  2877. if (contents.indexOf('@') >= 0 && match) {
  2878. if(contents.lastIndexOf('@') > 1) {
  2879. match[0] = match[0].substring(1,match[0].length);
  2880. }
  2881. if((contents.lastIndexOf('@')+match[0].length) == cursorPos) {
  2882. queetBox.siblings('.mentions-suggestions').children('.user-suggestion').remove();
  2883. queetBox.siblings('.mentions-suggestions').css('top',(queetBox.height()+20) + 'px');
  2884. var term = match[0].substring(match[0].lastIndexOf('@')+1, match[0].length).toLowerCase();
  2885. window.lastMention.mentionPos = mentionPos;
  2886. window.lastMention.cursorPos = cursorPos;
  2887. // see if anyone we're following matches
  2888. var suggestionsToShow = [];
  2889. var suggestionsUsernameCount = {};
  2890. suggestionsUsernameCount[window.loggedIn.screen_name] = 1; // any suggestions with the same screen name as mine will get their server url added
  2891. $.each(window.following,function(){
  2892. var userregex = new RegExp(term);
  2893. if(this.username.toLowerCase().match(userregex) || this.name.toLowerCase().match(userregex)) {
  2894. suggestionsToShow.push({avatar:this.avatar, name:this.name, username:this.username,url:this.url});
  2895. // count the usernames to see if we need to show the server for any of them
  2896. if(typeof suggestionsUsernameCount[this.username] != 'undefined') {
  2897. suggestionsUsernameCount[this.username] = suggestionsUsernameCount[this.username] + 1;
  2898. }
  2899. else {
  2900. suggestionsUsernameCount[this.username] = 1;
  2901. }
  2902. }
  2903. });
  2904. // show matches
  2905. $.each(suggestionsToShow,function(){
  2906. var serverHtml = '';
  2907. if(suggestionsUsernameCount[this.username]>1 && this.url !== false) {
  2908. serverHtml = '@' + this.url;
  2909. }
  2910. queetBox.siblings('.mentions-suggestions').append('<div class="user-suggestion" title="@' + this.username + serverHtml + '"><img height="24" width="24" src="' + this.avatar + '" /><strong>' + this.name + '</strong> @<span>' + this.username + serverHtml + '</span></div>')
  2911. });
  2912. }
  2913. else {
  2914. queetBox.siblings('.mentions-suggestions').children('.user-suggestion').remove();
  2915. }
  2916. }
  2917. else {
  2918. queetBox.siblings('.mentions-suggestions').children('.user-suggestion').remove();
  2919. }
  2920. }
  2921. });
  2922. // check for group mentions
  2923. $('body').on('keyup', 'div.queet-box-syntax', function(e) {
  2924. var queetBox = $(this);
  2925. var cursorPosArray = getSelectionInElement(queetBox[0]);
  2926. var cursorPos = cursorPosArray[0];
  2927. // add space before linebreaks (to separate mentions in beginning of new lines when .text():ing later)
  2928. if(e.keyCode == '13') {
  2929. e.preventDefault();
  2930. var range = createRangeFromCharacterIndices(queetBox[0], cursorPos, cursorPos);
  2931. range.insertNode(document.createTextNode(" \n"));
  2932. }
  2933. else if(e.keyCode != '40' && e.keyCode != '38' && e.keyCode != '13' && e.keyCode != '9') {
  2934. var contents = queetBox.text().substring(0,cursorPos);
  2935. var mentionPos = contents.lastIndexOf('!');
  2936. var check_contents = contents.substring(mentionPos - 1, cursorPos);
  2937. var regex = /(^|\s|\.|\n)(!)[a-zA-Z0-9]+/;
  2938. var match = check_contents.match(regex);
  2939. if (contents.indexOf('!') >= 0 && match) {
  2940. if(contents.lastIndexOf('!') > 1) {
  2941. match[0] = match[0].substring(1,match[0].length);
  2942. }
  2943. if((contents.lastIndexOf('!')+match[0].length) == cursorPos) {
  2944. queetBox.siblings('.mentions-suggestions').children('.group-suggestion').remove();
  2945. queetBox.siblings('.mentions-suggestions').css('top',(queetBox.height()+20) + 'px');
  2946. var term = match[0].substring(match[0].lastIndexOf('!')+1, match[0].length).toLowerCase();
  2947. window.lastMention.mentionPos = mentionPos;
  2948. window.lastMention.cursorPos = cursorPos;
  2949. // see if any group we're member of matches
  2950. var suggestionsToShow = [];
  2951. var suggestionsUsernameCount = {};
  2952. $.each(window.groupMemberships,function(){
  2953. var userregex = new RegExp(term);
  2954. if(this.username.toLowerCase().match(userregex) || this.name.toLowerCase().match(userregex)) {
  2955. suggestionsToShow.push({id:this.id, avatar:this.avatar, name:this.name, username:this.username,url:this.url});
  2956. // count the usernames to see if we need to show the server for any of them
  2957. if(typeof suggestionsUsernameCount[this.username] != 'undefined') {
  2958. suggestionsUsernameCount[this.username] = suggestionsUsernameCount[this.username] + 1;
  2959. }
  2960. else {
  2961. suggestionsUsernameCount[this.username] = 1;
  2962. }
  2963. }
  2964. });
  2965. // show matches
  2966. $.each(suggestionsToShow,function(){
  2967. var serverHtml = '';
  2968. if(suggestionsUsernameCount[this.username]>1 && this.url !== false) {
  2969. serverHtml = this.url + '/group/';
  2970. }
  2971. queetBox.siblings('.mentions-suggestions').append('<div class="group-suggestion" title="' + serverHtml + this.username + '" data-group-id="' + this.id + '"><img height="24" width="24" src="' + this.avatar + '" /><strong>' + this.name + '</strong> !<span>' + this.username + '</span></div>')
  2972. });
  2973. }
  2974. else {
  2975. queetBox.siblings('.mentions-suggestions').children('.group-suggestion').remove();
  2976. }
  2977. }
  2978. else {
  2979. queetBox.siblings('.mentions-suggestions').children('.group-suggestion').remove();
  2980. }
  2981. }
  2982. });
  2983. /* ·
  2984. ·
  2985. · Any click empties the mentions-suggestions
  2986. ·
  2987. · · · · · · · · · · · · · */
  2988. $(document).click(function() {
  2989. $('.mentions-suggestions').empty();
  2990. });
  2991. /* ·
  2992. ·
  2993. · Store unposted queets in cache, if the user accidentally reloads the page or something
  2994. ·
  2995. · · · · · · · · · · · · · */
  2996. $('body').on('keyup', 'div.queet-box-syntax', function(e) {
  2997. var thisId = $(this).attr('id');
  2998. var thisText = $.trim($(this).text());
  2999. // keep in global var to avoid doing all these operations every keystroke
  3000. if(typeof window.queetBoxCurrentlyActive == 'undefined'
  3001. || window.queetBoxCurrentlyActive.id != thisId) {
  3002. window.queetBoxCurrentlyActive = {
  3003. id: thisId,
  3004. startText: $.trim($('<div/>').append(decodeURIComponent($(this).attr('data-start-text'))).text()),
  3005. repliesText: $.trim($('<div/>').append(decodeURIComponent($(this).attr('data-replies-text'))).text())
  3006. };
  3007. }
  3008. // remove from cache if empty, or same as default text
  3009. if(thisText == ''
  3010. || thisText == window.sL.compose
  3011. || thisText == window.queetBoxCurrentlyActive.startText
  3012. || thisText == window.queetBoxCurrentlyActive.repliesText) {
  3013. localStorageObjectCache_STORE('queetBoxInput',thisId,false);
  3014. }
  3015. else {
  3016. localStorageObjectCache_STORE('queetBoxInput',thisId,$(this).html());
  3017. }
  3018. });
  3019. /* ·
  3020. ·
  3021. · Keyboard shortcuts
  3022. ·
  3023. · · · · · · · · · · · · · */
  3024. // menu
  3025. $('#shortcuts-link').click(function(){
  3026. // not if disabled
  3027. if($(this).hasClass('disabled')) {
  3028. return true;
  3029. }
  3030. popUpAction('popup-shortcuts', window.sL.keyboardShortcuts,'<div id="shortcuts-container"></div>',false);
  3031. getDoc('shortcuts',function(html){
  3032. $('#shortcuts-container').html(html);
  3033. centerPopUp($('#popup-shortcuts').find('.modal-draggable'));
  3034. });
  3035. });
  3036. // send queet on ctrl+enter or ⌘+enter (mac)
  3037. $('body').on('keydown','.queet-box-syntax',function (e) {
  3038. // do nothing if shortcuts are disabled
  3039. if(window.disableKeyboardShortcuts === true) {
  3040. return true;
  3041. }
  3042. if((e.ctrlKey && e.which == 13)
  3043. || (e.metaKey && e.which == 13)) {
  3044. e.preventDefault();
  3045. var pressThisButton = $(this).siblings('.queet-toolbar').children('.queet-button').children('button');
  3046. pressThisButton.click();
  3047. $(this).blur();
  3048. }
  3049. });
  3050. $('body').keyup(function (e) {
  3051. // do nothing if shortcuts are disabled
  3052. if(window.disableKeyboardShortcuts === true) {
  3053. return true;
  3054. }
  3055. // only if queetbox is blurred, and we're not typing in any input, and we're logged in
  3056. if($('.queet-box-syntax[contenteditable="true"]').length == 0
  3057. && $(":focus").length == 0
  3058. && window.loggedIn !== false) {
  3059. // shortcuts documentation on '?'
  3060. if(e.shiftKey && (e.which == 171 || e.which == 191)) {
  3061. $('#shortcuts-link').click();
  3062. }
  3063. // queet box popup on 'n'
  3064. else if(e.which == 78) { // n
  3065. e.preventDefault();
  3066. var pressThis = $('#top-compose')
  3067. pressThis.click();
  3068. }
  3069. // select first queet on first selection, 'j' or 'k'
  3070. else if ((e.which == 74 || e.which == 75) && $('.stream-item.selected-by-keyboard').length == 0) {
  3071. $('#feed-body').children('.stream-item.visible').first().addClass('selected-by-keyboard');
  3072. }
  3073. // only if we have a selected queet
  3074. else if($('.stream-item.selected-by-keyboard').length == 1) {
  3075. var selectedQueet = $('#feed-body').children('.stream-item.selected-by-keyboard');
  3076. // next queet on 'j'
  3077. if(e.which == 74) {
  3078. selectedQueet.removeClass('selected-by-keyboard');
  3079. var next = selectedQueet.nextAll('.visible').not('.always-hidden').first();
  3080. next.addClass('selected-by-keyboard');
  3081. scrollToQueet(next);
  3082. }
  3083. // prev queet on 'k'
  3084. else if(e.which == 75) {
  3085. selectedQueet.removeClass('selected-by-keyboard');
  3086. var prev = selectedQueet.prevAll('.visible').not('.always-hidden').first();
  3087. prev.addClass('selected-by-keyboard');
  3088. scrollToQueet(prev);
  3089. }
  3090. // fav queet on 'f'
  3091. else if(e.which == 70) {
  3092. selectedQueet.children('.queet').find('.icon.sm-fav').click();
  3093. }
  3094. // rq queet on 't'
  3095. else if(e.which == 84) {
  3096. selectedQueet.children('.queet').find('.icon.sm-rt:not(.is-mine)').click();
  3097. }
  3098. // expand/collapse queet on enter
  3099. else if(e.which == 13) {
  3100. selectedQueet.children('.queet').click();
  3101. }
  3102. // reply to queet on 'r'
  3103. else if(e.which == 82) {
  3104. if(selectedQueet.hasClass('expanded')) {
  3105. selectedQueet.find('.queet-box-syntax').click();
  3106. }
  3107. else {
  3108. selectedQueet.children('.queet').find('.icon.sm-reply').click();
  3109. }
  3110. }
  3111. }
  3112. }
  3113. });
  3114. /* ·
  3115. ·
  3116. · When clicking show more links, walk upwards or downwards
  3117. ·
  3118. · · · · · · · · · · · · · */
  3119. $('body').on('click','.view-more-container-bottom', function(){
  3120. var thisParentStreamItem = $(this).parent('.stream-item');
  3121. findReplyToStatusAndShow(thisParentStreamItem, thisParentStreamItem.attr('data-quitter-id'),$(this).attr('data-replies-after'));
  3122. $(this).remove();
  3123. findAndMarkLastVisibleInConversation(thisParentStreamItem);
  3124. });
  3125. $('body').on('click','.view-more-container-top', function(){
  3126. var this_qid = $(this).closest('.stream-item:not(.conversation)').attr('data-quitter-id');
  3127. var queet = $(this).siblings('.queet');
  3128. var thisParentStreamItem = $(this).parent('.stream-item');
  3129. rememberMyScrollPos(queet,'moretop' + this_qid);
  3130. findInReplyToStatusAndShow(thisParentStreamItem, thisParentStreamItem.attr('data-quitter-id'),$(this).attr('data-trace-from'),false,true);
  3131. $(this).remove();
  3132. backToMyScrollPos(queet,'moretop' + this_qid,false);
  3133. // remove the "show full conversation" link if nothing more to show
  3134. if(thisParentStreamItem.find('.hidden-conversation').length == 0) {
  3135. thisParentStreamItem.children('.queet').find('.show-full-conversation').remove();
  3136. }
  3137. findAndMarkLastVisibleInConversation(thisParentStreamItem);
  3138. });
  3139. /* ·
  3140. ·
  3141. · When clicking "show full conversation", show all hidden queets in conversation
  3142. ·
  3143. · · · · · · · · · · · · · */
  3144. $('body').on('click','.show-full-conversation',function(){
  3145. var this_q = $(this).closest('.queet');
  3146. var thisStreamItem = this_q.parent();
  3147. var this_qid = thisStreamItem.attr('data-quitter-id');
  3148. rememberMyScrollPos(this_q,this_qid);
  3149. thisStreamItem.find('.view-more-container-top').remove();
  3150. thisStreamItem.find('.view-more-container-bottom').remove();
  3151. $.each(thisStreamItem.find('.hidden-conversation'),function(key,obj){
  3152. $(obj).removeClass('hidden-conversation');
  3153. $(obj).animate({opacity:'1'},400,function(){
  3154. $(obj).css('background-color','pink').animate({backgroundColor:'#F6F6F6'},1000);
  3155. });
  3156. });
  3157. $(this).remove();
  3158. backToMyScrollPos(this_q,this_qid,false);
  3159. findAndMarkLastVisibleInConversation(thisStreamItem);
  3160. });
  3161. /* ·
  3162. ·
  3163. · Edit profile
  3164. ·
  3165. · · · · · · · · · · · · · */
  3166. $('body').on('click','#page-container > .profile-card .edit-profile-button',function(){
  3167. if(!$(this).hasClass('disabled')) {
  3168. $(this).addClass('disabled');
  3169. $('html').scrollTop(0);
  3170. $('html').addClass('fixed');
  3171. $('body').prepend('<div id="edit-profile-popup" class="modal-container"></div>');
  3172. display_spinner();
  3173. getFromAPI('users/show/' + window.loggedIn.screen_name + '.json', function(data){
  3174. remove_spinner();
  3175. if(data){
  3176. data = cleanUpUserObject(data);
  3177. // use avatar if no cover photo
  3178. var coverPhotoHtml = '';
  3179. if(data.cover_photo !== false) {
  3180. coverPhotoHtml = 'background-image:url(\'' + data.cover_photo + '\')';
  3181. }
  3182. $('.hover-card,.hover-card-caret').remove();
  3183. $('#edit-profile-popup').prepend('\
  3184. <div class="edit-profile-container">\
  3185. <div class="upload-background-image"></div>\
  3186. <input type="file" name="background-image-input" id="background-image-input" />\
  3187. <div class="profile-card">\
  3188. <div class="profile-header-inner" style="' + coverPhotoHtml + '">\
  3189. <input type="file" name="cover-photo-input" id="cover-photo-input" />\
  3190. <div class="close-edit-profile-window"></div>\
  3191. <div class="upload-cover-photo"></div>\
  3192. <input type="file" name="avatar-input" id="avatar-input" />\
  3193. <div class="upload-avatar"></div>\
  3194. <div class="profile-header-inner-overlay"></div>\
  3195. <a class="profile-picture" href="' + data.profile_image_url_original + '"><img src="' + data.profile_image_url_profile_size + '" /></a>\
  3196. <div class="profile-card-inner">\
  3197. <input class="fullname" id="edit-profile-fullname" placeholder="' + window.sL.signUpFullName + '" data-start-value="' + data.name + '" value="' + data.name + '" />\
  3198. <h2 class="username"><span class="screen-name">@' + data.screen_name + '</span><span class="follow-status"></span></h2>\
  3199. <div class="bio-container">\
  3200. <textarea class="bio" id="edit-profile-bio" data-start-value="' + data.description + '" placeholder="' + window.sL.registerBio + '">' + data.description + '</textarea>\
  3201. </div>\
  3202. <p class="location-and-url">\
  3203. <input class="location" id="edit-profile-location" placeholder="' + window.sL.registerLocation + '" data-start-value="' + data.location + '" value="' + data.location + '" />\
  3204. <span class="divider"> · </span>\
  3205. <input class="url" id="edit-profile-url" placeholder="' + window.sL.registerHomepage + '" data-start-value="' + data.url + '" value="' + data.url + '" />\
  3206. </p>\
  3207. </div>\
  3208. </div>\
  3209. <div class="profile-banner-footer">\
  3210. <div class="color-selection">\
  3211. <label for="link-color-selection">' + window.sL.linkColor + '</label>\
  3212. <input id="link-color-selection" type="text" value="#' + window.loggedIn.linkcolor + '" />\
  3213. </div>\
  3214. <div class="color-selection">\
  3215. <label for="link-color-selection">' + window.sL.backgroundColor + '</label>\
  3216. <input id="background-color-selection" type="text" value="#' + window.loggedIn.backgroundcolor + '" />\
  3217. </div>\
  3218. <div class="user-actions">\
  3219. <button type="button" class="abort-edit-profile-button"><span class="button-text edit-profile-text">' + window.sL.cancelVerb + '</span>\
  3220. <button type="button" class="save-profile-button"><span class="button-text edit-profile-text">' + window.sL.saveChanges + '</span>\
  3221. <button type="button" class="crop-and-save-button"><span class="button-text edit-profile-text">' + window.sL.cropAndSave + '</span>\
  3222. </div>\
  3223. <div class="clearfix"></div>\
  3224. </div>\
  3225. </div>\
  3226. </div>');
  3227. $('#edit-profile-popup .profile-card').css('top',$('#page-container .profile-card').offset().top-53 + 'px'); // position exactly over
  3228. // save colors on change
  3229. $('#link-color-selection').minicolors({
  3230. change: function(hex) {
  3231. // pause for 500ms before saving and displaying color changes
  3232. window.changeToLinkColor = hex;
  3233. setTimeout(function(){
  3234. if(hex == window.changeToLinkColor) {
  3235. changeDesign({linkcolor:hex});
  3236. postNewLinkColor(hex.substring(1));
  3237. window.loggedIn.linkcolor = hex.substring(1);
  3238. }
  3239. },500);
  3240. }
  3241. });
  3242. $('#background-color-selection').minicolors({
  3243. change: function(hex) {
  3244. // pause for 500ms before saving and displaying color changes
  3245. window.changeToBackgroundColor = hex;
  3246. setTimeout(function(){
  3247. if(hex == window.changeToBackgroundColor) {
  3248. changeDesign({backgroundcolor:hex});
  3249. postNewBackgroundColor(hex.substring(1));
  3250. window.loggedIn.backgroundcolor = hex.substring(1);
  3251. }
  3252. },500);
  3253. }
  3254. });
  3255. // also on keyup in input (minicolors 'change' event does not do this, apparently)
  3256. $('#link-color-selection').on('keyup',function(){
  3257. keyupSetLinkColor($(this).val());
  3258. });
  3259. $('#background-color-selection').on('keyup',function(){
  3260. keyupSetBGColor($(this).val());
  3261. });
  3262. // check if profile info is change and show/hide buttons
  3263. $('input.fullname,textarea.bio,input.location,input.url').on('keyup paste input',function(){
  3264. showHideSaveProfileButtons();
  3265. });
  3266. }
  3267. else {
  3268. abortEditProfile();
  3269. }
  3270. });
  3271. }
  3272. });
  3273. // function to see if anything in profile is changed and show/hide buttons accordingly
  3274. function showHideSaveProfileButtons() {
  3275. if($('input.fullname').val() != $('input.fullname').attr('data-start-value')
  3276. || $('textarea.bio').val() != $('textarea.bio').attr('data-start-value')
  3277. || $('input.location').val() != $('input.location').attr('data-start-value')
  3278. || $('input.url').val() != $('input.url').attr('data-start-value')) {
  3279. $('.abort-edit-profile-button, .save-profile-button').show();
  3280. }
  3281. else {
  3282. $('.abort-edit-profile-button, .save-profile-button').hide();
  3283. }
  3284. }
  3285. // idle function for linkcolor selection by keyboard input
  3286. var keyupLinkColorTimer;
  3287. function keyupSetLinkColor(hex) {
  3288. clearTimeout(keyupLinkColorTimer);
  3289. keyupLinkColorTimer = setTimeout(function () {
  3290. $('#link-color-selection').minicolors('value',hex);
  3291. changeLinkColor($('#link-color-selection').val());
  3292. postNewLinkColor($('#link-color-selection').val().substring(1));
  3293. }, 500);
  3294. }
  3295. // idle function for bgcolor selection by keyboard input
  3296. var keyupBGColorTimer;
  3297. function keyupSetBGColor(hex) {
  3298. clearTimeout(keyupBGColorTimer);
  3299. keyupBGColorTimer = setTimeout(function () {
  3300. $('#background-color-selection').minicolors('value',hex);
  3301. $('body').css('background-color',$('#background-color-selection').val());
  3302. postNewBackgroundColor($('#background-color-selection').val().substring(1));
  3303. }, 500);
  3304. }
  3305. // cancel
  3306. $('body').on('click','.close-edit-profile-window',function(){
  3307. abortEditProfile();
  3308. });
  3309. $('body').on('click','.abort-edit-profile-button',function(){
  3310. // if this is the avatar or cover photo
  3311. if($('#edit-profile-popup .jwc_frame').length>0) {
  3312. cleanUpAfterCropping();
  3313. }
  3314. // if profile info
  3315. else {
  3316. abortEditProfile();
  3317. }
  3318. });
  3319. function abortEditProfile() {
  3320. $('#edit-profile-popup').remove();
  3321. $('.edit-profile-button').removeClass('disabled');
  3322. $('html').removeClass('fixed');
  3323. }
  3324. // validate
  3325. $('body').on('keyup paste input', '#edit-profile-popup input,#edit-profile-popup textarea', function() {
  3326. if(validateEditProfileForm($('#edit-profile-popup'))){
  3327. $('.save-profile-button').removeAttr('disabled');
  3328. $('.save-profile-button').removeClass('disabled');
  3329. }
  3330. else {
  3331. $('.save-profile-button').attr('disabled','disabled');
  3332. $('.save-profile-button').addClass('disabled');
  3333. }
  3334. });
  3335. // submit cover photo or avatar
  3336. $('body').on('click','.crop-and-save-button',function(){
  3337. if($('.crop-and-save-button').attr('disabled') != 'disabled') {
  3338. $('.crop-and-save-button').attr('disabled','disabled');
  3339. $('.crop-and-save-button').addClass('disabled');
  3340. display_spinner();
  3341. // if this is the cover photo
  3342. if($('#edit-profile-popup .jwc_frame.cover-photo-to-crop').length>0) {
  3343. var coverImgFormData = new FormData();
  3344. coverImgFormData.append('banner', $('#cover-photo-input')[0].files[0]);
  3345. coverImgFormData.append('height', window.jwc.result.cropH);
  3346. coverImgFormData.append('width', window.jwc.result.cropW);
  3347. coverImgFormData.append('offset_left', window.jwc.result.cropX);
  3348. coverImgFormData.append('offset_top', window.jwc.result.cropY);
  3349. $.ajax({
  3350. url: window.apiRoot + 'account/update_profile_banner.json',
  3351. type: "POST",
  3352. data: coverImgFormData,
  3353. processData: false,
  3354. contentType: false,
  3355. cache: false,
  3356. dataType: "json",
  3357. error: function(data){
  3358. console.log('error saving profile banner'); console.log(data);
  3359. $('.crop-and-save-button').removeAttr('disabled');
  3360. $('.crop-and-save-button').removeClass('disabled');
  3361. cleanUpAfterCropping();
  3362. remove_spinner();
  3363. },
  3364. success: function(data) {
  3365. remove_spinner();
  3366. if(typeof data.error == 'undefined') {
  3367. $('.crop-and-save-button').removeAttr('disabled');
  3368. $('.crop-and-save-button').removeClass('disabled');
  3369. cleanUpAfterCropping();
  3370. $('.profile-header-inner').css('background-image','url(' + data.url + ')');
  3371. $('#user-header').css('background-image','url(' + data.url + ')');
  3372. }
  3373. else {
  3374. alert('Try again! ' + data.error);
  3375. $('.crop-and-save-button').removeAttr('disabled');
  3376. $('.crop-and-save-button').removeClass('disabled');
  3377. }
  3378. }
  3379. });
  3380. }
  3381. // if this is the avatar
  3382. else if($('#edit-profile-popup .jwc_frame.avatar-to-crop').length>0) {
  3383. $.ajax({ url: window.apiRoot + 'qvitter/update_avatar.json',
  3384. type: "POST",
  3385. data: {
  3386. cropH: window.jwc.result.cropH,
  3387. cropW: window.jwc.result.cropW,
  3388. cropX: window.jwc.result.cropX,
  3389. cropY: window.jwc.result.cropY,
  3390. img: $('#avatar-to-crop').attr('src')
  3391. },
  3392. dataType:"json",
  3393. error: function(data){ console.log('error'); console.log(data); },
  3394. success: function(data) {
  3395. remove_spinner();
  3396. if(typeof data.error == 'undefined') {
  3397. $('.crop-and-save-button').removeAttr('disabled');
  3398. $('.crop-and-save-button').removeClass('disabled');
  3399. cleanUpAfterCropping();
  3400. $('.profile-picture').attr('href',data.profile_image_url_original);
  3401. $('.profile-picture img, #user-avatar').attr('src',data.profile_image_url_profile_size);
  3402. $('#settingslink .nav-session').css('background-image','url(\'' + data.profile_image_url_profile_size + '\')');
  3403. $('.account-group .name[data-user-id="' + window.loggedIn.id + '"]').siblings('.avatar').attr('src',data.profile_image_url_profile_size);
  3404. }
  3405. else {
  3406. alert('Try again! ' + data.error);
  3407. $('.crop-and-save-button').removeAttr('disabled');
  3408. $('.crop-and-save-button').removeClass('disabled');
  3409. }
  3410. }
  3411. });
  3412. }
  3413. // if this is the background-image
  3414. else if($('#edit-profile-popup .jwc_frame.background-to-crop').length>0) {
  3415. $.ajax({ url: window.apiRoot + 'qvitter/update_background_image.json',
  3416. type: "POST",
  3417. data: {
  3418. cropH: window.jwc.result.cropH,
  3419. cropW: window.jwc.result.cropW,
  3420. cropX: window.jwc.result.cropX,
  3421. cropY: window.jwc.result.cropY,
  3422. img: $('#background-to-crop').attr('src')
  3423. },
  3424. dataType:"json",
  3425. error: function(data){ console.log('error'); console.log(data); },
  3426. success: function(data) {
  3427. remove_spinner();
  3428. if(typeof data.error == 'undefined') {
  3429. $('.crop-and-save-button').removeAttr('disabled');
  3430. $('.crop-and-save-button').removeClass('disabled');
  3431. cleanUpAfterCropping();
  3432. changeDesign({backgroundimage:data.url});
  3433. window.loggedIn.background_image = data.url;
  3434. }
  3435. else {
  3436. alert('Try again! ' + data.error);
  3437. $('.crop-and-save-button').removeAttr('disabled');
  3438. $('.crop-and-save-button').removeClass('disabled');
  3439. }
  3440. }
  3441. });
  3442. }
  3443. }
  3444. });
  3445. // submit new profile info
  3446. $('body').on('click','.save-profile-button',function(){
  3447. if($('.save-profile-button').attr('disabled') != 'disabled') {
  3448. $('.save-profile-button').attr('disabled','disabled');
  3449. $('.save-profile-button').addClass('disabled');
  3450. display_spinner();
  3451. if(validateEditProfileForm($('#edit-profile-popup'))) {
  3452. $.ajax({ url: window.apiRoot + 'account/update_profile.json',
  3453. type: "POST",
  3454. data: {
  3455. name: $('#edit-profile-popup input.fullname').val(),
  3456. url: $('#edit-profile-popup input.url').val(),
  3457. location: $('#edit-profile-popup input.location').val(),
  3458. description: $('#edit-profile-popup textarea.bio').val(),
  3459. },
  3460. dataType:"json",
  3461. error: function(data){ console.log('error'); console.log(data); },
  3462. success: function(data) {
  3463. remove_spinner();
  3464. if(typeof data.error == 'undefined') {
  3465. location.reload(); // reload, hopefully the new profile is saved
  3466. }
  3467. else {
  3468. alert('Try again! ' + data.error);
  3469. $('.save-profile-button').removeAttr('disabled');
  3470. $('.save-profile-button').removeClass('disabled');
  3471. }
  3472. }
  3473. });
  3474. }
  3475. }
  3476. });
  3477. // cover photo, avatar and background image select and crop
  3478. $('body').on('click','.upload-cover-photo, .upload-avatar, .upload-background-image',function(){
  3479. var coverOrAvatar = $(this).attr('class');
  3480. if(coverOrAvatar == 'upload-cover-photo') {
  3481. var inputId = 'cover-photo-input'
  3482. }
  3483. else if(coverOrAvatar == 'upload-avatar') {
  3484. var inputId = 'avatar-input'
  3485. }
  3486. else if(coverOrAvatar == 'upload-background-image') {
  3487. var inputId = 'background-image-input'
  3488. }
  3489. $('#' + inputId).click(function(){ $(this).one('change',function(e){ // trick to make the change event only fire once when selecting a file
  3490. coverPhotoAndAvatarSelectAndCrop(e, coverOrAvatar);
  3491. })});
  3492. // trigger click
  3493. triggerClickOnInputFile($('#' + inputId));
  3494. });
  3495. // load image from file input
  3496. function coverPhotoAndAvatarSelectAndCrop(e, coverOrAvatar) {
  3497. if(coverOrAvatar == 'upload-cover-photo') {
  3498. var targetWidth = 588;
  3499. var targetHeight = 260;
  3500. var cropId = 'cover-photo-to-crop';
  3501. }
  3502. else if(coverOrAvatar == 'upload-avatar') {
  3503. var targetWidth = 220;
  3504. var targetHeight = 220;
  3505. var maxWidth = 1040;
  3506. var minWidth = 1040;
  3507. var cropId = 'avatar-to-crop';
  3508. }
  3509. else if(coverOrAvatar == 'upload-background-image') {
  3510. var targetWidth = $(window).width();
  3511. var targetHeight = $(window).height()-46;
  3512. var maxWidth = 3000;
  3513. var minWidth = 3000;
  3514. var cropId = 'background-to-crop';
  3515. }
  3516. // get orientation
  3517. loadImage.parseMetaData(e.target.files[0], function (data) {
  3518. if (data.exif) {
  3519. var orientation = data.exif.get('Orientation');
  3520. }
  3521. else {
  3522. var orientation = 1;
  3523. }
  3524. display_spinner();
  3525. // clean up
  3526. cleanUpAfterCropping();
  3527. // create image
  3528. loadImage(e.target.files[0],
  3529. function (img) {
  3530. if(typeof img.target == 'undefined') {
  3531. var appendedImg = $('#edit-profile-popup .profile-card').prepend('<img id="' + cropId +'" src="' + img.toDataURL('image/jpeg') + '" />');
  3532. // enable cropping
  3533. $('#' + cropId).jWindowCrop({
  3534. targetWidth:targetWidth,
  3535. targetHeight:targetHeight,
  3536. onChange: function(result) {
  3537. remove_spinner();
  3538. }
  3539. });
  3540. // align centered, fade out background
  3541. $('#' + cropId).parent().addClass(cropId);
  3542. $('#' + cropId).parent().css('position','absolute')
  3543. $('#' + cropId).parent().css('left','50%')
  3544. $('#' + cropId).parent().css('margin-left','-' + (targetWidth/2) + 'px')
  3545. $('#' + cropId).parent().siblings('.profile-header-inner').children('div,input,a').css('display','none');
  3546. // replace the hardcoded "click to drag" string
  3547. $('#' + cropId).siblings('.jwc_controls').children('span').html(window.sL.clickToDrag);
  3548. window.jwc = $('#' + cropId).getjWindowCrop();
  3549. $('.save-profile-button').hide();
  3550. $('.abort-edit-profile-button, .crop-and-save-button').show();
  3551. }
  3552. else {
  3553. remove_spinner();
  3554. $('.queet-box-loading-cover').remove();
  3555. alert('could not read image');
  3556. }
  3557. },
  3558. { maxWidth: maxWidth,
  3559. minWidth: minWidth,
  3560. canvas: true,
  3561. orientation: orientation } // Options
  3562. );
  3563. });
  3564. }
  3565. function cleanUpAfterCropping(){
  3566. $('.jwc_frame').siblings('.profile-header-inner').children('div,input,a').css('display','block');
  3567. if(typeof window.jwc != 'undefined') {
  3568. window.jwc.destroy();
  3569. }
  3570. $('.jwc_frame').remove();
  3571. $('#cover-photo-to-crop').remove();
  3572. $('#avatar-to-crop').remove();
  3573. $('#background-to-crop').remove();
  3574. $('input:file').unbind('click');
  3575. $('.crop-and-save-button').removeClass('disabled');
  3576. $('.crop-and-save-button').removeAttr('disabled');
  3577. $('.crop-and-save-button, .abort-edit-profile-button').hide();
  3578. showHideSaveProfileButtons();
  3579. }
  3580. /* ·
  3581. ·
  3582. · Upload attachment
  3583. ·
  3584. · · · · · · · · · · · · · */
  3585. $('body').on('mousedown','.upload-image',function () {
  3586. // remember caret position
  3587. var caretPos = getSelectionInElement($(this).closest('.queet-toolbar').siblings('.queet-box-syntax')[0]);
  3588. $(this).attr('data-caret-pos',caretPos);
  3589. // prevent queet-box collapse
  3590. $(this).addClass('clicked');
  3591. });
  3592. $('body').on('click','.upload-image',function () {
  3593. var thisUploadButton = $(this);
  3594. $('#upload-image-input').one('click',function(){ // trick to make the change event only fire once when selecting a file
  3595. $(this).unbind('change');
  3596. $(this).one('change',function(e){
  3597. uploadAttachment(e, thisUploadButton);
  3598. })
  3599. });
  3600. // trigger click
  3601. triggerClickOnInputFile($('#upload-image-input'));
  3602. });
  3603. function uploadAttachment(e, thisUploadButton) {
  3604. // loader cover stuff
  3605. thisUploadButton.closest('.queet-toolbar').parent().append('<div class="queet-box-loading-cover"></div>');
  3606. thisUploadButton.closest('.queet-toolbar').siblings('.queet-box-loading-cover').width(thisUploadButton.closest('.queet-toolbar').parent().outerWidth());
  3607. display_spinner(thisUploadButton.closest('.queet-toolbar').siblings('.queet-box-loading-cover')[0]);
  3608. thisUploadButton.closest('.queet-toolbar').siblings('.queet-box-loading-cover').find('.loader').css('top', (thisUploadButton.closest('.queet-toolbar').parent().outerHeight()/2-20) + 'px');
  3609. var uploadButton = thisUploadButton.closest('.queet-toolbar').find('.upload-image');
  3610. var queetBox = thisUploadButton.closest('.queet-toolbar').siblings('.queet-box-syntax');
  3611. var caretPos = uploadButton.attr('data-caret-pos').split(',');
  3612. var imgFormData = new FormData();
  3613. imgFormData.append('media', $('#upload-image-input')[0].files[0]);
  3614. // upload
  3615. $.ajax({ url: window.apiRoot + 'statusnet/media/upload',
  3616. type: "POST",
  3617. data: imgFormData,
  3618. contentType: false,
  3619. processData: false,
  3620. dataType: "xml",
  3621. error: function(data, textStatus, errorThrown){
  3622. showErrorMessage(window.sL.ERRORattachmentUploadFailed, queetBox.siblings('.syntax-two'));
  3623. $('.queet-box-loading-cover').remove();
  3624. queetBox.focus();
  3625. },
  3626. success: function(data) {
  3627. var rsp = $(data).find('rsp');
  3628. if (rsp.attr('stat') == 'ok') {
  3629. // maybe add thumbnail below queet box
  3630. if($(data).find('atom\\:link,link').length>0) {
  3631. var mimeType = $(data).find('atom\\:link,link').attr('type');
  3632. if(mimeType.indexOf('image/') == 0) {
  3633. var imgUrl = $(data).find('atom\\:link,link').attr('href');
  3634. thisUploadButton.closest('.queet-toolbar').before('<span class="upload-image-container"><img class="to-upload" src="' + imgUrl + '" /></span>');
  3635. }
  3636. }
  3637. var mediaurl = rsp.find('mediaurl').text();
  3638. $('img.to-upload').attr('data-shorturl', mediaurl);
  3639. $('img.to-upload').addClass('uploaded');
  3640. $('img.to-upload').removeClass('to-upload');
  3641. // insert shorturl in queet box
  3642. deleteBetweenCharacterIndices(queetBox[0], caretPos[0], caretPos[1]);
  3643. var range = createRangeFromCharacterIndices(queetBox[0], caretPos[0], caretPos[0]);
  3644. if(typeof range == 'undefined') {
  3645. // if queetbox is empty no range is returned, and inserting will fail,
  3646. // so we insert a space and try to get range again...
  3647. queetBox.html('&nbsp;');
  3648. range = createRangeFromCharacterIndices(queetBox[0], caretPos[0], caretPos[0]);
  3649. }
  3650. range.insertNode(document.createTextNode(' ' + mediaurl + ' '));
  3651. // put caret after
  3652. queetBox.focus();
  3653. var putCaretAt = parseInt(caretPos[0],10)+mediaurl.length+2;
  3654. setSelectionRange(queetBox[0], putCaretAt, putCaretAt);
  3655. queetBox.trigger('input'); // avoid some flickering
  3656. setTimeout(function(){ queetBox.trigger('input');},1); // make sure chars are counted and shorten-button activated
  3657. $('.queet-box-loading-cover').remove();
  3658. }
  3659. else {
  3660. alert('Try again! ' + rsp.find('err').attr('msg'));
  3661. $('.save-profile-button').removeAttr('disabled');
  3662. $('.save-profile-button').removeClass('disabled');
  3663. $('img.to-upload').parent().remove();
  3664. $('.queet-box-loading-cover').remove();
  3665. }
  3666. }
  3667. });
  3668. }
  3669. /* ·
  3670. ·
  3671. · Small edit profile button on hover cards goes to edit profile
  3672. ·
  3673. · · · · · · · · · · · · · */
  3674. $('body').on('click','.hover-card .edit-profile-button',function(){
  3675. goToEditProfile();
  3676. });
  3677. /* ·
  3678. ·
  3679. · User menu when clicking the mini cog wheel in the logged in mini card
  3680. ·
  3681. · · · · · · · · · · · · · */
  3682. $('body').on('click','#mini-logged-in-user-cog-wheel:not(.dropped)',function(){
  3683. var menu = $(getMenu(loggedInUsersMenuArray())).appendTo(this);
  3684. alignMenuToParent(menu,$(this));
  3685. $(this).addClass('dropped');
  3686. });
  3687. // hide when clicking it again
  3688. $('body').on('click','#mini-logged-in-user-cog-wheel.dropped',function(e){
  3689. if($(e.target).is('#mini-logged-in-user-cog-wheel')) {
  3690. $('#mini-logged-in-user-cog-wheel').children('.dropdown-menu').remove();
  3691. $('#mini-logged-in-user-cog-wheel').removeClass('dropped');
  3692. }
  3693. });
  3694. // hide the menu when clicking outside it
  3695. $('body').on('click',function(e){
  3696. if($('#mini-logged-in-user-cog-wheel').hasClass('dropped') && !$(e.target).closest('#mini-logged-in-user-cog-wheel').length>0) {
  3697. $('#mini-logged-in-user-cog-wheel').children('.dropdown-menu').remove();
  3698. $('#mini-logged-in-user-cog-wheel').removeClass('dropped');
  3699. }
  3700. });
  3701. /* ·
  3702. ·
  3703. · Goes to edit profile
  3704. ·
  3705. · · · · · · · · · · · · · */
  3706. function goToEditProfile(arg, callback) {
  3707. if(window.currentStreamObject.name == 'my profile') {
  3708. $('#page-container > .profile-card .edit-profile-button').trigger('click');
  3709. if(typeof callback == 'function') {
  3710. callback(true);
  3711. }
  3712. }
  3713. else {
  3714. setNewCurrentStream(pathToStreamRouter(window.loggedIn.screen_name), true, false, function(){
  3715. $('#page-container > .profile-card .edit-profile-button').trigger('click');
  3716. if(typeof callback == 'function') {
  3717. callback(true);
  3718. }
  3719. });
  3720. }
  3721. }