App-Alice-0.19/0000755000175000017500000000000011437215373012146 5ustar leedoleedoApp-Alice-0.19/MANIFEST.SKIP0000644000175000017500000000163011437215253014041 0ustar leedoleedo#!start included /opt/perl5.10/lib/5.10.0/ExtUtils/MANIFEST.SKIP # Avoid version control files. \bRCS\b \bCVS\b \bSCCS\b ,v$ \B\.svn\b \B\.git\b \B\.gitignore\b \B\.gitmodules\b \b_darcs\b # Avoid Makemaker generated and utility files. \bMANIFEST\.bak \bMakefile$ \bblib/ \bMakeMaker-\d \bpm_to_blib\.ts$ \bpm_to_blib$ \bblibdirs\.ts$ # 6.18 through 6.25 generated this # Avoid Module::Build generated and utility files. \bBuild$ \b_build/ # Avoid temp and backup files. ~$ \.old$ \#$ \b\.# \.bak$ # Avoid Devel::Cover files. \bcover_db\b #!end included /opt/perl5.10/lib/5.10.0/ExtUtils/MANIFEST.SKIP # Avoid Module::Build generated and utility files. \bBuild$ \bBuild.bat$ \b_build \bBuild.COM$ \bBUILD.COM$ \bbuild.com$ \.swp$ \.DS_Store \bextlib.* # Avoid archives of this distribution \bshare/sprockets.* \balice.app.* \bAlice.app.* TODO App-Alice README.md \.modulebuildrc bin/client src INSTALL.md App-Alice-0.19/Makefile.PL0000644000175000017500000000325511437015710014116 0ustar leedoleedouse inc::Module::Install; name 'App-Alice'; perl_version '5.008'; author 'Lee Aylward '; all_from 'lib/App/Alice.pm'; requires 'List::MoreUtils' => '0'; requires 'Any::Moose' => '0'; requires 'Try::Tiny' => '0'; requires 'AnyEvent' => '5.2'; requires 'AnyEvent::IRC' => '0.95'; requires 'AnyEvent::HTTP' => '0'; requires 'Twiggy' => '0'; requires 'Plack' => '0'; requires 'Plack::Session' => '0'; requires 'DBD::SQLite' => '0'; requires 'SQL::Abstract' => '0'; requires 'AnyEvent::DBI' => '0'; requires 'IRC::Formatting::HTML' => '0.27'; requires 'Text::MicroTemplate' => '0.09'; requires 'JSON' => '2.12'; requires 'File::ShareDir' => '0.01'; requires 'File::Copy' => '0'; test_requires 'Test::More' => '0.86'; test_requires 'Test::TCP' => '0'; install_share 'share'; install_script 'bin/alice'; postamble q{ BUILD := share/static SOURCE := src JS_SOURCE := $(SOURCE)/js CSS_SOURCE := $(SOURCE)/css SPROCKETS := alice prototype scriptaculous shortcut wysihat sprintf INCLUDES := $(foreach sprocket,$(SPROCKETS),$(JS_SOURCE)/$(sprocket)/src) INCLUDE := $(foreach dir,$(INCLUDES),-I $(dir)) JAVASCRIPT_SOURCES := $(foreach dir,$(INCLUDES),$(wildcard $(dir)/*.js)) ALICE_SCSS := $(CSS_SOURCE)/alice.scss ALICE_JS := $(JS_SOURCE)/alice/src/alice.js SITE_JS := $(BUILD)/alice.js COLORFILES := $(foreach dir,$(CSS_SOURCE)/colors,$(wildcard $(dir)/*.scss)) CSSFILES := $(addprefix $(BUILD)/alice-,$(notdir $(COLORFILES:.scss=.css))) $(BUILD)/alice-%.css: $(CSS_SOURCE)/colors/%.scss $(ALICE_SCSS) cat $< $(ALICE_SCSS) | sass --scss -s $@ assets: stylesheets javascript stylesheets: $(CSSFILES) javascript: $(JAVASCRIPT_SOURCES) sprocketize $(INCLUDE) $(ALICE_JS) > $(SITE_JS) }; WriteAll; App-Alice-0.19/share/0000755000175000017500000000000011437215373013250 5ustar leedoleedoApp-Alice-0.19/share/templates/0000755000175000017500000000000011437215373015246 5ustar leedoleedoApp-Alice-0.19/share/templates/help.html0000644000175000017500000000354311432561537017072 0ustar leedoleedo? my $app = shift; App-Alice-0.19/share/templates/announcement.html0000644000175000017500000000013611366325415020626 0ustar leedoleedo
  • {message} ?>
  • App-Alice-0.19/share/templates/server_listitem.html0000644000175000017500000000022611366325415021354 0ustar leedoleedo? my ($app, $alias) = @_; App-Alice-0.19/share/templates/window.html0000644000175000017500000000165611403451216017442 0ustar leedoleedo? my ($app, $window) = @_;
    " id="id ?>">
    topic->{string} ?>
    App-Alice-0.19/share/templates/event.html0000644000175000017500000000233511431051545017251 0ustar leedoleedo? my ($app, $msg) = @_;
  • {timestamp} ?>
    ? if ($msg->{event} eq "topic") { Topic changed to "{body} ?>" ? if ($msg->{nick}) { by {nick} ?> ? } ? } elsif ($msg->{event} eq "invite") { {nick} ?> has invited you to join {body} ?> ? } elsif ($msg->{event} eq "joined" or $msg->{event} eq "left") { {nick} ?> {event} ?> the chat room. ? if ($msg->{body}) { ({body} ?>) ? } ? } elsif ($msg->{event} eq "nick") { {nick} ?> is now known as {body} ?>. ? } elsif ($msg->{event} eq "disconnect") { You have been disconnected {body}) { ?> ({body} ?>) ? }
  • App-Alice-0.19/share/templates/tab.html0000644000175000017500000000045411436012111016665 0ustar leedoleedo? my ($app, $window) = @_;
  • ">
    title ?>
  • App-Alice-0.19/share/templates/new_server.html0000644000175000017500000000441211366325415020314 0ustar leedoleedo? my ($app, $alias) = @_;
    SSL
    App-Alice-0.19/share/templates/message.html0000644000175000017500000000173311431051545017555 0ustar leedoleedo? my ($app, $msg) = @_; ? my $avatar = ($msg->{avatar} and $app->config->{avatars} eq "show") ? $msg->{avatar} : "";
  • {nick} ?>{self} ? " self" : "" ?>{consecutive} ? " consecutive" : "" ?>{monospaced} ? " monospace" : ""?>" id="{msgid} ?>">
    {html} ?>
    {consecutive} and $msg->{timestamp}) { ?>
    {timestamp} ?>
  • App-Alice-0.19/share/templates/logs.html0000644000175000017500000000346011403451216017072 0ustar leedoleedo? my $app = shift; Logs " />
    App-Alice-0.19/share/templates/window_head.html0000644000175000017500000000100511366325415020420 0ustar leedoleedo? my ($app, $window) = @_;
    type ?>" id="id ?>"> type eq "channel") { ?>
    topic->{string} ?>
      App-Alice-0.19/share/templates/range.html0000644000175000017500000000075311401234663017227 0ustar leedoleedo? my ($app, $results, $position) = @_; App-Alice-0.19/share/templates/index_head.html0000644000175000017500000000426311437213356020230 0ustar leedoleedo? my ($app, @windows) = @_; ? my $titlewin = @windows > 1 ? $windows[1] : $windows[0]; <?= $titlewin->title ?> "> config->style.".css") ?>" /> " /> " /> " />
        render_file('tab.html', $_[0], $window) ?>
      App-Alice-0.19/share/templates/login.html0000644000175000017500000000331411435242024017234 0ustar leedoleedo? my ($app, $error) = @_; Use Alice Login "> " /> " />
      App-Alice-0.19/share/templates/servers.html0000644000175000017500000001175211437015710017624 0ustar leedoleedo? my $app = shift; ? my @connections = $app->ircs;
      • Connections
      • alias; ?>
      • active" onclick="Alice.connections.showConnection('')" id="menu_">
      alias; ?>
      is_connected ? "connected" : "disconnected" ?>
      config->{autoconnect}) {?> checked="checked" name="_autoconnect" />
      is_connected ? "disconnect" : "connect" ?>
      " size="15"/>
      " size="6" style="float:left"/>
      config->{ssl}) { ?> checked="checked" /> SSL
      " size="15" />
      " size="15" />
      " size="15" />
      " size="15" />
      App-Alice-0.19/share/templates/avatargrid.html0000644000175000017500000000045611431051545020256 0ustar leedoleedo? my ($app, $window) = @_;
        ? for my $nick (@{$window->all_nicks}) {
      • irc->nick_avatar($nick); ?>
      • ? }
      App-Alice-0.19/share/templates/index_footer.html0000644000175000017500000000153311437015710020614 0ustar leedoleedo? my ($app, @windows) = @_;
      render_file('help.html', $_[0]) ?> App-Alice-0.19/share/templates/select.html0000644000175000017500000000031111366325415017406 0ustar leedoleedo? my ($app, $window, $selected) = @_; App-Alice-0.19/share/templates/prefs.html0000644000175000017500000000456111437015710017252 0ustar leedoleedo? my $app = shift;
      config->images eq "show") { ?> checked name="images" id="images" />
      config->avatars eq "show") { ?> checked="checked" name="avatars" id="avatars" />
      config->alerts eq "show") { ?> checked="checked" name="alerts" id="alerts" /> Grant permission
      App-Alice-0.19/share/templates/results.html0000644000175000017500000000076111401234663017633 0ustar leedoleedo? my ($app, $results) = @_;
        ? for my $result (@$results) {
      • ? my @date = localtime($result->[0]); ? $date[5] = $date[5] % 100; ? $date[4] = $date[4] + 1;
        [1] ?> [3] ?>
      • ? }
      App-Alice-0.19/share/templates/window_footer.html0000644000175000017500000000106111433255127021014 0ustar leedoleedo? my ($app, $window) = @_;
    App-Alice-0.19/share/log.sql0000644000175000017500000000023111373075677014560 0ustar leedoleedoCREATE TABLE messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, time INT, user VARCHAR(16), nick VARCHAR(16), channel VARCHAR(16), body TEXT );App-Alice-0.19/share/static/0000755000175000017500000000000011437215373014537 5ustar leedoleedoApp-Alice-0.19/share/static/image/0000755000175000017500000000000011437215373015621 5ustar leedoleedoApp-Alice-0.19/share/static/image/config.png0000644000175000017500000000076411435501124017571 0ustar leedoleedoPNG  IHDRa pHYs  IDAT8RJQ>FBiUHӐa0P27" ѨO"XN&Pn(Ls8&ga 2EUt$K·YwɄVfe25_QI_UDʬ$(iJqeS(;ɇ lӥl nb&X9|֚98'Gz`r XUIENDB`App-Alice-0.19/share/static/image/formatting.png0000644000175000017500000000071311412170767020502 0ustar leedoleedoPNG  IHDR pHYs  }IDAT8?/ao]'B(4| NTD+ohTW8۝Y^,乙wfݽ7 R M[ -C/Z":/#omrEz!x uب(Y" 3I hC줱=x2#t&QWhAkdyš@z)`@:ӏU{|GSA.Y.c&֤0 אʪkK(!kH(-Y78̓u1UAM 0ڲ=OsS=xs^`>dX?!/] G) wH6LL15+bI~xR0`'5IENDB`App-Alice-0.19/share/static/image/pause.png0000644000175000017500000000072011366325415017443 0ustar leedoleedoPNG  IHDRabKGD pHYs  tIME  +J`D]IDAT8͒KnA *(؊8@r HHp(;DgH0ia/J\~?_Gڻ=;-"#:kΟ=y/Y C>f_ywnyfWfps_L-{>}`t]7#7Svw)@T0sEDkv(?$д;AT@v7Zq4mC"!yt0FU_,n>&C *fO,e`n; ﻩ_dQxh 6N1-F$j\BB|9Q*Ώ몞l7D"ic#)HsJtV:mJ8IENDB`App-Alice-0.19/share/static/image/shadow-bottom.png0000644000175000017500000000020111366325415021107 0ustar leedoleedoPNG  IHDRtEXtSoftwareAdobe ImageReadyqe<#IDATxbq&yJtHIENDB`App-Alice-0.19/share/static/image/aquaTabClose.png0000644000175000017500000000057611366325415020703 0ustar leedoleedoPNG  IHDR Vu\ pHYs  0IDAT(NAE.cDCbYml聄 #p| b1FqY!+=彀i] 8<7.k4~8o#@iBBֺ (4CF+ce2IpI,,[q,|{W@h-w|>_A0NZ[geLr`Y9*\eYI)3ÐL(FiOɧ4MJo `R21~6msaEV>gZkQEYu@qz2x7*Py8@x5"mIENDB`App-Alice-0.19/share/static/image/roomTab.png0000644000175000017500000000141611366325415017734 0ustar leedoleedoPNG  IHDRagAMAOX2tEXtSoftwareAdobe ImageReadyqe<IDATxڜ]HSag>hNqΰцMD]iAvSE]utAwE0rdԅʙm}sz7*Vx}_yy(?jFFH +$3Vؚ;6F2.qGyYB2 EQ2Xmn8_oq鍕zN$"-#e5vRCN{ -yYP ( 4ˀaBZKC#/F\6DJ`@dazct`Sw]48D<^Uai/i1#@8LPxJ☝5Xs{"A+DjM+ok cccޜz:c9ZEPҼISk=y#5̸+Uh]$9`VRE"9CP B:ZLνDž'" !'LZ_[@ BZMf8mZH9Ȳ\,xNK]# 9V].m!ߨ04McfD &8SPIENDB`App-Alice-0.19/share/static/image/roomTabDisabled.png0000644000175000017500000000124611366325415021365 0ustar leedoleedoPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<HIDATxڜSˋRa?z̘/&pCوV@۠ 7P@nA*7a  ^24~u8(V>^,Zn ^ nϯoooOqK1N|>2L&N|~TTML& ^7OOO+k.JHj@{{{4-R ,,)X&y/,h4$88*NP8ZփC R/zށEרy666El5i#vN:i)JTp1#bDLħ+eəfi$ <@E$}ȶGQ|lU*B 1Hs:pgYF5rx<f3_Ђ(C1^oAZkaj#wL"XtFf"YQ4SQfso^9]]W/Bf;"I|>ߗ9###}(heVBl"#Ya@Ņ VHUĂ H(gAZU\8ܧ}zy&j9R<:OHɽH gyx~t?op.$P&W " R.TSd ly|B" I>ةآ(G$@`UR,@".Y2GvX@`B, 8C L0ҿ_pH˕͗K3w!lBa)f "#HL 8?flŢko">!N_puk[Vh]3 Z zy8@P< %b0>3o~@zq@qanvRB1n#Dž)4\,XP"MyRD!ɕ2 w ONl~Xv@~- g42y@+͗\LD*A aD@ $<B AT:18 \p` Aa!:b""aH4 Q"rBj]H#-r9\@ 2G1Qu@Ơst4]k=Kut}c1fa\E`X&cX5V5cX7va$^lGXLXC%#W 1'"O%zxb:XF&!!%^'_H$ɒN !%2I IkHH-S>iL&m O:ňL $RJ5e?2BQͩ:ZImvP/S4u%͛Cˤ-Кigih/t ݃EЗkw Hb(k{/LӗT02goUX**|:V~TUsU?y TU^V}FUP թU6RwRPQ__c FHTc!2eXBrV,kMb[Lvv/{LSCsfffqƱ9ٜJ! {--?-jf~7zھbrup@,:m:u 6Qu>cy Gm7046l18c̐ckihhI'&g5x>fob4ekVyVV׬I\,mWlPW :˶vm))Sn1 9a%m;t;|rtuvlp4éĩWggs5KvSmnz˕ҵܭm=}M.]=AXq㝧/^v^Y^O&0m[{`:>=e>>z"=#~~~;yN`k5/ >B Yroc3g,Z0&L~oL̶Gli})*2.QStqt,֬Yg񏩌;jrvgjlRlc웸xEt$ =sl3Ttcܢ˞w|/%ҟ3gAMA|Q cHRMz%u0`:o_F+IDATxڤ]LS_G(Pu08Z`݃Yt.Y]b&4f/ {c&{1-fa͈FEQk CůjG{4y=9Ώ/2tgueU+_,!b~St^t$=ur\ʦlxJF'+,tv$KDQf֞h@D*}Z7"XyÀ"ːDiMuOLM~̏T+*CўTB_#|;r\cDN2?>SK!NIعz5r&7cρ/Qs'iWF`[9ԫO{*" պlf9Ekjq%ibk|K-+ٳ]viJb !\뽟s'HgW!"gp;]ry1l-Mgm ز%k:Y9~ hP0 -m'g'%GbXmRۃZ =EN\(Ipa] f cf&Nli>zX9=ȓdw0M]c}' i=;|cDD 4Z>mOO NYm[|h8zd|eŏ$ $T>ҶcSSZ-9Y!Iȶm &b{"'zFcGRqf`Ҳ ޢZ_AQY$U.pX=г4}miMs᜖O n޾eL,9ol=W3.4fN/7O{1IENDB`App-Alice-0.19/share/static/image/overflow-active.png0000644000175000017500000000112511366325415021442 0ustar leedoleedoPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<IDATxڤSMK[A=3Ě֐-vO.PJPbwݸ-R\uVp.W- *Rc{sKxM)p3w(3y 0Y"E6 EEv "ĵDZ_G1QBZ V؊6k$Emⓘp׿C{z d a#=g(k֡p%P \c^^6pWLx>QRNI---Qh7sP07y4 B“>Y`ΰ6QأɎ"+JlhuA\ }vdwVLpz E,? SBn&IENDB`App-Alice-0.19/share/static/image/play.png0000644000175000017500000000122411366325415017273 0ustar leedoleedoPNG  IHDRabKGD pHYs  tIME $!٭^!IDAT8˝Kk<2$M&1ibZI0#JHRlgt'XB\H/r . BMXlJФ3M23.Ħb)wus9@wx`]: z`0,>0LDz/nZϵZMB4>7ԤʥOSL4-kuhpf.^x06 Îx""@0Sx}LQ*̝d t 6x=>褃bxuhEFo͎0,kf,BoZ;5e>Y& x' FeVBl"#Ya@Ņ VHUĂ H(gAZU\8ܧ}zy&j9R<:OHɽH gyx~t?op.$P&W " R.TSd ly|B" I>ةآ(G$@`UR,@".Y2GvX@`B, 8C L0ҿ_pH˕͗K3w!lBa)f "#HL 8?flŢko">!N_puk[Vh]3 Z zy8@P< %b0>3o~@zq@qanvRB1n#Dž)4\,XP"MyRD!ɕ2 w ONl~Xv@~- g42y@+͗\LD*A aD@ $<B AT:18 \p` Aa!:b""aH4 Q"rBj]H#-r9\@ 2G1Qu@Ơst4]k=Kut}c1fa\E`X&cX5V5cX7va$^lGXLXC%#W 1'"O%zxb:XF&!!%^'_H$ɒN !%2I IkHH-S>iL&m O:ňL $RJ5e?2BQͩ:ZImvP/S4u%͛Cˤ-Кigih/t ݃EЗkw Hb(k{/LӗT02goUX**|:V~TUsU?y TU^V}FUP թU6RwRPQ__c FHTc!2eXBrV,kMb[Lvv/{LSCsfffqƱ9ٜJ! {--?-jf~7zھbrup@,:m:u 6Qu>cy Gm7046l18c̐ckihhI'&g5x>fob4ekVyVV׬I\,mWlPW :˶vm))Sn1 9a%m;t;|rtuvlp4éĩWggs5KvSmnz˕ҵܭm=}M.]=AXq㝧/^v^Y^O&0m[{`:>=e>>z"=#~~~;yN`k5/ >B Yroc3g,Z0&L~oL̶Gli})*2.QStqt,֬Yg񏩌;jrvgjlRlc웸xEt$ =sl3Ttcܢ˞w|/%ҟ3gAMA|Q cHRMz%u0`:o_F3IDATxڤ]L_V(v`ߨ@qQ6H.es2f W)fe8le5傺E&:\WZ ksC.\s!f) ""Roqʧjh0dp-i^pfjׯ{Ɨ7U9v)ա}{(Dbf̘3\ڿi7Pl#r<9Q`13c^l-q블&8ẺM|nq,:블,q [ߩ(SU xm",><~(kii}WQ%XPLmCCWN&r˗+=HI)TWo @Nukbnn^Q.j?#a޳7n涝h v3ӏ{T96rfɤg$IֳYy%hҌErE"@XV٬I䣊#pթ-x~d2\ vr#f &tSccfdY 9u^$uvvfkVm'( ?Wa"(R8s,DD}{5\n A5=2,+4]Gbr<{ >t-B_IDA|ťo55ZFo2h2`$u\v;tr5Z9<(,,,t-Es~{5=v<o4m{T_)2{:]wn46~g :ړ|9vf'NFIENDB`App-Alice-0.19/share/static/image/background.png0000644000175000017500000000024511366325415020447 0ustar leedoleedoPNG  IHDR(tEXtSoftwareAdobe ImageReadyqe< PLTE) s2IDATxb````"F0bFDYaD` $L #V *vdIENDB`App-Alice-0.19/share/static/image/roomTabNewHighlightMessage.png0000644000175000017500000000151311366325415023541 0ustar leedoleedoPNG  IHDRagAMAOX2tEXtSoftwareAdobe ImageReadyqe<IDATxڜKaǿawVC)m3 )-1z[VL\WDdP0'iEC:k>8sA$ BB0؍8a*52.DzVb̞\zyG:-Iw q(J2,\Cr2)9t"vih$9l&%rEtLCE'tpX$ G {p4|<*i9HB=Y 04ZvnZA`0{f3P,20G mD\C x"}%X0-,bMfb!: 2@lxp"Ni Z5_[A,FZ_ݼdO |=@,QmT4@'g M`1>6%8џv#### VYn6*2dQ!P~pp{9~~txNg" $ :.<u8 87:03~ .OOYQQd1!1O%UvIU}_.T[u:w;so~dG~o]BPk5g_­|ZУ`0PDDpο9jZr\vs`tH|B/W^!ދ"9@ Vv[A))d9۬tYj %ͤ ,̤x{)@1*Md696V//<m|]nJK Cv;hNr:A;g{wuP,!CҽwKϽ\>J5:7rg']4lfe9}?X]E'z?(H|~*(ߣDG T4YS~xhG3A} dU9?[i!YC4@?m% @m7e#10o#o(aL'lDv( =( UЃCaV߯xbŎuCPf: ~cSAϒTxh_uE&=gi C\Hq9q߯Huc!\qDcoM?<@Xw=?Aooאe9xE RS L]A*"#7FMm|%/<(R3o[y.XIdNE:sQ܀:nKirp֬Yw_uոҊj4]w=C(1(" `&dޮZV)78~VNL&*99#ߌg(ZIRҵCAkLQy߯-m<8Ν;+..~p¸qЪFR Gix|<}'ܦ<4fٕYiã22ɃiMz(O*?}ggկ~/7+q:jL]~u3u7xT/fN8qԩS?OuK1++{_PP$DJKd*JXSSәn:t!R(^8N4$q 1rm;$Ř|]'K %6i#-ΟtFVza)YA/D;dKݶ^VzyM;nX I u(Ҷ f`#XĠÕgߴIV$ 9#tfSfͷCn V&OIZ3*UZwգh!o;ja>,ႡI *00vM6;%%g^"[i$KفirQ_T9$Pw^c%GdedLο9 Ԏi'8}1 p9CUSEuzAKvweO>Q) ʺtii$5ԒH&-\%@K&^\QYEqd[m4QBzf#j4h/ٻb] zzITE^Geoq?)멱 H .6r+*PK&L0eLD64n'{^G4RADg5%ҲӧgΜ TVV6lP9bĈ[{8$atDžpKB8ȕ2^r@b8i 'PXsR΅fsδir-Շ*:vϻ[N4H8Sx7㠚:L$m۴igʮ0BZ9sVXb.Oܑs` uocKV}=zK/Tc6(6 :yБ _~ee`'qTw^ kf!™HFJ;vrvǣa?Av-0qUaB? 77 鱴DV} v]v$^QLL$ 28&dD?dd*t'$pMt⮆A9~e# P-#l>A\yyy4rH@9AGI*Nࠨ spkNKKh'G ; wf`\1W0ݻwW=D`Fȟ:I8#B_ۥ+ H; IE=9(Ƀ]l3ʹv{s\X}讒@$c+x騼1&#hѝo9w۶$W^hʌqj aԐd4Ruẗpq}Ur:IZNGW*c׍U:'s ؕOkW?yC~@)$o hrX[ I?ʶ7g%g+A^|P](NZN$olm1@O{QQuWm}Ng"{c+"`, PIuե΢5qI&B'<]A@3Y,׸F1 R|&|iKS_XfiQD'|0FEHsn;\THv@).RGٗ%[*tHƏ@g#}d4MHKatx:yqP'$#=4 j+tz"-oA0ǒ#!F¯Ma/QkEV')=o$R6lreMlul:PG/5oeљ^1ljnv~8NÒ*Atq}בxZ$.%??0 b?$.b!i;m4"-&򶂴@d 5ez۽)b?zXk 5n1Ƹ5GE>K/XKO!rU ҵrs{Gp_-5g)qD qLVj]4E uH^oN7%78~)iiIumdu盜0Տ,>ѴFC@!aI]0C5aiC5KeCr'[`/&rgR ν>EF([; \0޼pAeJMMM:tX2t3clcwyB ?Hl @\W7ʜ>fI䡖|L6ΘcYP8ԅP(CqžnXv߿wƍ7ϟ[sss ܢQ1D}0xƂ?'hBkJ^;?AWs)K" Jv!:΀n 8-&$Yr&9A?[a ҋnDܭ ynX,Y2QaDщ[V.xqc |y{UD'I|;t{O?Bʿ2T6( ^E-S *}bytM+oHNN@ ֿZ@eّүUe_+J￿~ܸq{`0 `;9(F|*XT¹t%JRbd0"1\-;u VxL`?j;Ra4A%yzwa|e›(gP`+^8X}@_:^#q?s0 !Y6Pij\ d 7Zz 쐝Օ})֖@(~Լ,%w7ٳ32]b+-zOl>Env6U<]RŲS-X [Ulx +H,`5،iϞ]w|d>97ݻEQKoY*ֽ˅ԝ.0x@xWђc{ZtQzF D#I]ʎiȱfqV~*>!mAhDQc֢!8~HI.X|e-$Ƕk֬>'Qn2G jgѻ}֓=reE}5VЃTˋڳ3pa_ty+Ub/9Z0"=gs̜>gƤ_O3QN.pՒ7iҊQ9c72P}^w/Zp\/Ppw)Y`^@7@P61dA{xP9WJO<8v<П'Gv8fxH0ر#ɓ6lz#CMLz駡 |o{ԑ1HyQSxcblI& +7@)#Æ ꫯ.==۷&O*6VZBA8M&'GM0 p5}  oJ7k4$Kxkˌ/}#0DV/ể|]i|U)vt*U) _[WKDh5`x6 Q@uD0n$&"Oc@_:jˆ sxpAKVj*;Vy]~j%~.;jtXK m-6jV6y̓}AGRy/-KJvJ-1cD^:$`1Ǯ,aƧ)J%1dW @a:,F;*dũ@V[w9{{ u>2P``_9^' {VpQ\,S&o዇b 7_E@X38$RM}5qrՔW J% o'O4AS'x\?@.VdTĚ-)WN1MJŭ$8Kc,LNImu}߹`e: o}V8) 7.N̹3gU!LN 1V p؝iS?-b(CGZE?!BͭJFc/cb3 3n@Z1w3ܕpגLiF>8y$}4[[-ۈPt>A-F `0wDLSnvM4:8 ~(A?ĝ 6 k&g:ɥFA <x{_;j %{tn][[!;ME!f"EkÖ]?Tev dc< [BF`"mQ B 4 .6 CȜ rL b"qx ڟ?ٯ {b7;ӽ3(Κ;bpnKęI~yG;cm<0 T@R dpq_X [86]`}ޙe̟;///[nzl&PuPeaY*RP;z^7s`<n*~2_'7L,L'Upқ~[{d(rK T~i[$Y`VGҞ T@es-6U*Ws@zv 8y}}9|:f/ks|o*ɲUiwOuWOI@;' F?DA#8洣.UYQ byy d +A=C3\k5ۿ]$׬?W 7'|4 NVvZ#Ms??vF"MG<aMϲ ` s9aUeR7V9d//4@¨z%K?q1NG!?K2-[BrF e:\qCLN(,ڗ Xm1OcwMN}E),Y ֺ,?ꛁVޒ V:U9^8Ħl!@ޱ ~^[*=65 $K1y nU4b>Vvo]!w7)Wh P<ޱMɰ~$31m&}_Ɵ,)=q(nW[5U*ƃU"x:Q{$bRF"X˲M}Wba^6̌RҦB*vpEL(p;6@lY`4(7!=Xox$#{14 76L$g3gMt Z®|Sp'wVTx AM5QwG[E~p.,4/q2@'*m X;;08]|\Ń*yk9xIPXa¡ ^sa@J[;"9s`m%@iL0sMk6Z @c1?u]M'}]}ປ */iLpY  5Uxj x9Ǜg27]#YX d^I6! Fku "`׿k<0$BSnSpL/@<-e Vgc!+0uVܽV9t6bfeQL&!ZaW/u-.ŜPo= @oo5/Nx bxgf!R]|C;ӎ` dn:/чI#L{/m5{Ӏ o/XUM[ڙd^-|:O[Z0(5 {?~ Xn%Ķl<.,<w퍂]݊#ɼ¨(5HZ8Js-,hxoю {Gy%,\"|Ն̢ySOVVns0 P|[8yT6q*w,&5L` /d(^t8H 073 tVK=rӲq͗[,vj`3i%[N:M|veWxhH@x.6ƴ9)3%DBƆF^Ԁ2(.ZϿUe `9:6i@pUmu e)vHsϊ䃸N5dVdB5TWt}E3P)%7+p6:+4]J2w"곊 qЅD0,^1"̱e>BT^эLł>ǜ &U'4 〪aߵCeYop;[sv;WMX4›/щܕٞn:̨:Zјz@EG^IDcX'ӓ')03wʵ$K5N;-abSVj2Y؆!)A$caicbxAc&Zo@*Q-՝4};K иiA$1aYVjL!/uÖoߦYN%oAV1mf)vj5Emd#z=;.gl=gޕS]x?>aY[5#>/-\3@+fN$9npl +tb wx3s@RbagcwnxiV~G;ņ__A5`#&uilHSU}"UY or!*Dn7__\a]),Kw34;=N,ꊿ9Wc37_6wƙJj???>#{"*IN&D 3c)O<[T1 7jp<7䏓?fhPm,Ӄdgwv$+4&d\ӂhW!0wЇt (=aY`1V{a$DoV&MK,iU\6{,a95B gUslUFPz|@IPEՌ"|7^sC6Y#mMA֦Vz6e؉h`%0FńO،vt@Q[iC*"*_phIֆKa,v?6>CMNMi{L5c-S{b2r[ ] +:RǗܡ΃02V 9Q;FZ;|u(_Y["̼6_B6:٫ ``-R=`-T,m?NLl׶:9)!4k`5muדU;bƱ 3 +@RSPVa `mźalh<`R$X`L]Eem8ӡL$rC9>il ĕ][/rR˦Ͱb5۶^ O`IH.ii߷=޳hg.O)b$6/G,C0$`XpոN+K}B]q{ͼ<Jw| j=湃 `r5?Fc*Tm{abwFM)ႼdE,H3bx'pb'OW8|:WsU? +l))verwZ"rr[yh+lBVJ*齢+G#^ǎɒ{xɍ}MQϼ[Ew :{hX_>o*:zy N ?Y̙˿؇'ιg?qE ^b^"]ٸ; RgN|k\X,* vVw܌eaTiߓ~ыb׍$wM7W1aò\*irT$H \y/j2vZQ:}R*5s,{6V.<.+#ٿ$X{ ^`%:Ièxb Gt<|Dcow5DjN&]P*.Wu^깬`2/<Vu°Hs ƹh#.\9Wr6+Xk^^/ubBZm@Rowo{o zA)kP/LEI7P;mnZxpu FAʊG -=ԿBmMMNpMθF1IKcz\^'~wPZ?ÜMl!`W>dc߁`7*1~lti8ݷvj0CzdZKbAٲDٜ t6ՊJ#&Dm ;Fhtgm ?CF)&1xЯTtG{dpxǏMcOKЪKr!IۥGCni\13mL ɿj`f"2 iKIkɶlrGCd_ڐ׀} Xaja8`)`:Z N`|.@6`0rȶy8i Ȏ _N2T֘0@cdI6A[ ʹg'1{яuծ^a9\8/jH޼]0 x D~qN^keY*PE {w;߼w7Æ]NTiE*p%=XPɤ\:͋.)zV|NNE98R-ưrP*9ras.NcHb /N:ԂXV dKk[rJL{oO\JЇvhv7jb]IPL+`F${Ii?)8vXUJӉSibCIp Tvuٮxhd 386_w0؅Vә~8ExǛl@++$]th 0E1hG}Y b*C7,,/2):΅o=Vt̝;-JNĭ2ƧCzpځ+x888S4 :* h5E6W;XW=xŇN0ʉ0ɇ71]%GaȵF_ $#Z,t&bU] KN^ ͥF0^%5.w&|CrWQs7u;gA3-lߥvltҚGckT%G0ղq}\bլ9phI֓k(6n0jbmQʴZ,?Gz@I\)M3ՠC;Mp4t+[}0FVwy @I4"q0k$p~tŦ*w'D@bpygj ΑׯUN!17`^r&A2S+ϙ5nc ]`3ܵ\%h=[XSYggZ;i(6zC1GgXԁ 7TčKU B%qF^T4N1W|X|\ՎI<|=A#$Ћa'곚t9Ro$_0P(*,ℷՒjxؖJCy63)dF>iK2d]EpTձ6B Fzl `1'hm1"V7bu 6O+,e|e#{i a6UNJ+fv>A,^0G }XqVjn&slb`%!XJ95*)/l?L@EKQ 7OACAlӁnz] AHyxï]桏M#ڤaDp ަ+`X{a,O@Ltn]Zӵ/G($Ğ)Nk#TVw*Ldhsr#:^-=sϳyF >Q!Fyѷr)04fENU?xk z&!C}fo U>[@_:?}:`, n-TwoPSZqv2KC1dX'%t)$bVq*Wx6ԥ,aj62TPKM}-j IW\RUopZ2NPvTx=X ؟O; q0+|-#w\[04*&i3- zb0Dӂ@~1 *-`ծSkC*mō;rZ&a՜GynW?OUcڶz)d 5)XGZcx}=H/򭅞LūG% džl)`?(Q[s_`Sn=ٴG\tl85o VсvmVR+* `t]|h&[tE([(/>z!ȿdVYMI;Lð0,&۰i|759yE#yg >o|n1J &`YNj̪HkM/CvmStj2oF^}Y!K9 z]~_h-ۊY&˹db_aXC91IϪ?х]J:3LOncңx2^QL(KN;˓| d]9m3 AT5 -_c+ h {}K˽-MծZmc]UW?J0\VR}XE Br,ƛ{G7 "L4^Zu2 FI~.F6&ZfS{{ Gٚ?pV+uʹ\v`1\EJ5ay )n8u*h=.F,O9qZAߎ/1Y`߂;\(Y`W=pv[aԼf{LdoDS;q B`Z8A-?HJV]aZ>z-id-瞧L-n聗bfv}l|yZ{xVMś)SncϦ=s]:.UO^E8>д e'WNx;b1pZg.]2nd" OݔX:wpp0t0}46-')UU}~u!ѫ@-FUo?Fz!"q `9s 'k S ^0RG띍fF5QFv+#x-"g\iw;"L8;aBʰ _][Pr8mL9Bϰ1YiiD1e|CFpnr7a <1{b ~,@rIV^9y_ayL">G#?MNҕkV@s39x˱!^)߸2F_B7X`HI+j#+bs;]\ME󠙈 'A[Q0,lq~7Ӯ LVݡ:ߊ2E跺zx%SF ah{c*k Xa,'I3.r@/Kٯkۆy0,%w==|a1[IK3o!C;&#>SS+TKwrp` S.>ؗkMzڪhy ^_ FJ~-_/hTdW9홪R1LY.ks/N\PBo#uIG/]V@iOH*n74Y]vk Ȗ_u:+;B67\`ZUxLJ@ Y) @N RUCϾo)a( ֝ۑSл >}@r^)K,: Zlq̭V$4킁a(YeL/%ۭ] ,ϳKOv! Xq_ L~,4V,lv?KCf`kv\1Epa<9^1 $d}52-Z_+%#-":n,n J&ijΞr~$̗aBދP, j`KYh7 %ѿ)w06Rܳ4s䢌">{?zM)OF'3's+] XV>聟ʄVԑUOoDq0WUT]MhZhDCWaidZM%{ o?ɓ>h ҰH5:Lmwn]׿Q'?*~^&gJ@d[o^SMA잸JI[,[oY3 {θ>XhEMz=o}p/ 30wd>GtW>U$6E6A(T=G'7WHej0:]X}X?SA'nZ1 ˰B-=ǏrL$VcCkR5ҙj8ORЗ$nU_ ;sPY7"r?+0#v`,e4V O@\ґC d$yUAvH2>}:Rwu{vv/_1 K?~\~rXXq|-jO4_ ?qNkN{4s{XGωlsƋ^w-WߟmﯜW~`rXS%~^'n^U*P5qM25Z>54pAMSpN6g= ҧ'' phyMII-+mcʝpZʭwAkL(2o 垢01@x fq+\fYO;U˂6'\|fsA k@LJn''g >IkfxP 0ei_J@B\\C2hRŸhe_|wEcw'uT|{ij_+γcVDTn^Ӑe9.qVy.w_,)., 7M8'rq͜pяf|l2,Jc=v{'=ej< CMUK2Cs<7C!8 -j:x¬t}xV&*>V-Nd{@$ऽSIfsa1 "IfW9r/j @uWH#7.̟gEp IfCdZUq'@ ~LK.n列GaS_oB"1BE5ԭd]Z(WI;6mdfWgwXC= k_)} 4 d] GvEЁn0Q:^HWDؘo$@ *@̘ yvJ@Ʌ)3-W>2?k9}ЇߙS^ ?Wr)œ v6 )n7lv*,ίkwV;)a~^"??X48jn|Lf$88\mCp5FnZI)&A9JtV`Y*Uqf.!6Tj=LaJ(u5$S1S!뜗QІ L)j5z[t%~sb౔X519r#urkqZiaZ#ALyT+,{RkU,23\ RfZHjȸ*1'-r&m{-q:\wYmOYf'rKq0W?]["t +&ic𠶕 ARd==aJ3Jg^ M9nd A &A2UmwJ\|gN+^@%f柨 &2,K]Qaݲ?*ZP,cچ{Q.j8$N %JLt4PA "y&ISNUjZ!Ÿ5-ii|pȴN/WKCdD ϟsoh7Ȫ/Gnd۱Ex5!}Y`}$R*VL~Ƭ߮LLZGL.]6+VYc)Zx4 -7uU[K,U%%,K3`AGƎJs/pAja-U°th4 ߘv(vxnst AwX6`(Z%bPcT9#1n,xUԬ0vLkST¾t`:>:{>[ovr.nA͇kv&jV3-Ŋ N7 tvK : 1M;YIJ  TtڍV^Ɩt+<4q\Kj gDym1Z[e]߇<@C{Nli:X _C=5fX|+4);PCAX籕0)-Mg^y7pqV l#Fc3U>m7wpծc?!C5 9y(6Trv'dq B(x*[TUKE?ȐTzNd+w@3)*LLNNg" 7ЅUӍ% lP0//ahw.wF =3uAtӢgVk2 Y1J j8z'i"ATM7LII Y@h0Px4qLU/^jakq֊]_Rp]V[s a~şOpZ49̰5tUy"e7wY\21OjPUkF X RC |gX0YseκE*1-VKpŢZ8pAKAV$H }t7 yOyak?E:Ȓ,C9:qVs˨(OuPE=:O n1~aٚIIG.:8O\3 Tv#hLbQX 8S%ZF@k6}f8ɥۙ^[# ㎚JI~"|6j:sAp&WSL#5ˌ,Ȳ vxo퇠8V$k?Y YUxZXm11{;<]6~]?tX⮙NT> 6yL\IcH&ň+8 [$zo1mrjv,K#~={?aզC^Z5KʹMI$4!қVoLY "剱B|x<]S .`ZvH'c%?-`pw?6 :V<?lppIyYӰ<I>PAda2`o @`%}Gc'K ~,INs Ţax ZaYLF“{첟c<X 60uƎy`+V&[*<r[d>.32ve86p~.C<%lBZ% X)Up Ny 洘4a$˲e׭ j`AV%BUyEGD'9c; Tw]TJrA 1 ٺ]'xH-e(HY13OǨt)M;Kp]:E5%C/?D_̲ҮpspeXǻH`N@O~( Tt`pZ*uU43}kms 故2ۜ/ɡ?-j֞L C.a)QpOU_|քsP;98mԂ \QND@Pa~/,!ytn#h鞑(>CbB Mf曶*'ŻxV4ESiDjf-"9f$/F Zuٿ>SAVWN >gOyxl&!āPCf m VTTD}N,WT5dh:z2H+|0fج?N;ܩDBW-|z3 C ET&3J4!?.<@l7R~V o@=o `ߜ4^e1 +p~BdY@Rل H+=KܱD3t5{JJe3fk̮hϰ<;<'!{` h ,ɴ#rUkٻ[2;4u$=Mz#H@ήD3KW$EfnɊE[2%XIG+JGZxNZC-Ir+'؉ հ2*XQpQS=.KW*,ɴd=T΄Ugm7t`W\Hw>cYb1oӏ/·p& l'=lKOHJWcA{ Ve'Q5%"tϙf'%*%+9$E\l%JMb&7!]K- V-J7&JљsiEIIgNZaY]_Hwb̀St_?Y'0'ʦeԳWƆZd:sZ3m+dNW:j,\90%2ڔǙ̿ζ-&tEP-feMVFDZ߳"uBs.w ( ck6`<'7(7G]DT`Ej 7\ :T-XX>nLf67sO><`u.4Jx3+PAz. s` TQʒϲf^|o_@D4qe$VI ,RG WP  d7TIxn9ެ6ѓyi/4G&Fi 6\̜O}{ga9ƥJ,툁շbzN!X;tq>]=vP FPOΟ5x 3 šS9Ҙ 3q*JA"FU W5e< 67d T1ɌV}xnfE]W 8'RO}я&T$ "Tʂ ~Cf%z%ā7.=?qT4 <>rˋ60VHPqRС͡B>hF!2848֘(Pʉ*rq~AFwlʫQ%=Bn4:],`oU.MJk"A+eE>?b A;LI0ɿF ͌՗ʟgY|Mn!!6I5kWUd\i5IQf[a,飼Cއ)E:Dz&)7a6L2)\,0Me6*1'7l. qHਕHć.}Y}535sዟ?wq $mKMq;S0+q:YJ"-:po O-y]˨YB1ojdpY/EJT%|#7\qjL9/]_H:]MJRT4ku 05*ӳ&+}ns5p-LÀe=yŒ-.+F'uz5R C'UqSD]K 9\MO}whxDtא7dZqTC(Ί)ॶ'%}x kqt?7SOYaG 5p\8Ԙ3XKiдi,<| Z`Y 9Q1XIkkwc(o kC>)dLpqFňQz&__hf edxLIםeI =wj%X6NqKH5egA֬s8+BWԕbؔ~H8Ϣq=+4]F aFs1![oc؍{i]jdY3u=x,G6 |^Gs!y& ex2s3Eی |>\t7ű%9|w]$DiwX4T?ח}&̃b hfJf(pd5}[؀PBaRЄ4 dS\ѸFWH9Ub&"z5"0"Aէ*65e:{qPNI_rkbQj*@̒L$s8G ?t臁EXjɖǀ҇?z 1h@Or`i34BPT ? w ({Qk4oEjy(T:񀅾jLK}}v5JD1&L:ɿ/P|_._~8/E>>BKvk!}f(7Fk}34> Xyȹb$>5)CGa+|h8yA]8ipNUӛTe!.1+G^Cπ~6Jle'dgXp/d o_6%Dexxt?Kgj^?(Ŋ͊)р">s쒒=$Y)&iE>?>Dנ9%I[)Ԥg [\4&RX D&i9?xF H퀴UM8 CtX*mAQͨJ[9JA]>)qZ%)'NaBS\! 5YB̹Hl oH$?tN㤾" ,kH:a+L ]~"p7r%͹`;I:7oEG{gPX\(VaKy͛+L<;*[`g%qsFqD_켭#[xOTCJ 6 _8ё"&V\21ʠ2*̰=WBqeŜѤqBiUqU5џ, u X@ecDM0%F+=\VΊAY,Y0"u+iET%da[bQ %KDGLU>e O$8GF<ĩL‰>ꟍGXU+44iCb\T|6sGԶLv@p\ntF |rf# _dXZR( 5Qugd]@Ձ)9-6mWfa9 דetAc O%H8_1l\{[蘯 %\ QS5Ukjd xHՃ0zb A&a925(C@yj9iQ5ұ;?YMyd>*jūԡVqx!>Uqfe]?&s{&Aur V[uq<RduV]2eiwb%'e:Hޮ͐Սyh!K;ϠfB*?V6OIS="Y**7c{SJCx6bNnԸ XRt!}[;U JZYN H/aqvU#P˹[۴*u MBM  d}T#g *piyzG:p{_5+>38>mFOJ ӘTfX EA Ll$l2ԁB#2z 'At-ѡ6`U%C>BdH6<\菸4ퟤo /O>Zܵ +,¹:U>*bş+SHh^ XLQ:`y3t@ebpdQH'NKIRP>z|,#LIճg 9HIڹZXwk*!][5W[4P ZJ" Qt{CkRHDѤ츹+عjX! kr; T3)K6,UЏ #N++hkZ P.IXU m|ApPl8P'_6SFCib(iPnĤ ¬) nବ4L:+PCV "0\ Tpǖ%t8)cpU CaQQM!]e^D2zE}TdoT5% v O<1{Ovљ[ +q:0 @5D+>4kր}ּ2׼`.:Ʌ7܈\p!0f]:`F4Y}Fí$2G dUwT2yY=ܪGCQUT7('mG]xO˃iRnKeS#n"6,zqzzOLyⷈCs|ITV݅*.R9NZHRsSEV4 á (uIӶ'ˉfbXJw/0K,!cV d t`'MQB dw:k"RAs`G2Hg(rwuF[_xQjV BI XANgoDtַEGh{ŽCwfdD Z菋T \: TEX TYs?w4[b]Sn6}CCɯqBl{A*`pJ ` ʮYS吺AGF+&RE'A <^-}"]z^*bYN`+M9WE';W9Cx}BS1uu5 3N7k,>MkhX NJ;:DGIUc;,+جL J)$A3NAC"qBPTw4|>ʔL`^Лb|֚m'!bQtXN W惔&etķ90)4;qL!{5fAۑo Nz~q;EUTm,o]VAhQeEXՇ.Ϗ4 ę,B2D+R X'J7;dz, ^* ~!1 _ F:U{m? 9Y̖60Sv$qZchGܢ/B^֚vq/;Ř*WO?bBHr/N6WL.nLU cv<4lLxsOJ3 $ ̣1 )U[_KE :efMVd.(acS˭= !دZ8&0V멥w^?XCQ@eȭV@UmmaW o0S~&-gVÈuU*PӘjOyӵIn3 RNtSA LۆrR-p%:eOjIyx~b#!܊EUrž5 BRd>eB!Uxs6b8f_FOYE >rkɰIZ @럿.@ $h hJCjA)wI`T6ri,I?2(їL9Mt5,Ҁ ;%uޘ4r[h0B{gHmuck[*Օ@ %psxFE3+RL24 [wwUJ4ƒ@롷ri%NJ\l d&%-UCB`h.J+UDeʼnrV&V.eTeءx}hUyي!\p݌KI’W*<~>2BzH3yӘ޺$NA+΄wޞ۱M,ƴuFnjۑ2 XIq;T&Mq 9AF7 c4 cM?P$ @۪ϊj Gg \R%׼HUVUT%7O<*c\;ʒn(ee.̮j@WHd )kEzX\TUPQKTȢf5CQ`ÁXIWWYQlAlk m@3n,,sm2) 7\ *?3sS#)Ru= UtZWRup]*hiIa-kc@i{8R=9k¬$9_o^]R6hē^c)iG,=+{^/ 6?%UQ7Mq$_i.4Q+RU1>6dRwHFl6r9!i˷^ТJK^5OW3}|UQҗ1YIK2CHATa.ROUB'.T-kN^|FZJ(]굊r@&~>Zut;<\]<A;J_PoM4}`ipܚU(Ҽ 4xMʑ8ʲw5$B>!Lܹz3RQ]];uj\I橻ÌJ2]XVco0k 6=2i7'@lT1yXY]{H ゖR\ qX6Nv vb+}ifԁJ `)T$IL%>1ĎˀDRT?C1U C (!ّL(A"wjMQprvZ?ѱ=i9T~^KBTy9阩IN(5 UϭkV@U4urPT0 1ύ8`:*9hḶmk?-ucL߸LqYfF @T5}F*I#oCHE\9dS%3ғ-T1 ?`qo&@tUb.cM2X mgѮ):<)fڜÜ\/@ s,i*B]JQ(*5 q$cy\; c R]Iev~9TC=wf_y41 ?󍦡xk0 rTY:? E{eϤ-׈qj$;2XYiAxF#Ϳx!:MuD#jIQ|. ,m> dM;ڌ}43 ";ZI\!9{B%XI'kZ xb,riޒ*P^!Jg?tσboߘvWF2H2W:s a5.r)C{| ]\ (ŰV|tΕ iW\?2o/Ӷ= -01qoAiLV3t`X,$Q}TS4/tmɾEB̤s5J}\`Sn0_?Wl,eElrprnNɴ4!BMRA| {BhjdŮ΢n\#Ab`Н5cWd'bg^1 +|9paBnœXJQ9击ހʒTR3m`B:q椝-n;&1ʌ*)T}:ʢ˯Kji-rCjJ;,e/ )d2|:e!)6#EP& 17'y^YQjTV-xjXdbLziqv8^5W%|okc]"6 Va-K*4`0ݑk"sMECO qja.KeYx:ӘLAFm}aY9ɨMr[hb;_uFNhdU%Lsc#R%5!U* #qu5&YDzFj>Ri\5t ɞٲեŰbC߇%V& RճfNPd&9?mΫQyrO:@8ϋ^UpMMrƒ(5Na`_n^7lEBNWC#?+J*GQ~(ɒ,o41 a5)Vq)Iq;|fdw^e:|(P3KeYǪOU/;P[^ `;N&9_?.EH5A\$ݲ=j' :+}<8\= dT*n`-I*=y!g;\so0lәުq О Լ7F G3yCHt(0Y]K c|u6oSg B`Evvb)#Pu9>dP=xj^w_rY *D/^C|$|trR)9*U459.Hb I|'6v0[oO_~=61_{i6ENd qTpa!H<\ANY$a绥) .)ei=> P~'<4g^[3r;TkaFL k@n5~.aU d@OU̦*3jaF2>j#cznL;Nx @C\8:j^cqA8auj1\d٘®dTD`, qP=C7G~.p"*U!hK =hQJQQnQ=o VF/kfjyyV^M8p@"?& ּP3U&/ `*h/U՘(}kcs[a!,,s$[;TIH>,W]TlKG5S #'!`?_Xa#~is2e-ɢ&$>יR/\hiKܷ>ALU#2w2uӄtKl* "9W*I$_pJ{J!܋:敨eZ)Cnc5~Aց*z IJ^|dWX8>S@tUj%UI3W l dqUQ{Hm?/ej~#%O'bT'2Dיj1f&VhQ]P44y6@xq T1ow}ұZv xr4O1:M'5/| Ji-]nWUNdC1.#u1>TԒ$'ݜbp/FjP5`@{G EӳJ2riUJgC736|W idqbaJDvT7`JA=(C FT?)+'w.a0멱iQ3s_Pr v ~V׽I)RؠU1LiWhyȑ ' :JV*j='j#jzs(9ZX#2 ֊RX)IeEv*i~ 6]`LN*x_㲐,3u{a?3ɶiFՈiH>,yfqFRZ I x ᎬjZn uA& ddW*<$W@kM=X:zq F:^&.rexM 2F)X,yA82#m i'KgVʬP7E(> ㋨p4THݱ9TQ(_ KJby9i/J+e2K6EM9@ϰ~.j5=ִaqT\3 2;#f^Wq7T UI GDA8nTeZj>]ƪ Lɿ˙UejXL%n#BRu90Ι eZ'*z,)HLSe)u,'i1vdbZ_sE[R"v5WןkX4 4 ]%<_ G_9 P+[q"3]Xm 2OTj@S*RG tvYDP"dՈTJt;q5~{F7XWI ‡\)Pj/U5%h!V #·de$ƁIjMRBhUYÐ:ei3D4!?^hA-P. WPCL-7>36j6!C̊ S!*s:XlG` R).{ 'b (NW' Ձ{ZNv{D-K^| l/xJ1eiq~ f* ZNRoɒLB)g9= I_E#2>]j`pY[Ud wݜAjm`G!(+C+V& aA1t Zb*|8V=1Q<><,Bq"5eR2w Kg˄YN0 >&DųpZ$}B`-xspYPS-~r½p7R+[9]7·f!QCֆם)5 Q=']< bQQҒg*S٤6' 1JϋL'`2쌔3ۦM0ZFr$Ťf'4Adj$g0;=-j3t0_,TʸǁzCa%,BճLaF[S-LPIRj ?qEכI6 d C,?b1yGs~o9HTYŤ&`:r*IZ^^>kVmnE5f`[fRM ) Bpd"H5縢q]}}X, L-JeYR,Sq'I3?N&ӤӴ#G3Mx2mqW(ƒh%(ICb]9݃w}oޙ],w罿;;LQf#]~cB`n5@ ;3(?Lr3I IIBa0(`W1XTU! a,% {xYе]*{Jױz)4r*sگ*>%"r{|H.[^2722[*LUpdD+M4[ j;ZJ8eSv&3!_ϸCl$%Kb? H(Vs#xݍeQ~˗+6Uνv恷¼_ڄF|}lAm壑1տRrJ¤1dgq?;Z"Ȗ>I U.XK1c?47}]1 a 0*wi%XnLӬ/.zK<ʗ!sfr[pZJZLl!/9YolSX:>t˒ &3it;eҔI+"} "l N;%/ `cC HW虢Ų:hh19[:@d;֋)xG nxn;A|HZ9.3p3_ːH)1T(&uFu֬HJpu_^ ~о/GLdGxp}=@.x_*Z| `Ie\Laf8*i,ZJfD 3V`ЄLWT\B%mmmwG=s895ԮJaK(ReC~QH i(uyU'%L WȁK4 ^g c`vL¢f_4~tN:ά٬jJNGa ʖbY0-s`m_˂e7Z[OO@bY²|^iR~$|Q&7u&2~ERu ja6w1:9׹PwS0Ecu>1q0jBb9Ov=+e;:AM*nX`eSKKfX8pć*togcgm@z| 4[F^SFof/AF1-)9&&g= ~u6(U(G6")_5Q~K9OO~yu'kӫx"sU<:?(jcL_Qx@ \4Y  e`И'G&4Mp'|6ćѠQ(?Μz 8Xb)Z鼮XMbt5&"ˍ䥤dq b%,\JaQℛd$;J`+c#D+`LK *Bה2 ~Yϴ,JbFo;4}>H~7?٥rйatĶ(:nT҉UNA!7,{GVbBYKSK#pp>ǜm%V8*F5p٤ 3KMž4v5焙83Q ΛR[~y~Lf6@}UOU5VM l%jbQ'E'WM@M]K9#s%YW aIiwd5ZtZyMvyZl\]F @m -ųf_{c8+=x5_qq!Q`FRH}S 2˶?7}&HN{fc"ؠڂPx DR!ߔJ4ZZ DǍ0v߮;큕bCdˊ͛>SCArtE#?#|a KGL6q I@f_xԍO??UQ3a 2&\ˋT'cic:.ziZӨ|L=Ѹ J7gOߣ\єo^lg L D/8ےeɆ@x 7 2"0[M s5' 5mu~_DkVWDݟ\N ,{ /YH7nM?;g휷ʔhlݝ9-rTklOY_'XC\⒅Aӓ#oM)GNL06VŒp6Yu|-VTBVX MiE8&#&c{@d**9Ny啭 XZsw^J_? kh*YXcw&N}" "g?NfʶT2\:麟'c{ ԙy1 ^fWtbVtT335~@iT'pcz<;iǐ0͸2*S,^csPMpH,̚#MZR!|05zl]uTE䍱OpE;p6b( :U1 \Ypl-ܛ 8ܷtup}V^*l 7ЃORnP@$j$VJcF.a/{+qDN{D$]}X(kMj~; ;χ9@2u+KrXȬY)6 X%5(_JiDUG?2Z2 s tvѬ{ã ӽIٞn{ERw(Canv}`谿܌xM~`e":4x@urBE,uܳ·9{kU`VDFV\ JBU@PdzR38mg;]o毊cddgJM,cZ.tqXƭ> j.p <9ݷ#P)n²L s3." žGS7cg8wVƒZv(7Lb'(s9ҹFoNYMg2*oκw h"MףTqZ}9M_#L1Cw6#{/ھ2lQ2a*Lut-6wO\Ꚁf+*7 Je{57}b?Cr@4ja88,Q3uV`AuMO͋rfr̵0 M.6S^usULXa&+~P cǪ}A}gtoˎZB ZIWZ9 齬2ml'e|&-~ߖ^n{D_ރvZ|tkNf:%7? lUYoUhFL3 1y3^=+Ӣψ+} [rH~ pYo2:HaWWJ-JI8yr ] h΀bU҂0J0be8-Vqje҄*K]Xe!YDHf_p}>rcpGdo};,M{3KsűEU:o=X"&hZp y0qJMf++5Ԝ$74\V`Һ*WP]>#+BUT!8Nu68e]&':DI͋:d V"i"F dMByI?6hx{fbT:ܝ${-@r@R(`CVepaS5Ņ1W7ݪѺXfLP١ȕՌҚ.ʦPX3,XЪ7 ّb0Y)Һoŵ_ T5)p1Fq.)3P;/kWǙ#`t)ta |W׾C@!Cy\$;GNfcW >nel0UKsIJjV{0(*W)_*ł!G\-qL5#}j Ou2E2LdqeվVE*'4(>ۯ:-`+H *& >sɠ(~3. *)A7%G̨Lx_=wiU[, ̻W}aDo|c051_uB簓? !)8aNo FA^jе#4L)n$5Ǚ4TlzKD5w+b}~p0PV~*MxJrظ5Q5Ĝx$/-0>YW"󒣽H-JkDvQbCZ\;sS/ùW.ށ%WN>Zgh*1E(`[7YFwrCGs;s&3yYujR>)E.њ3M_5W`c] +1F~ADt/ȉL+{W6XĬlx,k1WT Y|.8G1#Ľar%Ŭps!q+z4z}q |p'0}rƹΈy7_v-B;9 N»} O#S:i u|BaFHPJ%"w ~*]~{<6,ҽ*EҨ|zK$$ȕ~t{ҟ˅^'k`ad|9&A!b~?De IfҪfӽW 9"h©!٩W`yz:uS5'98pwShЏkdٙ7DZäb4U.~IL7Q[O`\:`n- %>y#@p5ހiє/&H9,+z߱n4?͆[ "vh8/WQb}ZkTiFKN;X0svww"=V9nvugX;%gŋg>w;>!Z-$ -K~%U=8EvMcK|Wnb`;S:++)IȕeV.wBT1Gl+{=%W;LVU,9߉&27+x_)پO+TF#@Xm՝[gGu:b+]mgZWCAe]|kϽa]$Dу &ELZRJ\JzIq4k<'av4j,հ7obhH1)dN "3E^C3{A&L}23c@ZI 8XuT^ LU?ۿzU *io"8;:Nʯ@0#+;`a<㉧I"s3`}*&]aB˺7`]gOFTkt;.FIF =UxaHsЅ.8?U(eNS t]v>'.vjFu_bZƸudJTJi9@q` YIYkݪUl_>p*xOfч,nj/ Q@UInLntL_8Ĺ.cO2\U]wj'95+XyEXd>:zZ k߲ tiȍk^zeY1T5:O29~-.4H3*2kB);iNAPϏ)樛ިu8ϑZ]b=1D@i27%@kBIavJ?,+?`]|'>3eŹ&OvҦ$ɺ8x& ޶f$䄗>Q+ x Al]]/PO::.JWgLc`q?c%3ՀI%^P͋4/ )MH\)MEyr"7USzO.y,S0HlRBg(_Q $r`jM4S"iPsM Z=ޢ4ڡ@^1sxĻ}e)f6{ 'D52AGޠ jW_ʄg‹5[Ѯp'F;6@{lω%PDSiD;El`R+ 7!wsB`/#qb!N԰t$鑀;hmx 65 ߱/ˠOݸíq EO@ ozC3Ȫz`A7.䌩]TqjPkD1H&Hk@QtAW_ghZ'jͭ)'|3(8 GG`[It-5sKdZP7GWFI`G;[B*Ziϓ۬0+WcXw+(!8nztcЅ[X@[{1"Y|MM*#/MOF$&6d&}rgT٣Dcq ^VĽ fy8ץAB1<|5 `I+jY>}:y˯Sscd/|XEt4Oɾy o[bJ`\7Hټ+i8s#-[Y3rY0`օWX7:üRhdU"8tȘ؇Cb̓"} 0lbpܼuWCWUnd vLt ߳WAʻSWJ"V r`kZT!z%iÖ\;9<[ĬL6J+'/݌aTĤzK> Q࠹7:->c1|6IR~:MZTGt0PUb7ns6A^M/XcFu90NrYX_1tKl%{dohZ+ɱ/+]ԀE WO@'?-l>/FU2bz}>2jeFKН- '>b}6̆$5kb!>aRe i낈)@L0'<1&!toccuh`g>IOUEٚbJHjYGzξtF/YB+u𺅧H+Ƨ @^MeX-E~#YFbǨyBp3`8+ ;2>e|Ewqći{¦G c ULV˴4iRY l~į#yL>=o6 I$/%XiK>Z|3>l"ނ@LO٨@!$[>%/H }ԵϜ'(/蚟7zU%1G%e? ZrT\||q c{?öd\Nlc.7B~XQ|Wm}<ӑ.848|XE|P%|؅q™х꧄/+Ȓ6̧5fm&pSOv^$03`ϐ|F}Kk\-b5CGDVWuKJoǑ#_Kvs0!LDV_JqW3"M핱oÛu_y4Ut?X/|./tNt&tdW6Nh^alYAQ]c$B;Wg0loVѐ:'5!eV;Eb8ߕ*a <^_QNŧypfr?2R^zs|_9U:m9R=Q0cS-3H~RCqwnt*dBX榴*f{`:Tc63PChQ`EA U@ܼ6nEd{I9_7ޞk:#O8O?ɂmPyK-&,C?õ!>Xqj+) XK1oLtۦkmsiҦ %/&a~& \kY2c$ Ov h2?KR^F='L ،|9.|}ygf]ʨS6bn"DJ ߊ]71ܬzr%ephÉ ݘC76l|{L OO&Pp;#Ɗ+(pHft`ISլIV%Ux) ~{+/ޝ,ΚC܊-:kIQ KUiVUؽZl%^g(_JzT#%Y+=UEA qI>f4ŋ5\8 ,Ry`%Z8)^-ㄱ8ɣ58ZuVw՝~A)q Z&`Ă? 5T;RB(ؒ0 || n;֫k_f&%lQ?]t^x@aE>7Y.G꾌o엙5K qVɵm5 ШNAt5nUӥ5J e@t9M1|>SO,i2^rːCF>nx}4}>Gln^bx A(pJj3p@G8oꛃEbp:nK&4? A>,(v˔ (Fő"#}*bz`Pȑ)y5ݧk~[Hk|#X{ uզ & pJb)qEO4^>DZT{ i"2L#S"Uk&4Z 'h; 3g`;mv-yG>GNg8^E'}7 >{1ȗ`+*$_;8/ო1+-d oP 8[XσDB28+nxL_W>ˁe(-M-fspk||^[ɖI/y< >G[0ou{hJ{LVҏIf,{J 2xAl] s$7Z$ }D"`ʖާ+#1x $xEv+Xeo_5UAEM9Z#5ȆP]X$ɒ uX-D+B8LXw,%и|L+_~ I&pYr$r~|W+s1^e׆{`:{7Y'p_F@@:%xV^z(uC cv K l58h]B|P-?s 悥3VFZ$!Huvgt0-$H+aA}_H@w{jTt3jf?'J:`֋ܛy_( Ep/~||QoK}v%? ^%;ῂ`|`Տj T4Hzp hg#`JIY+L%]H-촤cIǁV"|YB+ +FcOJȿIK+8,-k)3f}_ n!ioRhnFs;_.1&<1 YΩ5e6+"Wp@;k;ӿD<:*|L:>/ IҤ5GQXJN:~҄ʸ.ҿ2(3 /"m-I HOr*`/,Dys *4Z/JQv3Ϗ.}W O V` ^J.ﴈZ?w @ C㴢bUf j;>H˶㕟PaYV;c} cEϋ(#%/ߕB&%QV@4pف/=peEd4KxAb@P(yf5Magb@cn}70Xqf>dUmh!mwp&ď͚|Xh#~NEI+x%gNGpZ8_ۯ0~1#EB߾!xKK|} ]ZqpQ7 X*YG"5 Q݅xɹ85p^] J[InCcr6auc+%W Т57K}+9@5E> T= QHqS)jH/R bKjn\5.u7_y?G]_5X]`V`vXWl%_ĸ*/߂'?^:t8yiFɓ,HHV4%tfU;sV:퇊<(J?ά;.h[imw~fVdVPٝG.ykYl%\>꽇힞=>{DS'Wɛh RMPJծ =UXpNoBt|VjrϪV[`糝T%S1)g'_) M`zfwL1$ڈrƺrNgA~HR:u=^K,Ū歀+|V hz5֚᫁bN,Jo}%]Aki p08wf'J| >c$bI)jwT-&a o{5ܼ.K]Z BSjgK0}LYqEbVCbn mh \z(3/N7OX͗_GO.:lb`ci qj*7.pQ R <߳yF\7ZYBxO)|VJ ^v PE\ˌu,˖ 9Wr07=mZ uOz+=f>1ڿ} yy.(O jl9R*KREOz՗]=0V/$ޫo+XEU;6 T@[[va}̓/6S]v nm;႕cktWc=[Qנ5J@C䴉®UfE/z.Ti\B(ZՋY'j68` ͘Zm7p bg^X1=plzֲhI!AaA-/z~۔r :=:tw*yV ZrfF#ᴽTpu\9bNL9QʋrY@^ d cUSx/.;ȪUXU ZFLfZXٻ~Gi$l7* AB # <N]Z>u?E߮ _fKE[fm.( B*bUEU]|fnO_JZX8uZ)XJ4nXVkgR^taUQ6ObVmمu\BJi+IhT^y˥po]ym|} c9c9ʁ7><e kT T h-I̍0dA/w4ːLhUXfKOvoGUuyJZ|Ϳ\)XU%?t3hTr#Тץ?iJhVg|Y2 Vtcuw\>0dD@ePO. *UA^wv6@s JV\fZ[y4ևhJCGw$}>@U \_ΜEXѬv.*/i,! 1Z1]2?S-{aHz<8P} T%@<+CI#W.,6GO> X&J)de=SOҙtY'WRt_-VY+­˰\w'bêv`u*uN;+i? T-Yަk^$S]\d@v۶{<0OMͦhRU]KˠL$ aU۸Yq:}Ԣ%ksX/M?oX lr.e25j(N۽:l+Hz-ʉ-Ϻd:e>N͒^jmH b D!^V~U:mExlV|i51/PP넰VuZKi&:iN۸vXCuZuVFXuZu::XiNN`uZuZu:v[ dC,IENDB`App-Alice-0.19/share/static/image/privateChatTabNewMessage.png0000644000175000017500000000147711366325415023220 0ustar leedoleedoPNG  IHDRagAMAOX2tEXtSoftwareAdobe ImageReadyqe<IDATxڤSKHTa>;KgzQԒ 1Cv b7 uh$" aj PIac 9cmfsGoP=ǁ9ԿֆHbn=O &{{|wyE1t3ձU˷Ն0m0T$E4|Y~41sm V%y vD,fRO='r2>~:ȇ~UeTQkyAMC{:j^, yQP7ƘfYM5 |h8-5 ü8A{eY~Xv'pH$:i@Q$m;%IR]*"`8Y7&`06 ;Ct(m`PY.oB ﰘn.c.` eY툵#6(J/f5QBߖlhDhC,m77]~pY\eqɕeKnDGKsIENDB`App-Alice-0.19/share/static/image/speech.png0000644000175000017500000000037111366325415017577 0ustar leedoleedoPNG  IHDR=tEXtSoftwareAdobe ImageReadyqe<IDATxb?2`۷o|&BAAA=(߷XYYxyy }1Bd#vfffEjaVZ;;;Z<055\bYZu,૥}N9qcM'^EQ4Mj6FH}T~k 48xOU㛛YSrnodYWfT¨hkTQI B0:t11D~A%&K)~'!Z``8Uf_~9}BjBu$AP~t\rGPJ ez)~ iE@.9&ͥrN?3$XIENDB`App-Alice-0.19/share/static/image/overflow.png0000644000175000017500000000063311366325415020174 0ustar leedoleedoPNG  IHDRatEXtSoftwareAdobe ImageReadyqe<=IDATx̓;n@B"1@CJ+ 2=H PE< QHFBZ}328#ƒtvc*X5&I`rqEm iɲ 0A0W4M4 IE,PtyuZ 2*21Qt:!;`HEXy~P˥*X׀];24gDzg*Dlhy Z̆ay<\|<3/>ct}!&bwߗ[݅oMXRIENDB`App-Alice-0.19/share/static/image/sprites.acorn0000644000175000017500000011000011435501124020314 0ustar leedoleedoSQLite format 3@  "l-- tablelayer_attributeslayer_attributesCREATE TABLE layer_attributes ( id text, name text, value blob)xKtablelayerslayersCREATE TABLE layers (id text, parent_id text, sequence integer, uti text, name text, data blob)b--ytableimage_attributesimage_attributesCREATE TABLE image_attributes ( name text, value blob) ybK6MiQ1[/acorn.profileDataPNG  IHDRĉ IDATc? IENDB`Aacorn.snapToDocumentBounds1acorn.snapToGuides1acorn.guidesLocked3acorn.guidesVisible0 %Lacorn.guides streamtyped@NSMutableArrayNSArrayNSObjecti NSDictionaryNSString+locationNSNumberNSValue*q( isVerticalcwcO;' +acorn.printInfo streamtyped@ NSPrintInfoNSObjectNSMutableDictionary NSDictionaryiNSString+NSHorizontallyCenteredNSNumberNSValue*c NSRightMarginfH NSLeftMarginHNSHorizonalPaginationNSVerticalPaginationNSVerticallyCentered NSTopMarginZNSBottomMarginZ: +]acorn.gridColor{0.200000, 0.200000, 0.200000, 0.500000} -acorn.appVersion2.3.2 +acorn.imageMode/acorn.fileVersion/acorn.gridSpacing 3acorn.gridBlendModenormal-acorn.scalesGrid/acorn.snapsToGrid+acorn.showsGriddpi{72, 72}imageSize{40,U!)(d360b142-fd54-45af-b596-f3a2ab498454public.pngBitmap Layer 3PNG  IHDRaIDAT8S]HSa~Q?՚Y܉bDo"*%B 4«@ a (WѲt3jd\6~s~>*3k1P3AO-ԅU]ʭ K/Wٛa~B FQڝN$I(Xz%<"6?xu##2 yHPtZo YGre\Z2jRY*8xtcTo/3۟?! C BW33Q!wku4$]@c,Ah)3 ̆+% iKIf>8=>3Qig"LQz  !Y"IENDB`sU!)hcafcee28-d954-4ef0-b057-7eab4a0dd51bpublic.pngBitmap Layer 1PNG  IHDR(auIDATx 0 A# X *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^   zIX&Gh/~M\*7"U232166a8-279b-42bb-8ff6-2f8a5d822dd8opacity?7!U232166a8-279b-42bb-8ff6-2f8a5d822dd8blendModenormal/ U232166a8-279b-42bb-8ff6-2f8a5d822dd8locked0U232166a8-279b-42bb-8ff6-2f8a5d822dd8visibleAU5ffd6fcaf-bc6e-43cb-b299-fc1e4cae2775frame{{0, 184}, {16, 16}}:U/ffd6fcaf-bc6e-43cb-b299-fc1e4cae2775acorn.changeCount 7Uffd6fcaf-bc6e-43cb-b299-fc1e4cae2775opacity?7Uffd6fcaf-bc6e-43cb-b299-fc1e4cae2775blendModenormal/Uffd6fcaf-bc6e-43cb-b299-fc1e4cae2775locked0Uffd6fcaf-bc6e-43cb-b299-fc1e4cae2775visibleAU559f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9frame{{0, 204}, {16, 16}}:U/59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9acorn.changeCount 7U59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9opacity?7U59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9blendModenormal/U59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9locked0U59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9visibleAU5d360b142-fd54-45af-b596-f3a2ab498454frame{{0, 244}, {16, 16}}:U/d360b142-fd54-45af-b596-f3a2ab498454acorn.changeCount 7Ud360b142-fd54-45af-b596-f3a2ab498454opacity?7Ud360b142-fd54-45af-b596-f3a2ab498454blendModenormal/Ud360b142-fd54-45af-b596-f3a2ab498454locked0 Ud360b142-fd54-45af-b596-f3a2ab498454visibleA U5728e0c92-38cc-4bb5-9c5f-16a4b253938fframe{{0, 265}, {16, 15}}: U/728e0c92-38cc-4bb5-9c5f-16a4b253938facorn.changeCount 7 U728e0c92-38cc-4bb5-9c5f-16a4b253938fopacity?7 U728e0c92-38cc-4bb5-9c5f-16a4b253938fblendModenormal/U728e0c92-38cc-4bb5-9c5f-16a4b253938flocked0U728e0c92-38cc-4bb5-9c5f-16a4b253938fvisibleAU5cafcee28-d954-4ef0-b057-7eab4a0dd51bframe{{0, 41}, {40, 239}}:U/cafcee28-d954-4ef0-b057-7eab4a0dd51bacorn.changeCount7Ucafcee28-d954-4ef0-b057-7eab4a0dd51bopacity?7Ucafcee28-d954-4ef0-b057-7eab4a0dd51bblendModenormal/Ucafcee28-d954-4ef0-b057-7eab4a0dd51blocked0Ucafcee28-d954-4ef0-b057-7eab4a0dd5D"8v2LTsryӞr6 OCΈDH͡~mqiE waaaƌ1EfOf 5ȔGN_.J'(OCcc/5:V%8'桱U3g,KKNnLk}.2dCyR;*~GY[[WYQcnɓ'O['{0pԩsgΜ9_߁]C_^^oM"+}.qQT*]nn wߡ3@HЧFQtP5![]=h5{tFv1(!oU,>]dSP/鲑L2gq2RLp z Xj/ƾWmm]/W4Ud 8Aږ&2՝ lt\͂w7..mU/ j$9)C,ᄡ I*|0816 V$$榧^"[j$nScفirRC;S 3X〃zayisijaN1HanډzzpϞF} cP_WնU/8z*0:mO1E[>RAY2MZ:Z\XycNz$WPcd Yl2(Bz&#j4h鹓/޻b] zzIDD%m1?)9kyH .:Gі+*PKƍ3@V4nͩ'k5^C4JAD333lذ|EE_t_\\?{ܹsruʇ vxǠ$$JzIR^K.\ u*@n\ɹ0tulΚ2eJbűEZN2v9wy &g fTUUU7n\}yf\P+5k+Vb;r`B8҈#GvkOwz E.zeGBξ9BdEvz,..n)**UadΝhW% b5 /> >i}$'' &0buzݣafNeGZG}$--srrhr>YU AQA&C֜"EN ;;Ov(89rc``vx;?uPGSW2`' *K! _ e{sP;<(kĵmAokc};2'#rGF%Fw隣nݷgIi}iۉ'!h८V#5ǂŵE$)Y9s_@_V=pO*cWSG>^-~t)w/#Ot P4(53'13+W9rkmvDtjt $9Ӯi=rtۺj*Kn9s2:]; dL`y!/,J$wBEzC8T; ,TH'lZg!EV>` 3WuN_'?Y2K}":e0"Df%t1⤚zyNq:ʼ*߲V)pF2vD?+#i\J*ҁ/ :ҏ:P$i蠱Q^Ycnyv$\"\!HR6~ nʤyz`o+B:IA>x%zaM_4zWcip<ی/~?yJL!3 Ɩ6W) g-;{5B qpI`P.!qp Iہea)QwdEJ$m)&H16';Zc1cI=|ܢT%d^2;ep]_.^  #upC=_~ZNx0h!^xSN?A$3x\{VwvsAP;q#I5 K8zu"Pv{nQU9N,NzcyzGh| Ru{O^[>jedhl)<\Q=Roy|xJ45[TRN(Rnx_5dMů}s=nDEOJII'k68F%+m^ČQZvᩆ%U-^v7 K!pAw¸3)=o҈mʈGǏHw^\M68D+Oå*{|D ,SaQ)1 v_0|p=AeJNN_O>va4!5_SS+ +織ҥK_Q%ejj\r-Ax|EX7"VۼmK7,ԌK,N-DG ' 81ىe<]q[h"EtW]'ٻ>s˧ks(Be%!02Ud#>Pؠ"'mݶr۶mD?.ttݷמ9//9?[|_Qr_YV裍cƌY(+Q[ly ^Gaؔ\ ֑Kc&+/k|":Xܚ]p,=Lȡx Zމ'zj%%%BI3$Sⴈ\֣[q/>>ˊk {Vbi[]X2b|θ3g P(v6{H.Pf#N~{Æ ;N]^ZZ.)ʁb٩ϟֽ$Ly*lƴ{N'?ZZwX$ިQX-e˅.0x@yWѢteofF D#IMʆifqV|>*6.iBHD&%P}֢!ر~HJ*X%MiiDǶk֬>'iבn2iC jgo2#+}uz}څv?v72ʼmjʱOp{U-Og a^XmO3cУGjbe> lks6LbD0&6W˱V7U~1@ ip Y-FSOO8!<>1?Q|rn"^l5,L۷o?}PYYY4uTS -==}Yh[u$f {k$ޘwR~~IFpH Pʈbk5!Cn y$l}O4~DYY=ӊ-pws?&N/.<|;?%k|? a0؜LUljhu& _5`* R*b7$nO 88Ms%XfWUYEGi6[--d0[IpOJNZř [jk?:z9\`akc]5%3Jn\s`sCcQQ9A(bnAa9S&?5Qp1cqxVu|u%IW2 1曦o ӄSuN/JkINzd8_GRю[- MA(Vf:_ Ij#0;"X)]qy;'RO?Υ\pxlugJ#n rV\Dy҈[.5{qo=o$rUq[;}}B3w.ɉXYۂ(' Y#o Wwu’>܀s"rչw,D;·#r@|/g2 XuW-Z\#{xmGJ^TqeNqb6b=5Ec:"4⍈\Gv|~my d֖Ď{ju{93b{Axy՝8?"<DF$G:D.!F5ݍDz"2'C&d@,>}ܕi,쳇"gVs˳²-!sn]tDF"#k4@ZqT4[2kLjj*sDAetũr CAi| rZEgs/\p1瞟Z4ps<6z8yݺukPXw9ZG9Lk{)oݗi6"Fc+ #6B=\ f_WyΆ*㮅%ht)ڊ n{V[l(a|G- !=ԡB6|73ug9k/PIENDB` ybK6MiQ1[/acorn.profileDataPNG  IHDRĉ IDATc? IENDB`Aacorn.snapToDocumentBounds1acorn.snapToGuides1acorn.guidesLocked3acorn.guidesVisible0 %Lacorn.guides streamtyped@NSMutableArrayNSArrayNSObjecti NSDictionaryNSString+locationNSNumberNSValue*q( isVerticalcwcO;' +acorn.printInfo streamtyped@ NSPrintInfoNSObjectNSMutableDictionary NSDictionaryiNSString+NSHorizontallyCenteredNSNumberNSValue*c NSRightMarginfH NSLeftMarginHNSHorizonalPaginationNSVerticalPaginationNSVerticallyCentered NSTopMarginZNSBottomMarginZ: +]acorn.gridColor{0.200000, 0.200000, 0.200000, 0.500000} -acorn.appVersion2.3.2 +acorn.imageMode/acorn.fileVersion/acorn.gridSpacing 3acorn.gridBlendModenormal-acorn.scalesGrid/acorn.snapsToGrid+acorn.showsGriddpi{72, 72}imageSize{40, 280} compositePNG  IHDR(.tIDATx] xU>Ng" $ :.<e8 8taf\2#) 2,P $@Bu^O%UvIU/nԭsϹT?_S; ^3KEu{f,{qd0(,,L8Z-9΋b[[?]ы]2:$z^!_}kxex՜n+9-eK$|T~ڬtUrřͤ(OOz"͑y( @ss'1"""Md49:Z/.J5:7rg']4hFe9}?X]Yz>(H>/* ߣDGTW4YS>xh{3{UY?]J2i5Z.F70groɧ!#L%9Dg kv(-u=/ UaF߯xjEkqAWf:k~}3ϒTxHO^uZ ~:\_}_7G/ CֱS^xP!o۶ 9B"ZIdNF:sQ܀:.ǬkpOpƌ]wݘJjuj(8ջQ|D QX L'h*_޽Cnp|F1  U!)(d360b142-fd54-45af-b596-f3a2ab498454public.pngBitmap Layer 3PNG  IHDRaIDAT8S]HSa~Q?՚Y܉bDo"*%B 4«@ a (WѲt3jd\6~s~>*3k1P3AO-ԅU]ʭ K/Wٛa~B FQڝN$I(Xz%<"6?xu##2 yHPtZo YGre\Z2jRY*8xtcTo/3۟?! C BW33Q!wku4$]@c,Ah)3 ̆+% iKIf>8=>3Qig"LQz  !Y"IENDB`sU!)hcafcee28-d954-4ef0-b057-7eab4a0dd51bpublic.pngBitmap Layer 1PNG  IHDR(auIDATx 0 A# X *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^@k`{ uAU5H Խ V *P$X^|0ۍ IENDB` 9U!)>ffd6fcaf-bc6e-43cb-b299-fc1e4cae2775public.pngBitmap Layer 6PNG  IHDRaIDAT8_HSqǿ]sM .4TO_QR=KoT0M)jQh(̭nN6wv{ݡkJ~{;?JUUK抆DB0Wf[¼QM$6 Àc==CW3|դ7HJ D~ x(,j6w9,T(Zhc}CyAp}?MH*kR3NCGA+.Q]/X=y@:-3򮄌* Z54Mq^[kNb@nQ_?B I **Cp_U)p%חR]gg)A,Cn!kJA3%h&GP2X&``ѫOs)$F zOw,(B`6?Zᱧs [Z[M-M5wxڪќICQrX4,~z?i"WҞa$IZ#6_S]E((Ce9*ԚD F8t:{{{C R#Kɹ Tr*. "j,H&A [!Mem?Rl"kFIIENDB`DU!) 59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9public.pngBitmap Layer 5PNG  IHDRaFIDAT8R˫Ra<@'-$D1IŐЅhQj]hDJ+#V.\ n.*D.Dt"+W{+ }09f({>2oYYVr?_pHp8N+PʥR)YVd2^wU*s܇& {JE55a˲}ѸJ&;f{NlF$Ø\.'Z29>?Eh4CG=AVn%;b8 /j Fb<@f Dnj8n4- g "Jl6*/ >!HH۝R7. >s::q} BJ8J$162Ll01@d:LF 6L&C"4ZM`G,G\>V*p)qzjhH\]6 f/n!ECD"-? h+F[$X|?z;\/d_ƍ2n1J; 5?|ݍ.9[*~lp6uj=,W7,7/& +$zGo@C/6l6R4L~``!BGq"X7eo5m-y-@(FݣG"]TU}dR순t2 rш7i.Ma?V|>;Ifv@W(b bПMFQ_-A"0X}MB{$S̪C56IENDB`=U!)|232166a8-279b-42bb-8ff6-2f8a5d822dd8public.pngBitmap Layer 7PNG  IHDRaIDAT8_HSQv[)S[˩R#R $z@E"*|(zׄ(z0 |(m/3%7"f1Ånm6zps={~X3J{;9hlnt"ɟDzC:&aυ#uUE5v zDMi0,+oz_Q )8 ^;rrRAA"`9?[:ktU)M Kt " V@ R}vuk61 (K4T.UҠ* (w:xtcށͽA,T ]+c~S+`AdF#r5@asǵriH,jibF߂eXCB⽏PP(Oq.C*C @;F3,p9uF#bazBi'xڱ]T+د0vmq~h<cG L$XGJ:E}Pʱ8bۻ Ø BADK)k} ǝ0l[bLpxk9s{޻N@ @ d&JD"ٵTVT@stQ'Vd2& ]!bжҠbbh"`^5~XYÓ߫IENDB` .d U!+Hc56fb336-1da2-4c0d-a015-b62463130206 public.pngBitmap Layer 11PNG  IHDRaeIDAT8R=@D6VNK!+ Qd )6W !A `crvINHvͼoC˲(nQDܝHP`4}1 BEQ`݂yc8n&L&m^C$Ije5#_[Xtb&ɲz=_l28oH.. ~ѺG)c1<ɻpp(ț͆ "4_0iZ,wQXNG+1#.u7Z*^IENDB`! U!+B55a0792c-26db-4399-8e27-d694aa7523ebpublic.pngBitmap Layer 10PNG  IHDR&N:"IDAT(kAvi[˚l`!"fM]k΂%x`ozTh⒓jTbPhHmk8A}xofP`me#D}#oCY 3LONn iw-{/Z֎~rjjh4.(Brqg@=C_l{lIENDB`OU!) 027ce2df-336a-46b9-9ca7-4ca3a8739fc8public.pngBitmap Layer 9PNG  IHDR&N:QIDAT(KN@ =/Md`Q P d !68= = 9FDG,l;'Oo^5yY%c hit G 暒Ru< (WRsvR0dR!NEUUYY8x v!.C9u t*IENDB` ++ U!+(b0971a21-3b13-464e-8e1b-816466d607fd public.pngBitmap Layer 15PNG  IHDR Vu\IDAT(=KPkEjbV@;T!?!YE"66y%9=﹟mNAoP` H:(4>g }* $^O5$0 AKX0VS\jLUUEQq0ƴhVE4Tcx Z3Ý87y?׍P.M3Ӗ)suo$yͼ[IjTgX<',˞8 jHS ZW8(R[(h/;<p j_ĉ}HIENDB`| U!+xe0b2fd06-e1e7-4b6b-9c0d-a68fdc72f781 public.pngBitmap Layer 14PNG  IHDR }IDAT8?/ao]'B(4| NTD+ohTW8۝Y^,乙wfݽ7 R M[ -C/Z":/#omrEz!x uب(Y" 3I hC줱=x2#t&QWhAkdyš@z)`@:ӏU{|GSA.Y.c&֤0 אʪkK(!kH(-Y78̓u1UAM 0ڲ=OsS=xs^`>dX?!/] G) wH6LL15+bI~xR0`'5IENDB`< U!+x222d613a-4fd0-4288-8490-1a5533f0b7e4 public.pngBitmap Layer 12PNG  IHDRV%IDAT(RJQ+ w](ʠB%+a$: * '(zh_D0}2ȭ.N,fA sfosR,VWed\]^TgAevZ%h$:g*ep"F"3I*ɤ^rElijf H ۗ"_ #C~TAL'켝{z~C2J ւ]km/ DNp8?'>4Msz^_dm󓍱Q&ˁD*&S` >ddX $˜T ?& He 9k9|쫒+A?U @IENDB` 8TU!)*26cb5395-5081-4cff-bc0f-8e66c301e97apublic.pngBitmap Layer 4PNG  IHDRaIDAT8SKOQ=(-}%!*`hT@vB\$41bܠq!?pa҄؊T5Q*BcLi;H!%w|~;[ *A1eN˾)QUW~N){q}h6B bR\(e 0!8לּv'b TBi&̿uɳΩSvNKaADX"ʰ8p:q"E D$8 6=02!8[Yng`@PDb"P"br Lm#"\|wYq.A"㤄n>q(P2H`TG@PjוsWo4[IatbK[#i+. }> D)`8 %JʠV U#y4\vxsMž \KذX3B0v9ހo]+WsgJ&| U." GLD벭+?$^oea.q BFiQA aY9V+F?l|߶pIظh'#mEECd 9NfQzC$>ծK@`8`%dKKKLnOOOFFFgғSc_HoTI[__RWWwL{ s7c?_] Z9 CwNh?u1 IENDB`EU!+ fbd90bb7-7571-4bc1-b716-e9f3f84ce80e public.pngBitmap Layer 13PNG  IHDRaFIDAT8kQLIX*(Fpт EwRPlOpv%"p' . T\ՅEBlcZ4I;Lq7s(k-Ȏk&0ʐv`M81a6&le׈%/^ٍ"[BPW59Rg4Fr|nӗ!+ Jnqy%W, >PyuG-Q 3+L]Q7L c{F)R㳪?J|d!orM>7boL|jg&Q)M aWI:x,꿻k>g9ZLRQ\dD_RkX;ϳ.$3EnmJuk ;e>!_M:It۴YѢ58|7YxS!O'1|vxYPQmTP04"Z!TmAN(ĬTX^؈<֔EFEQ؎OԴ=E#쵱bKNz~i3<4rErd7]WER<MOa<IENDB` "d+zIX&Gh/~M\*7"U232166a8-279b-42bb-8ff6-2f8a5d822dd8opacity?7!U232166a8-279b-42bb-8ff6-2f8a5d822dd8blendModenormal/ U232166a8-279b-42bb-8ff6-2f8a5d822dd8locked0U232166a8-279b-42bb-8ff6-2f8a5d822dd8visibleAU5ffd6fcaf-bc6e-43cb-b299-fc1e4cae2775frame{{0, 184}, {16, 16}}:U/ffd6fcaf-bc6e-43cb-b299-fc1e4cae2775acorn.changeCount 7Uffd6fcaf-bc6e-43cb-b299-fc1e4cae2775opacity?7Uffd6fcaf-bc6e-43cb-b299-fc1e4cae2775blendModenormal/Uffd6fcaf-bc6e-43cb-b299-fc1e4cae2775locked0Uffd6fcaf-bc6e-43cb-b299-fc1e4cae2775visibleAU559f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9frame{{0, 204}, {16, 16}}:U/59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9acorn.changeCount 7U59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9opacity?7U59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9blendModenormal/U59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9locked0U59f1be6d-db9a-4df3-b6c4-bfe5ddbfd5b9visibleAU5d360b142-fd54-45af-b596-f3a2ab498454frame{{0, 244}, {16, 16}}:U/d360b142-fd54-45af-b596-f3a2ab498454acorn.changeCount 7Ud360b142-fd54-45af-b596-f3a2ab498454opacity?7Ud360b142-fd54-45af-b596-f3a2ab498454blendModenormal/Ud360b142-fd54-45af-b596-f3a2ab498454locked0 Ud360b142-fd54-45af-b596-f3a2ab498454visibleA U5728e0c92-38cc-4bb5-9c5f-16a4b253938fframe{{0, 265}, {16, 15}}: U/728e0c92-38cc-4bb5-9c5f-16a4b253938facorn.changeCount 7 U728e0c92-38cc-4bb5-9c5f-16a4b253938fopacity?7 U728e0c92-38cc-4bb5-9c5f-16a4b253938fblendModenormal/U728e0c92-38cc-4bb5-9c5f-16a4b253938flocked0U728e0c92-38cc-4bb5-9c5f-16a4b253938fvisibleAU5cafcee28-d954-4ef0-b057-7eab4a0dd51bframe{{0, 41}, {40, 239}}:U/cafcee28-d954-4ef0-b057-7eab4a0dd51bacorn.changeCount7Ucafcee28-d954-4ef0-b057-7eab4a0dd51bopacity?7Ucafcee28-d954-4ef0-b057-7eab4a0dd51bblendModenormal/Ucafcee28-d954-4ef0-b057-7eab4a0dd51blocked0Ucafcee28-d954-4ef0-b057-7eab4a0dd51bvisible "|Op-Xv=S"t2]!|/DUe0b2fd06-e1e7-4b6b-9c0d-a68fdc72f781locked0CUe0b2fd06-e1e7-4b6b-9c0d-a68fdc72f781visible@BU3222d613a-4fd0-4288-8490-1a5533f0b7e4frame{{0, 44}, {15, 16}}:AU/222d613a-4fd0-4288-8490-1a5533f0b7e4acorn.changeCount 7@U222d613a-4fd0-4288-8490-1a5533f0b7e4opacity?7?U222d613a-4fd0-4288-8490-1a5533f0b7e4blendModenormal/>U222d613a-4fd0-4288-8490-1a5533f0b7e4locked0=U222d613a-4fd0-4288-8490-1a5533f0b7e4visible@<U3c56fb336-1da2-4c0d-a015-b62463130206frame{{0, 86}, {16, 16}}:;U/c56fb336-1da2-4c0d-a015-b62463130206acorn.changeCount7:Uc56fb336-1da2-4c0d-a015-b62463130206opacity?79Uc56fb336-1da2-4c0d-a015-b62463130206blendModenormal/8Uc56fb336-1da2-4c0d-a015-b62463130206locked07Uc56fb336-1da2-4c0d-a015-b62463130206visibleA6U555a0792c-26db-4399-8e27-d694aa7523ebframe{{0, 104}, {14, 16}}:5U/55a0792c-26db-4399-8e27-d694aa7523ebacorn.changeCount 74U55a0792c-26db-4399-8e27-d694aa7523ebopacity?73U55a0792c-26db-4399-8e27-d694aa7523ebblendModenormal/2U55a0792c-26db-4399-8e27-d694aa7523eblocked01U55a0792c-26db-4399-8e27-d694aa7523ebvisibleA0U5027ce2df-336a-46b9-9ca7-4ca3a8739fc8frame{{0, 126}, {14, 16}}:/U/027ce2df-336a-46b9-9ca7-4ca3a8739fc8acorn.changeCount 7.U027ce2df-336a-46b9-9ca7-4ca3a8739fc8opacity?7-U027ce2df-336a-46b9-9ca7-4ca3a8739fc8blendModenormal/,U027ce2df-336a-46b9-9ca7-4ca3a8739fc8locked0+U027ce2df-336a-46b9-9ca7-4ca3a8739fc8visibleA*U58411f6c0-b34e-460b-85f9-37fbb482a61bframe{{0, 145}, {16, 16}}:)U/8411f6c0-b34e-460b-85f9-37fbb482a61bacorn.changeCount 7(U8411f6c0-b34e-460b-85f9-37fbb482a61bopacity?7'U8411f6c0-b34e-460b-85f9-37fbb482a61bblendModenormal/&U8411f6c0-b34e-460b-85f9-37fbb482a61blocked0%U8411f6c0-b34e-460b-85f9-37fbb482a61bvisibleA$U5232166a8-279b-42bb-8ff6-2f8a5d822dd8frame{{0, 165}, {16, 16}}:#U/232166a8-279b-42bb-8ff6-2f8a5d822dd8acorn.changeCount Rt;["k9ZAZU526cb5395-5081-4cff-bc0f-8e66c301e97aframe{{1, 224}, {16, 16}}:YU/26cb5395-5081-4cff-bc0f-8e66c301e97aacorn.changeCount 7XU26cb5395-5081-4cff-bc0f-8e66c301e97aopacity?7WU26cb5395-5081-4cff-bc0f-8e66c301e97ablendModenormal/VU26cb5395-5081-4cff-bc0f-8e66c301e97alocked0UU26cb5395-5081-4cff-bc0f-8e66c301e97avisible@TU3fbd90bb7-7571-4bc1-b716-e9f3f84ce80eframe{{0, 66}, {16, 16}}:SU/fbd90bb7-7571-4bc1-b716-e9f3f84ce80eacorn.changeCount 7RUfbd90bb7-7571-4bc1-b716-e9f3f84ce80eopacity?7QUfbd90bb7-7571-4bc1-b716-e9f3f84ce80eblendModenormal/PUfbd90bb7-7571-4bc1-b716-e9f3f84ce80elocked0OUfbd90bb7-7571-4bc1-b716-e9f3f84ce80evisible?NU1b0971a21-3b13-464e-8e1b-816466d607fdframe{{0, 7}, {12, 12}}:MU/b0971a21-3b13-464e-8e1b-816466d607fdacorn.changeCount7LUb0971a21-3b13-464e-8e1b-816466d607fdopacity?7KUb0971a21-3b13-464e-8e1b-816466d607fdblendModenormal/JUb0971a21-3b13-464e-8e1b-816466d607fdlocked0IUb0971a21-3b13-464e-8e1b-816466d607fdvisible@HU3e0b2fd06-e1e7-4b6b-9c0d-a68fdc72f781frame{{0, 20}, {20, 20}}:GU/e0b2fd06-e1e7-4b6b-9c0d-a68fdc72f781acorn.changeCount7FUe0b2fd06-e1e7-4b6b-9c0d-a68fdc72f781opacity?7EUe0b2fd06-e1e7-4b6b-9c0d-a68fdc72f781blendModenormalApp-Alice-0.19/share/static/alice-dark.css0000644000175000017500000004262111437015710017243 0ustar leedoleedodiv.window.active { display: block; } div.window { display: none; } input[type="hidden"] { display: none; } body { margin: 0; padding: 0; color: white; } ul#tabs { z-index: 20; } ul#tabs li:hover { cursor: default; } ul#controls { z-index: 30; background: #444444; } body.blurred ul#controls { background: #333333; } ul#controls li { position: relative; } select.select_overlay { display: block; position: absolute; top: 0px; left: 0px; bottom: 0px; width: 22px; border: none; padding: 0; margin: 0; opacity: 0; font-size: 12px; } select#tab_overflow_overlay option.unread { font-weight: bold; } li#tab_overflow_button { background: url(image/sprites.png) 3px -174px no-repeat; } li#tab_overflow_button.unread { background: url(image/sprites.png) 3px -194px no-repeat; } li#config_button { background: url(image/sprites.png) 4px -216px no-repeat; } body { font-family: "Lucida Grande", Helvetica, sans-serif; text-rendering: optimizeLegibility; background: #222222; } a:link, a:visited, a:hover, a:active { color: white; } a:hover { text-shadow: rgba(255, 255, 255, 0.8) 0 1px 1px; } div.window { position: fixed; top: 0px; left: 0px; right: 0px; bottom: 23px; } div.window table { table-layout: fixed; width: 100%; height: 100%; padding: 0; } tr.input td { height: 20px; background: #333333; border-bottom: 1px solid #222222; border-top: 1px solid #444444; } body.blurred tr.input td { border-bottom: 1px solid #111111; } tr.input form { display: block; margin: 0; padding: 0; height: auto; line-height: 0px; } tr.input div { padding: 0 3px; background: black; margin: 3px 0; position: relative; } tr.input div.editor, tr.input textarea { line-height: 1.2em; min-height: 1.2em; width: 100%; padding: 3px 0; border: none; margin: 0; outline: 0 none; font-size: 12px; overflow: hidden; resize: none; font-family: "Lucida Grande", Helvetica, sans-serif; } div.editor { white-space: pre-wrap; -khtml-nbsp-mode: space; } tr.input div.editor_toolbar { overflow: hidden; position: absolute; right: -170px; padding-right: 8px; top: -3px; width: 180px; height: 20px; -webkit-border-radius: 3px 0 0 3px; -moz-border-radius: 3px 0 0 3px; border-radius: 3px 0 0 3px; background: url(image/sprites.png) 0px -240px no-repeat black; z-index: 902; opacity: 0.2; -webkit-transition-property: right; -webkit-transition-duration: 0.3s; } tr.input div.editor_toolbar button { visibility: hidden; display: block; float: left; color: #222222; line-height: 10px; font-size: 11px; margin: 0; padding: 4px 3px 5px 3px; text-align: center; background: none; cursor: pointer; border: none; } tr.input div.editor_toolbar button:hover { color: black; } tr.input div.editor_toolbar button.selected { color: yellow; } .bold { font-weight: bold; } .italic { font-style: italic; } .underline { text-decoration: underline; } tr.input div.editor_toolbar:hover { opacity: 0.4; cursor: pointer; } tr.input div.editor_toolbar.visible:hover { opacity: 1; cursor: inherit; } tr.input div.editor_toolbar.visible { background-color: rgba(255, 255, 255, 0.6); background-image: none; right: -4px; -webkit-transition-property: right; -webkit-transition-duration: 0.3s; opacity: 1; } tr.input div.editor_toolbar.visible button { visibility: visible; } div.editor div, div.editor p, div.editor h1, div.editor h2, div.editor h3, div.editor h4, div.editor h5, div.editor span { font-size: 1em !important; margin: 0 !important; padding: 0 !important; } div.editor img { display: none !important; } tr.input input.send { display: none; } div#tab_container { position: fixed; bottom: 0px; left: 0px; right: 0px; height: 23px; background: #444444; } body.blurred div#tab_container { background: #333333; } ul#tabs { list-style: none; margin: 0; padding: 0; font-size: 11px; position: relative; float: left; margin-right: 62px; padding-left: 3px; } body.blurred ul#tabs li { background: #333333; color: #cccccc; } ul#tabs li { float: left; margin: 0; margin-left: -1px; color: white; background: #444444; } body.blurred ul#tabs li:first-child div.hit_area { border-left: 1px solid #333333; } ul#tabs li:first-child div.hit_area { border-left: 1px solid #444444; } ul#tabs li div.hit_area { border: 1px solid #222222; border-bottom: none; border-top: none; float: left; height: 14px; padding: 2px 10px 8px 4px; } body.blurred ul#tabs li div.hit_area { border: 1px solid #111111; border-bottom: none; border-top: none; } body.blurred ul#tabs li.active div.hit_area { border: 1px solid #111111; border-top: none; } ul#tabs li.active div.hit_area { border: 1px solid #222222; border-top: none; background: #333333; height: 11px; padding-top: 3px; margin-top: -1px; } ul#tabs li.active { height: 23px; z-index: 21; } ul#tabs li.active:hover div.hit_area { background: #333333; } ul#tabs li:hover div.hit_area { background: #3a3a3a; } ul#controls { position: fixed; bottom: 0px; right: 0px; height: 23px; margin: 0; padding: 0 18px 0 0; list-style: none; } ul#controls li { float: right; margin: 0; padding: 0; height: 16px; width: 16px; padding: 4px 3px 3px 3px; cursor: pointer; } ul#controls li:hover { background-color: #3a3a3a; } tr.topic td { width: 100%; background: #3a3a3a; font-size: 11px; text-align: center; border-top: 1px solid #666666; border-bottom: 1px solid black; padding: 1px 0; height: 1.2em; } tr.topic td div.topic { height: 1.2em; overflow: hidden; } tr.topic a { text-decoration: none; } div.message_wrap { height: 100%; overflow-x: hidden; overflow-y: auto; background: #222222; } ul.messages { list-style: none; margin: 0; padding: 0; overflow: hidden; margin: 3px 0; } ul.messages > li { font-size: 13px; display: block; position: relative; background-color: #111111; } ul.messages > li:not(.consecutive) { clear: both; } ul.messages li.highlight { border-right: 5px solid #eb2222; } .info ul.messages li.highlight, ul.messages li.self { background: #111111; border-right: none; } .info ul.messages li.highlight div.msg, ul.messages li.self div.msg { background: #333333; } ul.messages li div.msg { background: #222222; } ul.messages li.consecutive div.left { display: none; } div.left { float: left; width: 95px; font-weight: bold; text-align: right; padding: 4px 0; padding-right: 4px; padding-bottom: 0; text-overflow: ellipsis; position: relative; } li.event a, li.event { color: #cccccc; } li.event div.left { padding-top: 5px; } li.event div.msg { border-top: none; } ul.messages li.avatar div.left { padding-bottom: 3px; padding-top: 3px; } li.event div.left { padding: 5px 0; width: 95px; } li.event div.left span.timestamp { opacity: 0; -webkit-transition: opacity 0.1s ease-in-out; } ul.messages li.avatar:not(.consecutive) { z-index: 900; } div.left img { border: 1px solid black; display: block; max-width: 32px; max-height: 32px; float: right; } div.left span.nickhint { position: absolute; z-index: 901; right: 5px; top: 4px; padding: 0 2px; min-width: 28px; background: rgba(0, 0, 0, 0.5); color: #fff; text-shadow: black 0 1px 1px; opacity: 0; -webkit-transition: opacity 0.5s ease-in-out; } div.timehint { position: absolute; font-size: 10px; right: 0px; top: 1px; padding: 6px 4px 2px 4px; color: #cccccc; opacity: 0; text-shadow: black 0 0 3px; background: #222222; } .info ul.messages li.highlight div.timehint, li.self div.timehint { background: #333333; } div.msg { margin-left: 100px; padding: 3px 5px; word-wrap: break-word; -khtml-line-break: after-white-space; -khtml-nbsp-mode: space; border-top: 1px solid #111111; border-left: 1px solid black; } li.event div.msg { padding: 5px; } li.monospace div.msg { padding: 6px 5px; } li.event + li.message div.msg { border-top: 1px solid transparent; } ul.messages li:first-child div.msg { border-top: 1px solid transparent; } /* so the last avatar doesn't hang off the edge */ ul.messages li.avatar:not(.consecutive) div.msg { min-height: 35px; } ul.messages li.monospace { min-height: 0px; } ul.messages li.message:last-child { border-bottom: 1px solid #111111; } ul.messages li.consecutive div.msg { border-top: none; } ul.messages li + li.consecutive div.msg { padding-top: 0px; } div.msg a { word-break: break-all; } div.msg img { display: block; border: none; } ul.messages li.monospace + li.monospace.consecutive { margin-top: -5px; } ul.messages li.monospace div.msg { font-family: Monaco, monospace; font-size: 10px; line-height: 12px; word-wrap: normal; white-space: pre-wrap; } li.monospace div.msg span[style*="bold"] { letter-spacing: -1px; } ul.messages li.event { font-size: 11px; background: #333333; border-bottom: 1px solid #111111; } ul.messages li.event.notice { background: #eb2222; color: #fff; } ul.messages li.event.notice a { color: #fffc00; text-decoration: none; } ul.messages li.event.notice a:hover { text-shadow: none; } ul.messages li.event.happynotice { background: #3fc842; color: #fff; } ul.messages li.event div.msg { background: none; border-left: none; } ul.messages li.event div.left { font-weight: normal; } ul.messages li.announce div.msg { color: #777; white-space: pre-wrap; } ul.messages li.announce div.msg ul.avatar_grid { margin: 0; padding: 0; list-style: none; width: 100%; overflow: hidden; word-wrap: normal; white-space: normal; } ul.messages li.announce div.msg ul.avatar_grid li { width: 50px; height: 50px; float: left; margin: 0; padding: 0; text-align: center; clear: none; display: block; overflow: hidden; font-size: 10px; text-overflow: ellipsis; } ul.messages li.announce div.msg ul.avatar_grid li div.gridimg { width: 32px; height: 32px; margin: 0 auto; } ul.messages li.announce div.msg ul.avatar_grid li img { max-width: 32px; max-height: 32px; margin: 0 auto; padding: 0; position: relative; display: block; } div.msg img.audio { cursor: pointer; display: inline; padding: 0 4px; width: 16px; height: 16px; margin-bottom: -3px; } a.nick { color: white; text-decoration: none; } div.tab_button { width: 16px; height: 16px; float: left; margin-right: 6px; } li.channel_tab div.tab_button { background: url(image/sprites.png) 0px -80px no-repeat; } li.privmsg_tab div.tab_button { background: url(image/sprites.png) 0px -120px no-repeat; } li.info_tab div.tab_button { background: url(image/sprites.png) 0px 0px no-repeat; } ul#tabs li.active div.tab_button:hover { background: url(image/sprites.png) 1px -260px no-repeat; } ul#tabs li.unread.channel_tab div.tab_button { background: url(image/sprites.png) 0px -20px no-repeat; } ul#tabs li.unread.privmsg_tab div.tab_button { background: url(image/sprites.png) 0 -100px no-repeat; } ul#tabs li.highlight.channel_tab div.tab_button { background: url(image/sprites.png) 0 -40px no-repeat; } ul#tabs li.disabled.channel_tab div.tab_button { background: url(image/sprites.png) 0 -60px no-repeat; } div#logsearch { background: #eee; border-bottom: 1px solid #999; position: fixed; top: 0px; left: 0px; right: 0px; height: 28px; padding: 5px 0 0 0; } div#logsearch label { font-size: 0.7em; padding-right: 2px; padding-left: 10px; color: #666; } div#logsearch form { display: block; margin: 0; } ul#logresults { list-style: none; margin: 0; padding: 0; position: absolute; top: 34px; bottom: 0px; left: 0px; right: 0px; overflow: auto; } ul#logresults li { border-bottom: 1px solid #eee; padding: 0; font-size: 0.8em; cursor: pointer; } ul#logresults div.focus:hover { background: #eee; } ul#logresults li div.focus { background: #fff; padding: 5px 10px; } ul#logresults li.context div.focus:hover { background: #fff; } ul#logresults li.context ul { margin: 0; padding: 5px 10px; list-style: none; } ul#logresults li.context ul li { border: none; margin: 0; padding: 2px 0; } ul#logresults li.context { border-top: 1px solid #999; border-bottom: 1px solid #999; background: url(image/shadow-bottom.png) bottom left repeat-x, url(image/shadow-top.png) top left repeat-x #eeeeee; } ul#logresults span.metadata { font-size: 0.7em; color: #666; } div#help { position: absolute; z-index: 999; background: rgba(0, 0, 0, 0.8); color: #fff; left: 20px; right: 20px; top: 25px; bottom: 60px; padding: 10px 20px; -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; text-shadow: 0 0 3px rgba(0, 0, 0, 0.9); } div#help div#helpclose { position: absolute; top: 17px; right: 20px; color: rgba(255, 255, 255, 0.75); font-size: 9px; letter-spacing: 1px; text-shadow: none; text-transform: uppercase; } div#help div#helpclose:hover { cursor: pointer; color: #fff; } div#help div#topics { position: absolute; top: 34px; bottom: 15px; left: 20px; right: 20px; overflow-y: auto; } div#help h1 { margin: 0; font-size: 20px; border-bottom: 1px solid #999; font-weight: normal; } div#help dl { width: 48%; font-size: 12px; margin-top: 0; padding-top: 10px; float: left; display: block; border-right: 1px solid #666; padding-right: 2%; } div#help dl#shortcuts { border-right: none; float: right; padding-right: 0; padding-left: 2%; } div#help h2 { color: yellow; margin-top: 0; font-weight: normal; } div#help dt { font-weight: bold; } div#help dd { font-weight: normal; margin: 0; margin-bottom: 10px; } div.color_picker { position: absolute; right: 25px; bottom: 27px; width: 80px; border-bottom: none; margin: 0; padding: 0; z-index: 903; } div.color_picker span { cursor: pointer; } div.color_picker div.colors { clear: both; } div.color_picker div.colors span { float: left; display: block; width: 20px; height: 20px; margin: 0; -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6); -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6); box-shadow: 0 0 3px rgba(0, 0, 0, 0.6); } div.color_picker div.toggle { height: 18px; } div.color_picker div.toggle span.blank { float: right; color: red; font-weight: bold; text-align: center; width: 20px; line-height: 9px; } div.color_picker div.toggle span#fg, div.color_picker div.toggle span#bg { float: left; font-size: 9px; background: none; -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; color: white; text-align: center; padding: 2px 5px; } div.color_picker div.toggle span#fg.active, div.color_picker div.toggle span#bg.active { background: rgba(255, 255, 255, 0.6); color: black; } @media screen and (max-device-width: 480px) { div.left { width: 40px !important; } div.msg { margin-left: 52px !important; } div.topic { height: 1.2em; overflow: hidden; } tr.input div { margin-right: 55px; background: none; } tr.input input.send { display: block; position: absolute; bottom: 2px; right: 0px; z-index: 999; } div.msg.monospace, div.msg.announce { line-height: 11px; } } div.config { position: absolute; bottom: 55px; right: 20px; background: #efefef; padding: 10px 10px; z-index: 999; border: 10px solid rgba(0, 0, 0, 0.5); -webkit-border-radius: 10px; -moz-border-radius: 5px; border-radius: 5px; } div.config div.buttons { clear: both; } div.config div.buttons button { float: right; } div.config div.setting { margin-top: 0.5em; margin-left: 160px; display: none; } div.config div.setting.active { display: block; } div.config div.field { float: left; margin-bottom: 10px; margin-right: 8px; } div.config div.field.clear { clear: both; } div.config label, div.config input { display: block; font-size: 0.8em; } div.config span { font-size: 0.8em; } div.config select { width: 135px; font-size: 0.8em; outline: 0 none; } div.config[multiple] { height: 100px; } div.config div#server_controls { position: absolute; bottom: 18px; left: 6px; } div.config div.controls { clear: both; font-size: 0.7em; } div.config div.controls a { text-decoration: none; margin: 0 5px 0 0; } div.config div.controls a:hover { text-shadow: none; text-decoration: underline; } div.config label { color: #666; } div.config input[type="checkbox"] { float: left; margin-right: 4px; } div.config ul#connections { list-style: none; position: absolute; top: 5px; bottom: 32px; left: 5px; margin: 0; padding: 0px; width: 150px; background: #fff; border-right: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.config ul#connections li.header { background: #efefef; text-transform: uppercase; color: #777; letter-spacing: 1px; font-size: 0.7em; cursor: default; } div.config ul#connections li.header:hover { background: #efefef; } div.config ul#connections li { padding: 5px 8px; font-size: 0.9em; cursor: pointer; border: 1px solid #fff; border-bottom: none; } div.config ul#connections li:hover { background: #ffffcc; } div.config ul#connections li.disconnected.active, div.config ul#connections li.connected.active { background: #3875d7; color: #fff; } div.config ul#connections li.disconnected { color: #999; } div.config ul#connections li.connected { color: #222; } body.config { background: #efefef; } App-Alice-0.19/share/static/favicon.ico0000644000175000017500000000217611366325415016666 0ustar leedoleedo h(  1>cRX1M风d`Y%5ꤋwgy薙^.oOz;D&TYhNeH ^^IY{Qp< Kntgf琎Ꜭsjm 1{8 .J)F 쩳Z0App-Alice-0.19/share/static/alice.js0000644000175000017500000120431511437017366016162 0ustar leedoleedo/* Prototype JavaScript framework, version 1.6.1 * (c) 2005-2009 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ * *--------------------------------------------------------------------------*/ var Prototype = { Version: '1.6.1', Browser: (function(){ var ua = navigator.userAgent; var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; return { IE: !!window.attachEvent && !isOpera, Opera: isOpera, WebKit: ua.indexOf('AppleWebKit/') > -1, Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, MobileSafari: /Apple.*Mobile/.test(ua) } })(), BrowserFeatures: { XPath: !!document.evaluate, SelectorsAPI: !!document.querySelector, ElementExtensions: (function() { var constructor = window.Element || window.HTMLElement; return !!(constructor && constructor.prototype); })(), SpecificElementExtensions: (function() { if (typeof window.HTMLDivElement !== 'undefined') return true; var div = document.createElement('div'), form = document.createElement('form'), isSupported = false; if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { isSupported = true; } div = form = null; return isSupported; })() }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, emptyFunction: function() { }, K: function(x) { return x } }; if (Prototype.Browser.MobileSafari) Prototype.BrowserFeatures.SpecificElementExtensions = false; var Abstract = { }; var Try = { these: function() { var returnValue; for (var i = 0, length = arguments.length; i < length; i++) { var lambda = arguments[i]; try { returnValue = lambda(); break; } catch (e) { } } return returnValue; } }; /* Based on Alex Arnell's inheritance implementation. */ var Class = (function() { var IS_DONTENUM_BUGGY = (function(){ for (var p in { toString: 1 }) { if (p === 'toString') return false; } return true; })(); function subclass() {}; function create() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); function klass() { this.initialize.apply(this, arguments); } Object.extend(klass, Class.Methods); klass.superclass = parent; klass.subclasses = []; if (parent) { subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } for (var i = 0, length = properties.length; i < length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; return klass; } function addMethods(source) { var ancestor = this.superclass && this.superclass.prototype, properties = Object.keys(source); if (IS_DONTENUM_BUGGY) { if (source.toString != Object.prototype.toString) properties.push("toString"); if (source.valueOf != Object.prototype.valueOf) properties.push("valueOf"); } for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && value.argumentNames()[0] == "$super") { var method = value; value = (function(m) { return function() { return ancestor[m].apply(this, arguments); }; })(property).wrap(method); value.valueOf = method.valueOf.bind(method); value.toString = method.toString.bind(method); } this.prototype[property] = value; } return this; } return { create: create, Methods: { addMethods: addMethods } }; })(); (function() { var _toString = Object.prototype.toString, NULL_TYPE = 'Null', UNDEFINED_TYPE = 'Undefined', BOOLEAN_TYPE = 'Boolean', NUMBER_TYPE = 'Number', STRING_TYPE = 'String', OBJECT_TYPE = 'Object', BOOLEAN_CLASS = '[object Boolean]', NUMBER_CLASS = '[object Number]', STRING_CLASS = '[object String]', ARRAY_CLASS = '[object Array]', NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && typeof JSON.stringify === 'function' && JSON.stringify(0) === '0' && typeof JSON.stringify(Prototype.K) === 'undefined'; function Type(o) { switch(o) { case null: return NULL_TYPE; case (void 0): return UNDEFINED_TYPE; } var type = typeof o; switch(type) { case 'boolean': return BOOLEAN_TYPE; case 'number': return NUMBER_TYPE; case 'string': return STRING_TYPE; } return OBJECT_TYPE; } function extend(destination, source) { for (var property in source) destination[property] = source[property]; return destination; } function inspect(object) { try { if (isUndefined(object)) return 'undefined'; if (object === null) return 'null'; return object.inspect ? object.inspect() : String(object); } catch (e) { if (e instanceof RangeError) return '...'; throw e; } } function toJSON(value) { return Str('', { '': value }, []); } function Str(key, holder, stack) { var value = holder[key], type = typeof value; if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { value = value.toJSON(key); } var _class = _toString.call(value); switch (_class) { case NUMBER_CLASS: case BOOLEAN_CLASS: case STRING_CLASS: value = value.valueOf(); } switch (value) { case null: return 'null'; case true: return 'true'; case false: return 'false'; } type = typeof value; switch (type) { case 'string': return value.inspect(true); case 'number': return isFinite(value) ? String(value) : 'null'; case 'object': for (var i = 0, length = stack.length; i < length; i++) { if (stack[i] === value) { throw new TypeError(); } } stack.push(value); var partial = []; if (_class === ARRAY_CLASS) { for (var i = 0, length = value.length; i < length; i++) { var str = Str(i, value, stack); partial.push(typeof str === 'undefined' ? 'null' : str); } partial = '[' + partial.join(',') + ']'; } else { var keys = Object.keys(value); for (var i = 0, length = keys.length; i < length; i++) { var key = keys[i], str = Str(key, value, stack); if (typeof str !== "undefined") { partial.push(key.inspect(true)+ ':' + str); } } partial = '{' + partial.join(',') + '}'; } stack.pop(); return partial; } } function stringify(object) { return JSON.stringify(object); } function toQueryString(object) { return $H(object).toQueryString(); } function toHTML(object) { return object && object.toHTML ? object.toHTML() : String.interpret(object); } function keys(object) { if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } var results = []; for (var property in object) { if (object.hasOwnProperty(property)) { results.push(property); } } return results; } function values(object) { var results = []; for (var property in object) results.push(object[property]); return results; } function clone(object) { return extend({ }, object); } function isElement(object) { return !!(object && object.nodeType == 1); } function isArray(object) { return _toString.call(object) === ARRAY_CLASS; } var hasNativeIsArray = (typeof Array.isArray == 'function') && Array.isArray([]) && !Array.isArray({}); if (hasNativeIsArray) { isArray = Array.isArray; } function isHash(object) { return object instanceof Hash; } function isFunction(object) { return typeof object === "function"; } function isString(object) { return _toString.call(object) === STRING_CLASS; } function isNumber(object) { return _toString.call(object) === NUMBER_CLASS; } function isUndefined(object) { return typeof object === "undefined"; } extend(Object, { extend: extend, inspect: inspect, toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, toQueryString: toQueryString, toHTML: toHTML, keys: Object.keys || keys, values: values, clone: clone, isElement: isElement, isArray: isArray, isHash: isHash, isFunction: isFunction, isString: isString, isNumber: isNumber, isUndefined: isUndefined }); })(); Object.extend(Function.prototype, (function() { var slice = Array.prototype.slice; function update(array, args) { var arrayLength = array.length, length = args.length; while (length--) array[arrayLength + length] = args[length]; return array; } function merge(array, args) { array = slice.call(array, 0); return update(array, args); } function argumentNames() { var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; } function bind(context) { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; var __method = this, args = slice.call(arguments, 1); return function() { var a = merge(args, arguments); return __method.apply(context, a); } } function bindAsEventListener(context) { var __method = this, args = slice.call(arguments, 1); return function(event) { var a = update([event || window.event], args); return __method.apply(context, a); } } function curry() { if (!arguments.length) return this; var __method = this, args = slice.call(arguments, 0); return function() { var a = merge(args, arguments); return __method.apply(this, a); } } function delay(timeout) { var __method = this, args = slice.call(arguments, 1); timeout = timeout * 1000; return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); } function defer() { var args = update([0.01], arguments); return this.delay.apply(this, args); } function wrap(wrapper) { var __method = this; return function() { var a = update([__method.bind(this)], arguments); return wrapper.apply(this, a); } } function methodize() { if (this._methodized) return this._methodized; var __method = this; return this._methodized = function() { var a = update([this], arguments); return __method.apply(null, a); }; } return { argumentNames: argumentNames, bind: bind, bindAsEventListener: bindAsEventListener, curry: curry, delay: delay, defer: defer, wrap: wrap, methodize: methodize } })()); (function(proto) { function toISOString() { return this.getUTCFullYear() + '-' + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + this.getUTCDate().toPaddedString(2) + 'T' + this.getUTCHours().toPaddedString(2) + ':' + this.getUTCMinutes().toPaddedString(2) + ':' + this.getUTCSeconds().toPaddedString(2) + 'Z'; } function toJSON() { return this.toISOString(); } if (!proto.toISOString) proto.toISOString = toISOString; if (!proto.toJSON) proto.toJSON = toJSON; })(Date.prototype); RegExp.prototype.match = RegExp.prototype.test; RegExp.escape = function(str) { return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); }; var PeriodicalExecuter = Class.create({ initialize: function(callback, frequency) { this.callback = callback; this.frequency = frequency; this.currentlyExecuting = false; this.registerCallback(); }, registerCallback: function() { this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); }, execute: function() { this.callback(this); }, stop: function() { if (!this.timer) return; clearInterval(this.timer); this.timer = null; }, onTimerEvent: function() { if (!this.currentlyExecuting) { try { this.currentlyExecuting = true; this.execute(); this.currentlyExecuting = false; } catch(e) { this.currentlyExecuting = false; throw e; } } } }); Object.extend(String, { interpret: function(value) { return value == null ? '' : String(value); }, specialChar: { '\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '\\': '\\\\' } }); Object.extend(String.prototype, (function() { var NATIVE_JSON_PARSE_SUPPORT = window.JSON && typeof JSON.parse === 'function' && JSON.parse('{"test": true}').test; function prepareReplacement(replacement) { if (Object.isFunction(replacement)) return replacement; var template = new Template(replacement); return function(match) { return template.evaluate(match) }; } function gsub(pattern, replacement) { var result = '', source = this, match; replacement = prepareReplacement(replacement); if (Object.isString(pattern)) pattern = RegExp.escape(pattern); if (!(pattern.length || pattern.source)) { replacement = replacement(''); return replacement + source.split('').join(replacement) + replacement; } while (source.length > 0) { if (match = source.match(pattern)) { result += source.slice(0, match.index); result += String.interpret(replacement(match)); source = source.slice(match.index + match[0].length); } else { result += source, source = ''; } } return result; } function sub(pattern, replacement, count) { replacement = prepareReplacement(replacement); count = Object.isUndefined(count) ? 1 : count; return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; return replacement(match); }); } function scan(pattern, iterator) { this.gsub(pattern, iterator); return String(this); } function truncate(length, truncation) { length = length || 30; truncation = Object.isUndefined(truncation) ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : String(this); } function strip() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); } function stripTags() { return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); } function stripScripts() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); } function extractScripts() { var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); } function evalScripts() { return this.extractScripts().map(function(script) { return eval(script) }); } function escapeHTML() { return this.replace(/&/g,'&').replace(//g,'>'); } function unescapeHTML() { return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); } function toQueryParams(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { var key = decodeURIComponent(pair.shift()), value = pair.length > 1 ? pair.join('=') : pair[0]; if (value != undefined) value = decodeURIComponent(value); if (key in hash) { if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; hash[key].push(value); } else hash[key] = value; } return hash; }); } function toArray() { return this.split(''); } function succ() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); } function times(count) { return count < 1 ? '' : new Array(count + 1).join(this); } function camelize() { return this.replace(/-+(.)?/g, function(match, chr) { return chr ? chr.toUpperCase() : ''; }); } function capitalize() { return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); } function underscore() { return this.replace(/::/g, '/') .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') .replace(/([a-z\d])([A-Z])/g, '$1_$2') .replace(/-/g, '_') .toLowerCase(); } function dasherize() { return this.replace(/_/g, '-'); } function inspect(useDoubleQuotes) { var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { if (character in String.specialChar) { return String.specialChar[character]; } return '\\u00' + character.charCodeAt().toPaddedString(2, 16); }); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; return "'" + escapedString.replace(/'/g, '\\\'') + "'"; } function unfilterJSON(filter) { return this.replace(filter || Prototype.JSONFilter, '$1'); } function isJSON() { var str = this; if (str.blank()) return false; str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); return (/^[\],:{}\s]*$/).test(str); } function evalJSON(sanitize) { var json = this.unfilterJSON(), cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; if (cx.test(json)) { json = json.replace(cx, function (a) { return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); }); } try { if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); } function parseJSON() { var json = this.unfilterJSON(); return JSON.parse(json); } function include(pattern) { return this.indexOf(pattern) > -1; } function startsWith(pattern) { return this.lastIndexOf(pattern, 0) === 0; } function endsWith(pattern) { var d = this.length - pattern.length; return d >= 0 && this.indexOf(pattern, d) === d; } function empty() { return this == ''; } function blank() { return /^\s*$/.test(this); } function interpolate(object, pattern) { return new Template(this, pattern).evaluate(object); } return { gsub: gsub, sub: sub, scan: scan, truncate: truncate, strip: String.prototype.trim || strip, stripTags: stripTags, stripScripts: stripScripts, extractScripts: extractScripts, evalScripts: evalScripts, escapeHTML: escapeHTML, unescapeHTML: unescapeHTML, toQueryParams: toQueryParams, parseQuery: toQueryParams, toArray: toArray, succ: succ, times: times, camelize: camelize, capitalize: capitalize, underscore: underscore, dasherize: dasherize, inspect: inspect, unfilterJSON: unfilterJSON, isJSON: isJSON, evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, include: include, startsWith: startsWith, endsWith: endsWith, empty: empty, blank: blank, interpolate: interpolate }; })()); var Template = Class.create({ initialize: function(template, pattern) { this.template = template.toString(); this.pattern = pattern || Template.Pattern; }, evaluate: function(object) { if (object && Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements(); return this.template.gsub(this.pattern, function(match) { if (object == null) return (match[1] + ''); var before = match[1] || ''; if (before == '\\') return match[2]; var ctx = object, expr = match[3], pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; match = pattern.exec(expr); if (match == null) return before; while (match != null) { var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; ctx = ctx[comp]; if (null == ctx || '' == match[3]) break; expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); match = pattern.exec(expr); } return before + String.interpret(ctx); }); } }); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; var $break = { }; var Enumerable = (function() { function each(iterator, context) { var index = 0; try { this._each(function(value) { iterator.call(context, value, index++); }); } catch (e) { if (e != $break) throw e; } return this; } function eachSlice(number, iterator, context) { var index = -number, slices = [], array = this.toArray(); if (number < 1) return array; while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); } function all(iterator, context) { iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { result = result && !!iterator.call(context, value, index); if (!result) throw $break; }); return result; } function any(iterator, context) { iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { if (result = !!iterator.call(context, value, index)) throw $break; }); return result; } function collect(iterator, context) { iterator = iterator || Prototype.K; var results = []; this.each(function(value, index) { results.push(iterator.call(context, value, index)); }); return results; } function detect(iterator, context) { var result; this.each(function(value, index) { if (iterator.call(context, value, index)) { result = value; throw $break; } }); return result; } function findAll(iterator, context) { var results = []; this.each(function(value, index) { if (iterator.call(context, value, index)) results.push(value); }); return results; } function grep(filter, iterator, context) { iterator = iterator || Prototype.K; var results = []; if (Object.isString(filter)) filter = new RegExp(RegExp.escape(filter)); this.each(function(value, index) { if (filter.match(value)) results.push(iterator.call(context, value, index)); }); return results; } function include(object) { if (Object.isFunction(this.indexOf)) if (this.indexOf(object) != -1) return true; var found = false; this.each(function(value) { if (value == object) { found = true; throw $break; } }); return found; } function inGroupsOf(number, fillWith) { fillWith = Object.isUndefined(fillWith) ? null : fillWith; return this.eachSlice(number, function(slice) { while(slice.length < number) slice.push(fillWith); return slice; }); } function inject(memo, iterator, context) { this.each(function(value, index) { memo = iterator.call(context, memo, value, index); }); return memo; } function invoke(method) { var args = $A(arguments).slice(1); return this.map(function(value) { return value[method].apply(value, args); }); } function max(iterator, context) { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { value = iterator.call(context, value, index); if (result == null || value >= result) result = value; }); return result; } function min(iterator, context) { iterator = iterator || Prototype.K; var result; this.each(function(value, index) { value = iterator.call(context, value, index); if (result == null || value < result) result = value; }); return result; } function partition(iterator, context) { iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { (iterator.call(context, value, index) ? trues : falses).push(value); }); return [trues, falses]; } function pluck(property) { var results = []; this.each(function(value) { results.push(value[property]); }); return results; } function reject(iterator, context) { var results = []; this.each(function(value, index) { if (!iterator.call(context, value, index)) results.push(value); }); return results; } function sortBy(iterator, context) { return this.map(function(value, index) { return { value: value, criteria: iterator.call(context, value, index) }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); } function toArray() { return this.map(); } function zip() { var iterator = Prototype.K, args = $A(arguments); if (Object.isFunction(args.last())) iterator = args.pop(); var collections = [this].concat(args).map($A); return this.map(function(value, index) { return iterator(collections.pluck(index)); }); } function size() { return this.toArray().length; } function inspect() { return '#'; } return { each: each, eachSlice: eachSlice, all: all, every: all, any: any, some: any, collect: collect, map: collect, detect: detect, findAll: findAll, select: findAll, filter: findAll, grep: grep, include: include, member: include, inGroupsOf: inGroupsOf, inject: inject, invoke: invoke, max: max, min: min, partition: partition, pluck: pluck, reject: reject, sortBy: sortBy, toArray: toArray, entries: toArray, zip: zip, size: size, inspect: inspect, find: detect }; })(); function $A(iterable) { if (!iterable) return []; if ('toArray' in Object(iterable)) return iterable.toArray(); var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } function $w(string) { if (!Object.isString(string)) return []; string = string.strip(); return string ? string.split(/\s+/) : []; } Array.from = $A; (function() { var arrayProto = Array.prototype, slice = arrayProto.slice, _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available function each(iterator) { for (var i = 0, length = this.length; i < length; i++) iterator(this[i]); } if (!_each) _each = each; function clear() { this.length = 0; return this; } function first() { return this[0]; } function last() { return this[this.length - 1]; } function compact() { return this.select(function(value) { return value != null; }); } function flatten() { return this.inject([], function(array, value) { if (Object.isArray(value)) return array.concat(value.flatten()); array.push(value); return array; }); } function without() { var values = slice.call(arguments, 0); return this.select(function(value) { return !values.include(value); }); } function reverse(inline) { return (inline === false ? this.toArray() : this)._reverse(); } function uniq(sorted) { return this.inject([], function(array, value, index) { if (0 == index || (sorted ? array.last() != value : !array.include(value))) array.push(value); return array; }); } function intersect(array) { return this.uniq().findAll(function(item) { return array.detect(function(value) { return item === value }); }); } function clone() { return slice.call(this, 0); } function size() { return this.length; } function inspect() { return '[' + this.map(Object.inspect).join(', ') + ']'; } function indexOf(item, i) { i || (i = 0); var length = this.length; if (i < 0) i = length + i; for (; i < length; i++) if (this[i] === item) return i; return -1; } function lastIndexOf(item, i) { i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; var n = this.slice(0, i).reverse().indexOf(item); return (n < 0) ? n : i - n - 1; } function concat() { var array = slice.call(this, 0), item; for (var i = 0, length = arguments.length; i < length; i++) { item = arguments[i]; if (Object.isArray(item) && !('callee' in item)) { for (var j = 0, arrayLength = item.length; j < arrayLength; j++) array.push(item[j]); } else { array.push(item); } } return array; } Object.extend(arrayProto, Enumerable); if (!arrayProto._reverse) arrayProto._reverse = arrayProto.reverse; Object.extend(arrayProto, { _each: _each, clear: clear, first: first, last: last, compact: compact, flatten: flatten, without: without, reverse: reverse, uniq: uniq, intersect: intersect, clone: clone, toArray: clone, size: size, inspect: inspect }); var CONCAT_ARGUMENTS_BUGGY = (function() { return [].concat(arguments)[0][0] !== 1; })(1,2) if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; })(); function $H(object) { return new Hash(object); }; var Hash = Class.create(Enumerable, (function() { function initialize(object) { this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); } function _each(iterator) { for (var key in this._object) { var value = this._object[key], pair = [key, value]; pair.key = key; pair.value = value; iterator(pair); } } function set(key, value) { return this._object[key] = value; } function get(key) { if (this._object[key] !== Object.prototype[key]) return this._object[key]; } function unset(key) { var value = this._object[key]; delete this._object[key]; return value; } function toObject() { return Object.clone(this._object); } function keys() { return this.pluck('key'); } function values() { return this.pluck('value'); } function index(value) { var match = this.detect(function(pair) { return pair.value === value; }); return match && match.key; } function merge(object) { return this.clone().update(object); } function update(object) { return new Hash(object).inject(this, function(result, pair) { result.set(pair.key, pair.value); return result; }); } function toQueryPair(key, value) { if (Object.isUndefined(value)) return key; return key + '=' + encodeURIComponent(String.interpret(value)); } function toQueryString() { return this.inject([], function(results, pair) { var key = encodeURIComponent(pair.key), values = pair.value; if (values && typeof values == 'object') { if (Object.isArray(values)) return results.concat(values.map(toQueryPair.curry(key))); } else results.push(toQueryPair(key, values)); return results; }).join('&'); } function inspect() { return '#'; } function clone() { return new Hash(this); } return { initialize: initialize, _each: _each, set: set, get: get, unset: unset, toObject: toObject, toTemplateReplacements: toObject, keys: keys, values: values, index: index, merge: merge, update: update, toQueryString: toQueryString, inspect: inspect, toJSON: toObject, clone: clone }; })()); Hash.from = $H; Object.extend(Number.prototype, (function() { function toColorPart() { return this.toPaddedString(2, 16); } function succ() { return this + 1; } function times(iterator, context) { $R(0, this, true).each(iterator, context); return this; } function toPaddedString(length, radix) { var string = this.toString(radix || 10); return '0'.times(length - string.length) + string; } function abs() { return Math.abs(this); } function round() { return Math.round(this); } function ceil() { return Math.ceil(this); } function floor() { return Math.floor(this); } return { toColorPart: toColorPart, succ: succ, times: times, toPaddedString: toPaddedString, abs: abs, round: round, ceil: ceil, floor: floor }; })()); function $R(start, end, exclusive) { return new ObjectRange(start, end, exclusive); } var ObjectRange = Class.create(Enumerable, (function() { function initialize(start, end, exclusive) { this.start = start; this.end = end; this.exclusive = exclusive; } function _each(iterator) { var value = this.start; while (this.include(value)) { iterator(value); value = value.succ(); } } function include(value) { if (value < this.start) return false; if (this.exclusive) return value < this.end; return value <= this.end; } return { initialize: initialize, _each: _each, include: include }; })()); var Ajax = { getTransport: function() { return Try.these( function() {return new XMLHttpRequest()}, function() {return new ActiveXObject('Msxml2.XMLHTTP')}, function() {return new ActiveXObject('Microsoft.XMLHTTP')} ) || false; }, activeRequestCount: 0 }; Ajax.Responders = { responders: [], _each: function(iterator) { this.responders._each(iterator); }, register: function(responder) { if (!this.include(responder)) this.responders.push(responder); }, unregister: function(responder) { this.responders = this.responders.without(responder); }, dispatch: function(callback, request, transport, json) { this.each(function(responder) { if (Object.isFunction(responder[callback])) { try { responder[callback].apply(responder, [request, transport, json]); } catch (e) { } } }); } }; Object.extend(Ajax.Responders, Enumerable); Ajax.Responders.register({ onCreate: function() { Ajax.activeRequestCount++ }, onComplete: function() { Ajax.activeRequestCount-- } }); Ajax.Base = Class.create({ initialize: function(options) { this.options = { method: 'post', asynchronous: true, contentType: 'application/x-www-form-urlencoded', encoding: 'UTF-8', parameters: '', evalJSON: true, evalJS: true }; Object.extend(this.options, options || { }); this.options.method = this.options.method.toLowerCase(); if (Object.isString(this.options.parameters)) this.options.parameters = this.options.parameters.toQueryParams(); else if (Object.isHash(this.options.parameters)) this.options.parameters = this.options.parameters.toObject(); } }); Ajax.Request = Class.create(Ajax.Base, { _complete: false, initialize: function($super, url, options) { $super(options); this.transport = Ajax.getTransport(); this.request(url); }, request: function(url) { this.url = url; this.method = this.options.method; var params = Object.clone(this.options.parameters); if (!['get', 'post'].include(this.method)) { params['_method'] = this.method; this.method = 'post'; } this.parameters = params; if (params = Object.toQueryString(params)) { if (this.method == 'get') this.url += (this.url.include('?') ? '&' : '?') + params; else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_='; } try { var response = new Ajax.Response(this); if (this.options.onCreate) this.options.onCreate(response); Ajax.Responders.dispatch('onCreate', this, response); this.transport.open(this.method.toUpperCase(), this.url, this.options.asynchronous); if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); this.transport.onreadystatechange = this.onStateChange.bind(this); this.setRequestHeaders(); this.body = this.method == 'post' ? (this.options.postBody || params) : null; this.transport.send(this.body); /* Force Firefox to handle ready state 4 for synchronous requests */ if (!this.options.asynchronous && this.transport.overrideMimeType) this.onStateChange(); } catch (e) { this.dispatchException(e); } }, onStateChange: function() { var readyState = this.transport.readyState; if (readyState > 1 && !((readyState == 4) && this._complete)) this.respondToReadyState(this.transport.readyState); }, setRequestHeaders: function() { var headers = { 'X-Requested-With': 'XMLHttpRequest', 'X-Prototype-Version': Prototype.Version, 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' }; if (this.method == 'post') { headers['Content-type'] = this.options.contentType + (this.options.encoding ? '; charset=' + this.options.encoding : ''); /* Force "Connection: close" for older Mozilla browsers to work * around a bug where XMLHttpRequest sends an incorrect * Content-length header. See Mozilla Bugzilla #246651. */ if (this.transport.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) headers['Connection'] = 'close'; } if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; if (Object.isFunction(extras.push)) for (var i = 0, length = extras.length; i < length; i += 2) headers[extras[i]] = extras[i+1]; else $H(extras).each(function(pair) { headers[pair.key] = pair.value }); } for (var name in headers) this.transport.setRequestHeader(name, headers[name]); }, success: function() { var status = this.getStatus(); return !status || (status >= 200 && status < 300); }, getStatus: function() { try { return this.transport.status || 0; } catch (e) { return 0 } }, respondToReadyState: function(readyState) { var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); if (state == 'Complete') { try { this._complete = true; (this.options['on' + response.status] || this.options['on' + (this.success() ? 'Success' : 'Failure')] || Prototype.emptyFunction)(response, response.headerJSON); } catch (e) { this.dispatchException(e); } var contentType = response.getHeader('Content-type'); if (this.options.evalJS == 'force' || (this.options.evalJS && this.isSameOrigin() && contentType && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) this.evalResponse(); } try { (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); } catch (e) { this.dispatchException(e); } if (state == 'Complete') { this.transport.onreadystatechange = Prototype.emptyFunction; } }, isSameOrigin: function() { var m = this.url.match(/^\s*https?:\/\/[^\/]*/); return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ protocol: location.protocol, domain: document.domain, port: location.port ? ':' + location.port : '' })); }, getHeader: function(name) { try { return this.transport.getResponseHeader(name) || null; } catch (e) { return null; } }, evalResponse: function() { try { return eval((this.transport.responseText || '').unfilterJSON()); } catch (e) { this.dispatchException(e); } }, dispatchException: function(exception) { (this.options.onException || Prototype.emptyFunction)(this, exception); Ajax.Responders.dispatch('onException', this, exception); } }); Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; Ajax.Response = Class.create({ initialize: function(request){ this.request = request; var transport = this.transport = request.transport, readyState = this.readyState = transport.readyState; if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = String.interpret(transport.responseText); this.headerJSON = this._getHeaderJSON(); } if (readyState == 4) { var xml = transport.responseXML; this.responseXML = Object.isUndefined(xml) ? null : xml; this.responseJSON = this._getResponseJSON(); } }, status: 0, statusText: '', getStatus: Ajax.Request.prototype.getStatus, getStatusText: function() { try { return this.transport.statusText || ''; } catch (e) { return '' } }, getHeader: Ajax.Request.prototype.getHeader, getAllHeaders: function() { try { return this.getAllResponseHeaders(); } catch (e) { return null } }, getResponseHeader: function(name) { return this.transport.getResponseHeader(name); }, getAllResponseHeaders: function() { return this.transport.getAllResponseHeaders(); }, _getHeaderJSON: function() { var json = this.getHeader('X-JSON'); if (!json) return null; json = decodeURIComponent(escape(json)); try { return json.evalJSON(this.request.options.sanitizeJSON || !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } }, _getResponseJSON: function() { var options = this.request.options; if (!options.evalJSON || (options.evalJSON != 'force' && !(this.getHeader('Content-type') || '').include('application/json')) || this.responseText.blank()) return null; try { return this.responseText.evalJSON(options.sanitizeJSON || !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } } }); Ajax.Updater = Class.create(Ajax.Request, { initialize: function($super, container, url, options) { this.container = { success: (container.success || container), failure: (container.failure || (container.success ? null : container)) }; options = Object.clone(options); var onComplete = options.onComplete; options.onComplete = (function(response, json) { this.updateContent(response.responseText); if (Object.isFunction(onComplete)) onComplete(response, json); }).bind(this); $super(url, options); }, updateContent: function(responseText) { var receiver = this.container[this.success() ? 'success' : 'failure'], options = this.options; if (!options.evalScripts) responseText = responseText.stripScripts(); if (receiver = $(receiver)) { if (options.insertion) { if (Object.isString(options.insertion)) { var insertion = { }; insertion[options.insertion] = responseText; receiver.insert(insertion); } else options.insertion(receiver, responseText); } else receiver.update(responseText); } } }); Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { initialize: function($super, container, url, options) { $super(options); this.onComplete = this.options.onComplete; this.frequency = (this.options.frequency || 2); this.decay = (this.options.decay || 1); this.updater = { }; this.container = container; this.url = url; this.start(); }, start: function() { this.options.onComplete = this.updateComplete.bind(this); this.onTimerEvent(); }, stop: function() { this.updater.options.onComplete = undefined; clearTimeout(this.timer); (this.onComplete || Prototype.emptyFunction).apply(this, arguments); }, updateComplete: function(response) { if (this.options.decay) { this.decay = (response.responseText == this.lastText ? this.decay * this.options.decay : 1); this.lastText = response.responseText; } this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); }, onTimerEvent: function() { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); function $(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) elements.push($(arguments[i])); return elements; } if (Object.isString(element)) element = document.getElementById(element); return Element.extend(element); } if (Prototype.BrowserFeatures.XPath) { document._getElementsByXPath = function(expression, parentElement) { var results = []; var query = document.evaluate(expression, $(parentElement) || document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (var i = 0, length = query.snapshotLength; i < length; i++) results.push(Element.extend(query.snapshotItem(i))); return results; }; } /*--------------------------------------------------------------------------*/ if (!Node) var Node = { }; if (!Node.ELEMENT_NODE) { Object.extend(Node, { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, TEXT_NODE: 3, CDATA_SECTION_NODE: 4, ENTITY_REFERENCE_NODE: 5, ENTITY_NODE: 6, PROCESSING_INSTRUCTION_NODE: 7, COMMENT_NODE: 8, DOCUMENT_NODE: 9, DOCUMENT_TYPE_NODE: 10, DOCUMENT_FRAGMENT_NODE: 11, NOTATION_NODE: 12 }); } (function(global) { var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ try { var el = document.createElement(''); return el.tagName.toLowerCase() === 'input' && el.name === 'x'; } catch(err) { return false; } })(); var element = global.Element; global.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); } if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; Object.extend(global.Element, element || { }); if (element) global.Element.prototype = element.prototype; })(this); Element.idCounter = 1; Element.cache = { }; Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; }, toggle: function(element) { element = $(element); Element[Element.visible(element) ? 'hide' : 'show'](element); return element; }, hide: function(element) { element = $(element); element.style.display = 'none'; return element; }, show: function(element) { element = $(element); element.style.display = ''; return element; }, remove: function(element) { element = $(element); element.parentNode.removeChild(element); return element; }, update: (function(){ var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ var el = document.createElement("select"), isBuggy = true; el.innerHTML = ""; if (el.options && el.options[0]) { isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; } el = null; return isBuggy; })(); var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ try { var el = document.createElement("table"); if (el && el.tBodies) { el.innerHTML = "test"; var isBuggy = typeof el.tBodies[0] == "undefined"; el = null; return isBuggy; } } catch (e) { return true; } })(); var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { var s = document.createElement("script"), isBuggy = false; try { s.appendChild(document.createTextNode("")); isBuggy = !s.firstChild || s.firstChild && s.firstChild.nodeType !== 3; } catch (e) { isBuggy = true; } s = null; return isBuggy; })(); function update(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) return element.update().insert(content); content = Object.toHTML(content); var tagName = element.tagName.toUpperCase(); if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { element.text = content; return element; } if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { if (tagName in Element._insertionTranslations.tags) { while (element.firstChild) { element.removeChild(element.firstChild); } Element._getContentFromAnonymousElement(tagName, content.stripScripts()) .each(function(node) { element.appendChild(node) }); } else { element.innerHTML = content.stripScripts(); } } else { element.innerHTML = content.stripScripts(); } content.evalScripts.bind(content).defer(); return element; } return update; })(), replace: function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); else if (!Object.isElement(content)) { content = Object.toHTML(content); var range = element.ownerDocument.createRange(); range.selectNode(element); content.evalScripts.bind(content).defer(); content = range.createContextualFragment(content.stripScripts()); } element.parentNode.replaceChild(content, element); return element; }, insert: function(element, insertions) { element = $(element); if (Object.isString(insertions) || Object.isNumber(insertions) || Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) insertions = {bottom:insertions}; var content, insert, tagName, childNodes; for (var position in insertions) { content = insertions[position]; position = position.toLowerCase(); insert = Element._insertionTranslations[position]; if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { insert(element, content); continue; } content = Object.toHTML(content); tagName = ((position == 'before' || position == 'after') ? element.parentNode : element).tagName.toUpperCase(); childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); if (position == 'top' || position == 'after') childNodes.reverse(); childNodes.each(insert.curry(element)); content.evalScripts.bind(content).defer(); } return element; }, wrap: function(element, wrapper, attributes) { element = $(element); if (Object.isElement(wrapper)) $(wrapper).writeAttribute(attributes || { }); else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); else wrapper = new Element('div', wrapper); if (element.parentNode) element.parentNode.replaceChild(wrapper, element); wrapper.appendChild(element); return wrapper; }, inspect: function(element) { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { var property = pair.first(), attribute = pair.last(), value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; }, recursivelyCollect: function(element, property, maximumLength) { element = $(element); maximumLength = maximumLength || -1; var elements = []; while (element = element[property]) { if (element.nodeType == 1) elements.push(Element.extend(element)); if (elements.length == maximumLength) break; } return elements; }, ancestors: function(element) { return Element.recursivelyCollect(element, 'parentNode'); }, descendants: function(element) { return Element.select(element, "*"); }, firstDescendant: function(element) { element = $(element).firstChild; while (element && element.nodeType != 1) element = element.nextSibling; return $(element); }, immediateDescendants: function(element) { var results = [], child = $(element).firstChild; while (child) { if (child.nodeType === 1) { results.push(Element.extend(child)); } child = child.nextSibling; } return results; }, previousSiblings: function(element, maximumLength) { return Element.recursivelyCollect(element, 'previousSibling'); }, nextSiblings: function(element) { return Element.recursivelyCollect(element, 'nextSibling'); }, siblings: function(element) { element = $(element); return Element.previousSiblings(element).reverse() .concat(Element.nextSiblings(element)); }, match: function(element, selector) { element = $(element); if (Object.isString(selector)) return Prototype.Selector.match(element, selector); return selector.match(element); }, up: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(element.parentNode); var ancestors = Element.ancestors(element); return Object.isNumber(expression) ? ancestors[expression] : Prototype.Selector.find(ancestors, expression, index); }, down: function(element, expression, index) { element = $(element); if (arguments.length == 1) return Element.firstDescendant(element); return Object.isNumber(expression) ? Element.descendants(element)[expression] : Element.select(element, expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); if (Object.isNumber(expression)) index = expression, expression = false; if (!Object.isNumber(index)) index = 0; if (expression) { return Prototype.Selector.find(element.previousSiblings(), expression, index); } else { return element.recursivelyCollect("previousSibling", index + 1)[index]; } }, next: function(element, expression, index) { element = $(element); if (Object.isNumber(expression)) index = expression, expression = false; if (!Object.isNumber(index)) index = 0; if (expression) { return Prototype.Selector.find(element.nextSiblings(), expression, index); } else { var maximumLength = Object.isNumber(index) ? index + 1 : 1; return element.recursivelyCollect("nextSibling", index + 1)[index]; } }, select: function(element) { element = $(element); var expressions = Array.prototype.slice.call(arguments, 1).join(', '); return Prototype.Selector.select(expressions, element); }, adjacent: function(element) { element = $(element); var expressions = Array.prototype.slice.call(arguments, 1).join(', '); return Prototype.Selector.select(expressions, element.parentNode).without(element); }, identify: function(element) { element = $(element); var id = Element.readAttribute(element, 'id'); if (id) return id; do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); Element.writeAttribute(element, 'id', id); return id; }, readAttribute: function(element, name) { element = $(element); if (Prototype.Browser.IE) { var t = Element._attributeTranslations.read; if (t.values[name]) return t.values[name](element, name); if (t.names[name]) name = t.names[name]; if (name.include(':')) { return (!element.attributes || !element.attributes[name]) ? null : element.attributes[name].value; } } return element.getAttribute(name); }, writeAttribute: function(element, name, value) { element = $(element); var attributes = { }, t = Element._attributeTranslations.write; if (typeof name == 'object') attributes = name; else attributes[name] = Object.isUndefined(value) ? true : value; for (var attr in attributes) { name = t.names[attr] || attr; value = attributes[attr]; if (t.values[attr]) name = t.values[attr](element, value); if (value === false || value === null) element.removeAttribute(name); else if (value === true) element.setAttribute(name, name); else element.setAttribute(name, value); } return element; }, getHeight: function(element) { return Element.getDimensions(element).height; }, getWidth: function(element) { return Element.getDimensions(element).width; }, classNames: function(element) { return new Element.ClassNames(element); }, hasClassName: function(element, className) { if (!(element = $(element))) return; var elementClassName = element.className; return (elementClassName.length > 0 && (elementClassName == className || new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); }, addClassName: function(element, className) { if (!(element = $(element))) return; if (!Element.hasClassName(element, className)) element.className += (element.className ? ' ' : '') + className; return element; }, removeClassName: function(element, className) { if (!(element = $(element))) return; element.className = element.className.replace( new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); return element; }, toggleClassName: function(element, className) { if (!(element = $(element))) return; return Element[Element.hasClassName(element, className) ? 'removeClassName' : 'addClassName'](element, className); }, cleanWhitespace: function(element) { element = $(element); var node = element.firstChild; while (node) { var nextNode = node.nextSibling; if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) element.removeChild(node); node = nextNode; } return element; }, empty: function(element) { return $(element).innerHTML.blank(); }, descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); if (element.compareDocumentPosition) return (element.compareDocumentPosition(ancestor) & 8) === 8; if (ancestor.contains) return ancestor.contains(element) && ancestor !== element; while (element = element.parentNode) if (element == ancestor) return true; return false; }, scrollTo: function(element) { element = $(element); var pos = Element.cumulativeOffset(element); window.scrollTo(pos[0], pos[1]); return element; }, getStyle: function(element, style) { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); var value = element.style[style]; if (!value || value == 'auto') { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } if (style == 'opacity') return value ? parseFloat(value) : 1.0; return value == 'auto' ? null : value; }, getOpacity: function(element) { return $(element).getStyle('opacity'); }, setStyle: function(element, styles) { element = $(element); var elementStyle = element.style, match; if (Object.isString(styles)) { element.style.cssText += ';' + styles; return styles.include('opacity') ? element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; } for (var property in styles) if (property == 'opacity') element.setOpacity(styles[property]); else elementStyle[(property == 'float' || property == 'cssFloat') ? (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : property] = styles[property]; return element; }, setOpacity: function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }, makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; if (Prototype.Browser.Opera) { element.style.top = 0; element.style.left = 0; } } return element; }, undoPositioned: function(element) { element = $(element); if (element._madePositioned) { element._madePositioned = undefined; element.style.position = element.style.top = element.style.left = element.style.bottom = element.style.right = ''; } return element; }, makeClipping: function(element) { element = $(element); if (element._overflow) return element; element._overflow = Element.getStyle(element, 'overflow') || 'auto'; if (element._overflow !== 'hidden') element.style.overflow = 'hidden'; return element; }, undoClipping: function(element) { element = $(element); if (!element._overflow) return element; element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; element._overflow = null; return element; }, cumulativeOffset: function(element) { var valueT = 0, valueL = 0; if (element.parentNode) { do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); } return Element._returnOffset(valueL, valueT); }, positionedOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { if (element.tagName.toUpperCase() == 'BODY') break; var p = Element.getStyle(element, 'position'); if (p !== 'static') break; } } while (element); return Element._returnOffset(valueL, valueT); }, absolutize: function(element) { element = $(element); if (Element.getStyle(element, 'position') == 'absolute') return element; var offsets = Element.positionedOffset(element), top = offsets[1], left = offsets[0], width = element.clientWidth, height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); element._originalWidth = element.style.width; element._originalHeight = element.style.height; element.style.position = 'absolute'; element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.width = width + 'px'; element.style.height = height + 'px'; return element; }, relativize: function(element) { element = $(element); if (Element.getStyle(element, 'position') == 'relative') return element; element.style.position = 'relative'; var top = parseFloat(element.style.top || 0) - (element._originalTop || 0), left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; element.style.height = element._originalHeight; element.style.width = element._originalWidth; return element; }, cumulativeScrollOffset: function(element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return Element._returnOffset(valueL, valueT); }, getOffsetParent: function(element) { if (element.offsetParent) return $(element.offsetParent); if (element == document.body) return $(element); while ((element = element.parentNode) && element != document.body) if (Element.getStyle(element, 'position') != 'static') return $(element); return $(document.body); }, viewportOffset: function(forElement) { var valueT = 0, valueL = 0, element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body && Element.getStyle(element, 'position') == 'absolute') break; } while (element = element.offsetParent); element = forElement; do { if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } } while (element = element.parentNode); return Element._returnOffset(valueL, valueT); }, clonePosition: function(element, source) { var options = Object.extend({ setLeft: true, setTop: true, setWidth: true, setHeight: true, offsetTop: 0, offsetLeft: 0 }, arguments[2] || { }); source = $(source); var p = Element.viewportOffset(source), delta = [0, 0], parent = null; element = $(element); if (Element.getStyle(element, 'position') == 'absolute') { parent = Element.getOffsetParent(element); delta = Element.viewportOffset(parent); } if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if (options.setWidth) element.style.width = source.offsetWidth + 'px'; if (options.setHeight) element.style.height = source.offsetHeight + 'px'; return element; } }; Object.extend(Element.Methods, { getElementsBySelector: Element.Methods.select, childElements: Element.Methods.immediateDescendants }); Element._attributeTranslations = { write: { names: { className: 'class', htmlFor: 'for' }, values: { } } }; if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { switch (style) { case 'left': case 'top': case 'right': case 'bottom': if (proceed(element, 'position') === 'static') return null; case 'height': case 'width': if (!Element.visible(element)) return null; var dim = parseInt(proceed(element, style), 10); if (dim !== element['offset' + style.capitalize()]) return dim + 'px'; var properties; if (style === 'height') { properties = ['border-top-width', 'padding-top', 'padding-bottom', 'border-bottom-width']; } else { properties = ['border-left-width', 'padding-left', 'padding-right', 'border-right-width']; } return properties.inject(dim, function(memo, property) { var val = proceed(element, property); return val === null ? memo : memo - parseInt(val, 10); }) + 'px'; default: return proceed(element, style); } } ); Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( function(proceed, element, attribute) { if (attribute === 'title') return element.title; return proceed(element, attribute); } ); } else if (Prototype.Browser.IE) { Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( function(proceed, element) { element = $(element); if (!element.parentNode) return $(document.body); var position = element.getStyle('position'); if (position !== 'static') return proceed(element); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); return value; } ); $w('positionedOffset viewportOffset').each(function(method) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); if (!element.parentNode) return Element._returnOffset(0, 0); var position = element.getStyle('position'); if (position !== 'static') return proceed(element); var offsetParent = element.getOffsetParent(); if (offsetParent && offsetParent.getStyle('position') === 'fixed') offsetParent.setStyle({ zoom: 1 }); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); return value; } ); }); Element.Methods.getStyle = function(element, style) { element = $(element); style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); var value = element.style[style]; if (!value && element.currentStyle) value = element.currentStyle[style]; if (style == 'opacity') { if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) if (value[1]) return parseFloat(value[1]) / 100; return 1.0; } if (value == 'auto') { if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) return element['offset' + style.capitalize()] + 'px'; return null; } return value; }; Element.Methods.setOpacity = function(element, value) { function stripAlpha(filter){ return filter.replace(/alpha\([^\)]*\)/gi,''); } element = $(element); var currentStyle = element.currentStyle; if ((currentStyle && !currentStyle.hasLayout) || (!currentStyle && element.style.zoom == 'normal')) element.style.zoom = 1; var filter = element.getStyle('filter'), style = element.style; if (value == 1 || value === '') { (filter = stripAlpha(filter)) ? style.filter = filter : style.removeAttribute('filter'); return element; } else if (value < 0.00001) value = 0; style.filter = stripAlpha(filter) + 'alpha(opacity=' + (value * 100) + ')'; return element; }; Element._attributeTranslations = (function(){ var classProp = 'className', forProp = 'for', el = document.createElement('div'); el.setAttribute(classProp, 'x'); if (el.className !== 'x') { el.setAttribute('class', 'x'); if (el.className === 'x') { classProp = 'class'; } } el = null; el = document.createElement('label'); el.setAttribute(forProp, 'x'); if (el.htmlFor !== 'x') { el.setAttribute('htmlFor', 'x'); if (el.htmlFor === 'x') { forProp = 'htmlFor'; } } el = null; return { read: { names: { 'class': classProp, 'className': classProp, 'for': forProp, 'htmlFor': forProp }, values: { _getAttr: function(element, attribute) { return element.getAttribute(attribute); }, _getAttr2: function(element, attribute) { return element.getAttribute(attribute, 2); }, _getAttrNode: function(element, attribute) { var node = element.getAttributeNode(attribute); return node ? node.value : ""; }, _getEv: (function(){ var el = document.createElement('div'), f; el.onclick = Prototype.emptyFunction; var value = el.getAttribute('onclick'); if (String(value).indexOf('{') > -1) { f = function(element, attribute) { attribute = element.getAttribute(attribute); if (!attribute) return null; attribute = attribute.toString(); attribute = attribute.split('{')[1]; attribute = attribute.split('}')[0]; return attribute.strip(); }; } else if (value === '') { f = function(element, attribute) { attribute = element.getAttribute(attribute); if (!attribute) return null; return attribute.strip(); }; } el = null; return f; })(), _flag: function(element, attribute) { return $(element).hasAttribute(attribute) ? attribute : null; }, style: function(element) { return element.style.cssText.toLowerCase(); }, title: function(element) { return element.title; } } } } })(); Element._attributeTranslations.write = { names: Object.extend({ cellpadding: 'cellPadding', cellspacing: 'cellSpacing' }, Element._attributeTranslations.read.names), values: { checked: function(element, value) { element.checked = !!value; }, style: function(element, value) { element.style.cssText = value ? value : ''; } } }; Element._attributeTranslations.has = {}; $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; Element._attributeTranslations.has[attr.toLowerCase()] = attr; }); (function(v) { Object.extend(v, { href: v._getAttr2, src: v._getAttr2, type: v._getAttr, action: v._getAttrNode, disabled: v._flag, checked: v._flag, readonly: v._flag, multiple: v._flag, onload: v._getEv, onunload: v._getEv, onclick: v._getEv, ondblclick: v._getEv, onmousedown: v._getEv, onmouseup: v._getEv, onmouseover: v._getEv, onmousemove: v._getEv, onmouseout: v._getEv, onfocus: v._getEv, onblur: v._getEv, onkeypress: v._getEv, onkeydown: v._getEv, onkeyup: v._getEv, onsubmit: v._getEv, onreset: v._getEv, onselect: v._getEv, onchange: v._getEv }); })(Element._attributeTranslations.read.values); if (Prototype.BrowserFeatures.ElementExtensions) { (function() { function _descendants(element) { var nodes = element.getElementsByTagName('*'), results = []; for (var i = 0, node; node = nodes[i]; i++) if (node.tagName !== "!") // Filter out comment nodes. results.push(node); return results; } Element.Methods.down = function(element, expression, index) { element = $(element); if (arguments.length == 1) return element.firstDescendant(); return Object.isNumber(expression) ? _descendants(element)[expression] : Element.select(element, expression)[index || 0]; } })(); } } else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1) ? 0.999999 : (value === '') ? '' : (value < 0.00001) ? 0 : value; return element; }; } else if (Prototype.Browser.WebKit) { Element.Methods.setOpacity = function(element, value) { element = $(element); element.style.opacity = (value == 1 || value === '') ? '' : (value < 0.00001) ? 0 : value; if (value == 1) if (element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); element.appendChild(n); element.removeChild(n); } catch (e) { } return element; }; Element.Methods.cumulativeOffset = function(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == document.body) if (Element.getStyle(element, 'position') == 'absolute') break; element = element.offsetParent; } while (element); return Element._returnOffset(valueL, valueT); }; } if ('outerHTML' in document.documentElement) { Element.Methods.replace = function(element, content) { element = $(element); if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { element.parentNode.replaceChild(content, element); return element; } content = Object.toHTML(content); var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); if (Element._insertionTranslations.tags[tagName]) { var nextSibling = element.next(), fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); parent.removeChild(element); if (nextSibling) fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); else fragments.each(function(node) { parent.appendChild(node) }); } else element.outerHTML = content.stripScripts(); content.evalScripts.bind(content).defer(); return element; }; } Element._returnOffset = function(l, t) { var result = [l, t]; result.left = l; result.top = t; return result; }; Element._getContentFromAnonymousElement = function(tagName, html) { var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; if (t) { div.innerHTML = t[0] + html + t[1]; for (var i = t[2]; i--; ) { div = div.firstChild; } } else { div.innerHTML = html; } return $A(div.childNodes); }; Element._insertionTranslations = { before: function(element, node) { element.parentNode.insertBefore(node, element); }, top: function(element, node) { element.insertBefore(node, element.firstChild); }, bottom: function(element, node) { element.appendChild(node); }, after: function(element, node) { element.parentNode.insertBefore(node, element.nextSibling); }, tags: { TABLE: ['', '
    ', 1], TBODY: ['', '
    ', 2], TR: ['', '
    ', 3], TD: ['
    ', '
    ', 4], SELECT: ['', 1] } }; (function() { var tags = Element._insertionTranslations.tags; Object.extend(tags, { THEAD: tags.TBODY, TFOOT: tags.TBODY, TH: tags.TD }); })(); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); return !!(node && node.specified); } }; Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); (function(div) { if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { window.HTMLElement = { }; window.HTMLElement.prototype = div['__proto__']; Prototype.BrowserFeatures.ElementExtensions = true; } div = null; })(document.createElement('div')); Element.extend = (function() { function checkDeficiency(tagName) { if (typeof window.Element != 'undefined') { var proto = window.Element.prototype; if (proto) { var id = '_' + (Math.random()+'').slice(2), el = document.createElement(tagName); proto[id] = 'x'; var isBuggy = (el[id] !== 'x'); delete proto[id]; el = null; return isBuggy; } } return false; } function extendElementWith(element, methods) { for (var property in methods) { var value = methods[property]; if (Object.isFunction(value) && !(property in element)) element[property] = value.methodize(); } } var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); if (Prototype.BrowserFeatures.SpecificElementExtensions) { if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { return function(element) { if (element && typeof element._extendedByPrototype == 'undefined') { var t = element.tagName; if (t && (/^(?:object|applet|embed)$/i.test(t))) { extendElementWith(element, Element.Methods); extendElementWith(element, Element.Methods.Simulated); extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); } } return element; } } return Prototype.K; } var Methods = { }, ByTag = Element.Methods.ByTag; var extend = Object.extend(function(element) { if (!element || typeof element._extendedByPrototype != 'undefined' || element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), tagName = element.tagName.toUpperCase(); if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); extendElementWith(element, methods); element._extendedByPrototype = Prototype.emptyFunction; return element; }, { refresh: function() { if (!Prototype.BrowserFeatures.ElementExtensions) { Object.extend(Methods, Element.Methods); Object.extend(Methods, Element.Methods.Simulated); } } }); extend.refresh(); return extend; })(); if (document.documentElement.hasAttribute) { Element.hasAttribute = function(element, attribute) { return element.hasAttribute(attribute); }; } else { Element.hasAttribute = Element.Methods.Simulated.hasAttribute; } Element.addMethods = function(methods) { var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; if (!methods) { Object.extend(Form, Form.Methods); Object.extend(Form.Element, Form.Element.Methods); Object.extend(Element.Methods.ByTag, { "FORM": Object.clone(Form.Methods), "INPUT": Object.clone(Form.Element.Methods), "SELECT": Object.clone(Form.Element.Methods), "TEXTAREA": Object.clone(Form.Element.Methods) }); } if (arguments.length == 2) { var tagName = methods; methods = arguments[1]; } if (!tagName) Object.extend(Element.Methods, methods || { }); else { if (Object.isArray(tagName)) tagName.each(extend); else extend(tagName); } function extend(tagName) { tagName = tagName.toUpperCase(); if (!Element.Methods.ByTag[tagName]) Element.Methods.ByTag[tagName] = { }; Object.extend(Element.Methods.ByTag[tagName], methods); } function copy(methods, destination, onlyIfAbsent) { onlyIfAbsent = onlyIfAbsent || false; for (var property in methods) { var value = methods[property]; if (!Object.isFunction(value)) continue; if (!onlyIfAbsent || !(property in destination)) destination[property] = value.methodize(); } } function findDOMClass(tagName) { var klass; var trans = { "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": "FrameSet", "IFRAME": "IFrame" }; if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; if (window[klass]) return window[klass]; klass = 'HTML' + tagName + 'Element'; if (window[klass]) return window[klass]; klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; var element = document.createElement(tagName), proto = element['__proto__'] || element.constructor.prototype; element = null; return proto; } var elementPrototype = window.HTMLElement ? HTMLElement.prototype : Element.prototype; if (F.ElementExtensions) { copy(Element.Methods, elementPrototype); copy(Element.Methods.Simulated, elementPrototype, true); } if (F.SpecificElementExtensions) { for (var tag in Element.Methods.ByTag) { var klass = findDOMClass(tag); if (Object.isUndefined(klass)) continue; copy(T[tag], klass.prototype); } } Object.extend(Element, Element.Methods); delete Element.ByTag; if (Element.extend.refresh) Element.extend.refresh(); Element.cache = { }; }; document.viewport = { getDimensions: function() { return { width: this.getWidth(), height: this.getHeight() }; }, getScrollOffsets: function() { return Element._returnOffset( window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; (function(viewport) { var B = Prototype.Browser, doc = document, element, property = {}; function getRootElement() { if (B.WebKit && !doc.evaluate) return document; if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) return document.body; return document.documentElement; } function define(D) { if (!element) element = getRootElement(); property[D] = 'client' + D; viewport['get' + D] = function() { return element[property[D]] }; return viewport['get' + D](); } viewport.getWidth = define.curry('Width'); viewport.getHeight = define.curry('Height'); })(document.viewport); Element.Storage = { UID: 1 }; Element.addMethods({ getStorage: function(element) { if (!(element = $(element))) return; var uid; if (element === window) { uid = 0; } else { if (typeof element._prototypeUID === "undefined") element._prototypeUID = [Element.Storage.UID++]; uid = element._prototypeUID[0]; } if (!Element.Storage[uid]) Element.Storage[uid] = $H(); return Element.Storage[uid]; }, store: function(element, key, value) { if (!(element = $(element))) return; if (arguments.length === 2) { Element.getStorage(element).update(key); } else { Element.getStorage(element).set(key, value); } return element; }, retrieve: function(element, key, defaultValue) { if (!(element = $(element))) return; var hash = Element.getStorage(element), value = hash.get(key); if (Object.isUndefined(value)) { hash.set(key, defaultValue); value = defaultValue; } return value; }, clone: function(element, deep) { if (!(element = $(element))) return; var clone = element.cloneNode(deep); clone._prototypeUID = void 0; if (deep) { var descendants = Element.select(clone, '*'), i = descendants.length; while (i--) { descendants[i]._prototypeUID = void 0; } } return Element.extend(clone); } }); Prototype._original_property = window.Sizzle; /*! * Sizzle CSS Selector Engine - v1.0 * Copyright 2009, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ (function(){ var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, done = 0, toString = Object.prototype.toString, hasDuplicate = false, baseHasDuplicate = true; [0, 0].sort(function(){ baseHasDuplicate = false; return 0; }); var Sizzle = function(selector, context, results, seed) { results = results || []; var origContext = context = context || document; if ( context.nodeType !== 1 && context.nodeType !== 9 ) { return []; } if ( !selector || typeof selector !== "string" ) { return results; } var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), soFar = selector; while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { soFar = m[3]; parts.push( m[1] ); if ( m[2] ) { extra = m[3]; break; } } if ( parts.length > 1 && origPOS.exec( selector ) ) { if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { set = posProcess( parts[0] + parts[1], context ); } else { set = Expr.relative[ parts[0] ] ? [ context ] : Sizzle( parts.shift(), context ); while ( parts.length ) { selector = parts.shift(); if ( Expr.relative[ selector ] ) selector += parts.shift(); set = posProcess( selector, set ); } } } else { if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { var ret = Sizzle.find( parts.shift(), context, contextXML ); context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; } if ( context ) { var ret = seed ? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; if ( parts.length > 0 ) { checkSet = makeArray(set); } else { prune = false; } while ( parts.length ) { var cur = parts.pop(), pop = cur; if ( !Expr.relative[ cur ] ) { cur = ""; } else { pop = parts.pop(); } if ( pop == null ) { pop = context; } Expr.relative[ cur ]( checkSet, pop, contextXML ); } } else { checkSet = parts = []; } } if ( !checkSet ) { checkSet = set; } if ( !checkSet ) { throw "Syntax error, unrecognized expression: " + (cur || selector); } if ( toString.call(checkSet) === "[object Array]" ) { if ( !prune ) { results.push.apply( results, checkSet ); } else if ( context && context.nodeType === 1 ) { for ( var i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { results.push( set[i] ); } } } else { for ( var i = 0; checkSet[i] != null; i++ ) { if ( checkSet[i] && checkSet[i].nodeType === 1 ) { results.push( set[i] ); } } } } else { makeArray( checkSet, results ); } if ( extra ) { Sizzle( extra, origContext, results, seed ); Sizzle.uniqueSort( results ); } return results; }; Sizzle.uniqueSort = function(results){ if ( sortOrder ) { hasDuplicate = baseHasDuplicate; results.sort(sortOrder); if ( hasDuplicate ) { for ( var i = 1; i < results.length; i++ ) { if ( results[i] === results[i-1] ) { results.splice(i--, 1); } } } } return results; }; Sizzle.matches = function(expr, set){ return Sizzle(expr, null, null, set); }; Sizzle.find = function(expr, context, isXML){ var set, match; if ( !expr ) { return []; } for ( var i = 0, l = Expr.order.length; i < l; i++ ) { var type = Expr.order[i], match; if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { var left = match[1]; match.splice(1,1); if ( left.substr( left.length - 1 ) !== "\\" ) { match[1] = (match[1] || "").replace(/\\/g, ""); set = Expr.find[ type ]( match, context, isXML ); if ( set != null ) { expr = expr.replace( Expr.match[ type ], "" ); break; } } } } if ( !set ) { set = context.getElementsByTagName("*"); } return {set: set, expr: expr}; }; Sizzle.filter = function(expr, set, inplace, not){ var old = expr, result = [], curLoop = set, match, anyFound, isXMLFilter = set && set[0] && isXML(set[0]); while ( expr && set.length ) { for ( var type in Expr.filter ) { if ( (match = Expr.match[ type ].exec( expr )) != null ) { var filter = Expr.filter[ type ], found, item; anyFound = false; if ( curLoop == result ) { result = []; } if ( Expr.preFilter[ type ] ) { match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); if ( !match ) { anyFound = found = true; } else if ( match === true ) { continue; } } if ( match ) { for ( var i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { found = filter( item, match, i, curLoop ); var pass = not ^ !!found; if ( inplace && found != null ) { if ( pass ) { anyFound = true; } else { curLoop[i] = false; } } else if ( pass ) { result.push( item ); anyFound = true; } } } } if ( found !== undefined ) { if ( !inplace ) { curLoop = result; } expr = expr.replace( Expr.match[ type ], "" ); if ( !anyFound ) { return []; } break; } } } if ( expr == old ) { if ( anyFound == null ) { throw "Syntax error, unrecognized expression: " + expr; } else { break; } } old = expr; } return curLoop; }; var Expr = Sizzle.selectors = { order: [ "ID", "NAME", "TAG" ], match: { ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ }, leftMatch: {}, attrMap: { "class": "className", "for": "htmlFor" }, attrHandle: { href: function(elem){ return elem.getAttribute("href"); } }, relative: { "+": function(checkSet, part, isXML){ var isPartStr = typeof part === "string", isTag = isPartStr && !/\W/.test(part), isPartStrNotTag = isPartStr && !isTag; if ( isTag && !isXML ) { part = part.toUpperCase(); } for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { if ( (elem = checkSet[i]) ) { while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? elem || false : elem === part; } } if ( isPartStrNotTag ) { Sizzle.filter( part, checkSet, true ); } }, ">": function(checkSet, part, isXML){ var isPartStr = typeof part === "string"; if ( isPartStr && !/\W/.test(part) ) { part = isXML ? part : part.toUpperCase(); for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { var parent = elem.parentNode; checkSet[i] = parent.nodeName === part ? parent : false; } } } else { for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { checkSet[i] = isPartStr ? elem.parentNode : elem.parentNode === part; } } if ( isPartStr ) { Sizzle.filter( part, checkSet, true ); } } }, "": function(checkSet, part, isXML){ var doneName = done++, checkFn = dirCheck; if ( !/\W/.test(part) ) { var nodeCheck = part = isXML ? part : part.toUpperCase(); checkFn = dirNodeCheck; } checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); }, "~": function(checkSet, part, isXML){ var doneName = done++, checkFn = dirCheck; if ( typeof part === "string" && !/\W/.test(part) ) { var nodeCheck = part = isXML ? part : part.toUpperCase(); checkFn = dirNodeCheck; } checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); } }, find: { ID: function(match, context, isXML){ if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? [m] : []; } }, NAME: function(match, context, isXML){ if ( typeof context.getElementsByName !== "undefined" ) { var ret = [], results = context.getElementsByName(match[1]); for ( var i = 0, l = results.length; i < l; i++ ) { if ( results[i].getAttribute("name") === match[1] ) { ret.push( results[i] ); } } return ret.length === 0 ? null : ret; } }, TAG: function(match, context){ return context.getElementsByTagName(match[1]); } }, preFilter: { CLASS: function(match, curLoop, inplace, result, not, isXML){ match = " " + match[1].replace(/\\/g, "") + " "; if ( isXML ) { return match; } for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { if ( elem ) { if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { if ( !inplace ) result.push( elem ); } else if ( inplace ) { curLoop[i] = false; } } } return false; }, ID: function(match){ return match[1].replace(/\\/g, ""); }, TAG: function(match, curLoop){ for ( var i = 0; curLoop[i] === false; i++ ){} return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); }, CHILD: function(match){ if ( match[1] == "nth" ) { var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); match[2] = (test[1] + (test[2] || 1)) - 0; match[3] = test[3] - 0; } match[0] = done++; return match; }, ATTR: function(match, curLoop, inplace, result, not, isXML){ var name = match[1].replace(/\\/g, ""); if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } return match; }, PSEUDO: function(match, curLoop, inplace, result, not){ if ( match[1] === "not" ) { if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { match[3] = Sizzle(match[3], null, null, curLoop); } else { var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); if ( !inplace ) { result.push.apply( result, ret ); } return false; } } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { return true; } return match; }, POS: function(match){ match.unshift( true ); return match; } }, filters: { enabled: function(elem){ return elem.disabled === false && elem.type !== "hidden"; }, disabled: function(elem){ return elem.disabled === true; }, checked: function(elem){ return elem.checked === true; }, selected: function(elem){ elem.parentNode.selectedIndex; return elem.selected === true; }, parent: function(elem){ return !!elem.firstChild; }, empty: function(elem){ return !elem.firstChild; }, has: function(elem, i, match){ return !!Sizzle( match[3], elem ).length; }, header: function(elem){ return /h\d/i.test( elem.nodeName ); }, text: function(elem){ return "text" === elem.type; }, radio: function(elem){ return "radio" === elem.type; }, checkbox: function(elem){ return "checkbox" === elem.type; }, file: function(elem){ return "file" === elem.type; }, password: function(elem){ return "password" === elem.type; }, submit: function(elem){ return "submit" === elem.type; }, image: function(elem){ return "image" === elem.type; }, reset: function(elem){ return "reset" === elem.type; }, button: function(elem){ return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; }, input: function(elem){ return /input|select|textarea|button/i.test(elem.nodeName); } }, setFilters: { first: function(elem, i){ return i === 0; }, last: function(elem, i, match, array){ return i === array.length - 1; }, even: function(elem, i){ return i % 2 === 0; }, odd: function(elem, i){ return i % 2 === 1; }, lt: function(elem, i, match){ return i < match[3] - 0; }, gt: function(elem, i, match){ return i > match[3] - 0; }, nth: function(elem, i, match){ return match[3] - 0 == i; }, eq: function(elem, i, match){ return match[3] - 0 == i; } }, filter: { PSEUDO: function(elem, match, i, array){ var name = match[1], filter = Expr.filters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } else if ( name === "contains" ) { return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; } else if ( name === "not" ) { var not = match[3]; for ( var i = 0, l = not.length; i < l; i++ ) { if ( not[i] === elem ) { return false; } } return true; } }, CHILD: function(elem, match){ var type = match[1], node = elem; switch (type) { case 'only': case 'first': while ( (node = node.previousSibling) ) { if ( node.nodeType === 1 ) return false; } if ( type == 'first') return true; node = elem; case 'last': while ( (node = node.nextSibling) ) { if ( node.nodeType === 1 ) return false; } return true; case 'nth': var first = match[2], last = match[3]; if ( first == 1 && last == 0 ) { return true; } var doneName = match[0], parent = elem.parentNode; if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { var count = 0; for ( node = parent.firstChild; node; node = node.nextSibling ) { if ( node.nodeType === 1 ) { node.nodeIndex = ++count; } } parent.sizcache = doneName; } var diff = elem.nodeIndex - last; if ( first == 0 ) { return diff == 0; } else { return ( diff % first == 0 && diff / first >= 0 ); } } }, ID: function(elem, match){ return elem.nodeType === 1 && elem.getAttribute("id") === match; }, TAG: function(elem, match){ return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; }, CLASS: function(elem, match){ return (" " + (elem.className || elem.getAttribute("class")) + " ") .indexOf( match ) > -1; }, ATTR: function(elem, match){ var name = match[1], result = Expr.attrHandle[ name ] ? Expr.attrHandle[ name ]( elem ) : elem[ name ] != null ? elem[ name ] : elem.getAttribute( name ), value = result + "", type = match[2], check = match[4]; return result == null ? type === "!=" : type === "=" ? value === check : type === "*=" ? value.indexOf(check) >= 0 : type === "~=" ? (" " + value + " ").indexOf(check) >= 0 : !check ? value && result !== false : type === "!=" ? value != check : type === "^=" ? value.indexOf(check) === 0 : type === "$=" ? value.substr(value.length - check.length) === check : type === "|=" ? value === check || value.substr(0, check.length + 1) === check + "-" : false; }, POS: function(elem, match, i, array){ var name = match[2], filter = Expr.setFilters[ name ]; if ( filter ) { return filter( elem, i, match, array ); } } } }; var origPOS = Expr.match.POS; for ( var type in Expr.match ) { Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); } var makeArray = function(array, results) { array = Array.prototype.slice.call( array, 0 ); if ( results ) { results.push.apply( results, array ); return results; } return array; }; try { Array.prototype.slice.call( document.documentElement.childNodes, 0 ); } catch(e){ makeArray = function(array, results) { var ret = results || []; if ( toString.call(array) === "[object Array]" ) { Array.prototype.push.apply( ret, array ); } else { if ( typeof array.length === "number" ) { for ( var i = 0, l = array.length; i < l; i++ ) { ret.push( array[i] ); } } else { for ( var i = 0; array[i]; i++ ) { ret.push( array[i] ); } } } return ret; }; } var sortOrder; if ( document.documentElement.compareDocumentPosition ) { sortOrder = function( a, b ) { if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { if ( a == b ) { hasDuplicate = true; } return 0; } var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; if ( ret === 0 ) { hasDuplicate = true; } return ret; }; } else if ( "sourceIndex" in document.documentElement ) { sortOrder = function( a, b ) { if ( !a.sourceIndex || !b.sourceIndex ) { if ( a == b ) { hasDuplicate = true; } return 0; } var ret = a.sourceIndex - b.sourceIndex; if ( ret === 0 ) { hasDuplicate = true; } return ret; }; } else if ( document.createRange ) { sortOrder = function( a, b ) { if ( !a.ownerDocument || !b.ownerDocument ) { if ( a == b ) { hasDuplicate = true; } return 0; } var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); aRange.setStart(a, 0); aRange.setEnd(a, 0); bRange.setStart(b, 0); bRange.setEnd(b, 0); var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); if ( ret === 0 ) { hasDuplicate = true; } return ret; }; } (function(){ var form = document.createElement("div"), id = "script" + (new Date).getTime(); form.innerHTML = ""; var root = document.documentElement; root.insertBefore( form, root.firstChild ); if ( !!document.getElementById( id ) ) { Expr.find.ID = function(match, context, isXML){ if ( typeof context.getElementById !== "undefined" && !isXML ) { var m = context.getElementById(match[1]); return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; } }; Expr.filter.ID = function(elem, match){ var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return elem.nodeType === 1 && node && node.nodeValue === match; }; } root.removeChild( form ); root = form = null; // release memory in IE })(); (function(){ var div = document.createElement("div"); div.appendChild( document.createComment("") ); if ( div.getElementsByTagName("*").length > 0 ) { Expr.find.TAG = function(match, context){ var results = context.getElementsByTagName(match[1]); if ( match[1] === "*" ) { var tmp = []; for ( var i = 0; results[i]; i++ ) { if ( results[i].nodeType === 1 ) { tmp.push( results[i] ); } } results = tmp; } return results; }; } div.innerHTML = ""; if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && div.firstChild.getAttribute("href") !== "#" ) { Expr.attrHandle.href = function(elem){ return elem.getAttribute("href", 2); }; } div = null; // release memory in IE })(); if ( document.querySelectorAll ) (function(){ var oldSizzle = Sizzle, div = document.createElement("div"); div.innerHTML = "

    "; if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { return; } Sizzle = function(query, context, extra, seed){ context = context || document; if ( !seed && context.nodeType === 9 && !isXML(context) ) { try { return makeArray( context.querySelectorAll(query), extra ); } catch(e){} } return oldSizzle(query, context, extra, seed); }; for ( var prop in oldSizzle ) { Sizzle[ prop ] = oldSizzle[ prop ]; } div = null; // release memory in IE })(); if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ var div = document.createElement("div"); div.innerHTML = "
    "; if ( div.getElementsByClassName("e").length === 0 ) return; div.lastChild.className = "e"; if ( div.getElementsByClassName("e").length === 1 ) return; Expr.order.splice(1, 0, "CLASS"); Expr.find.CLASS = function(match, context, isXML) { if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { return context.getElementsByClassName(match[1]); } }; div = null; // release memory in IE })(); function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { var sibDir = dir == "previousSibling" && !isXML; for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { if ( sibDir && elem.nodeType === 1 ){ elem.sizcache = doneName; elem.sizset = i; } elem = elem[dir]; var match = false; while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 && !isXML ){ elem.sizcache = doneName; elem.sizset = i; } if ( elem.nodeName === cur ) { match = elem; break; } elem = elem[dir]; } checkSet[i] = match; } } } function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { var sibDir = dir == "previousSibling" && !isXML; for ( var i = 0, l = checkSet.length; i < l; i++ ) { var elem = checkSet[i]; if ( elem ) { if ( sibDir && elem.nodeType === 1 ) { elem.sizcache = doneName; elem.sizset = i; } elem = elem[dir]; var match = false; while ( elem ) { if ( elem.sizcache === doneName ) { match = checkSet[elem.sizset]; break; } if ( elem.nodeType === 1 ) { if ( !isXML ) { elem.sizcache = doneName; elem.sizset = i; } if ( typeof cur !== "string" ) { if ( elem === cur ) { match = true; break; } } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { match = elem; break; } } elem = elem[dir]; } checkSet[i] = match; } } } var contains = document.compareDocumentPosition ? function(a, b){ return a.compareDocumentPosition(b) & 16; } : function(a, b){ return a !== b && (a.contains ? a.contains(b) : true); }; var isXML = function(elem){ return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; }; var posProcess = function(selector, context){ var tmpSet = [], later = "", match, root = context.nodeType ? [context] : context; while ( (match = Expr.match.PSEUDO.exec( selector )) ) { later += match[0]; selector = selector.replace( Expr.match.PSEUDO, "" ); } selector = Expr.relative[selector] ? selector + "*" : selector; for ( var i = 0, l = root.length; i < l; i++ ) { Sizzle( selector, root[i], tmpSet ); } return Sizzle.filter( later, tmpSet ); }; window.Sizzle = Sizzle; })(); Prototype.Selector = (function(engine) { function extend(elements) { for (var i = 0, length = elements.length; i < length; i++) { Element.extend(elements[i]); } return elements; } function select(selector, scope) { return extend(engine(selector, scope || document)); } function match(element, selector) { return engine.matches(selector, [element]).length == 1; } return { engine: engine, select: select, match: match }; })(Sizzle); window.Sizzle = Prototype._original_property; delete Prototype._original_property; (function() { function toDecimal(pctString) { var match = pctString.match(/^(\d+)%?$/i); if (!match) return null; return (Number(match[1]) / 100); } function getPixelValue(value, property) { if (Object.isElement(value)) { element = value; value = element.getStyle(property); } if (value === null) { return null; } if ((/^\d+(\.\d+)?(px)?$/i).test(value)) { return window.parseFloat(value); } if (/\d/.test(value) && element.runtimeStyle) { var style = element.style.left, rStyle = element.runtimeStyle.left; element.runtimeStyle.left = element.currentStyle.left; element.style.left = value || 0; value = element.style.pixelLeft; element.style.left = style; element.runtimeStyle.left = rStyle; return value; } if (value.include('%')) { var decimal = toDecimal(value); var whole; if (property.include('left') || property.include('right') || property.include('width')) { whole = $(element.parentNode).measure('width'); } else if (property.include('top') || property.include('bottom') || property.include('height')) { whole = $(element.parentNode).measure('height'); } return whole * decimal; } return 0; } function toCSSPixels(number) { if (Object.isString(number) && number.endsWith('px')) { return number; } return number + 'px'; } function isDisplayed(element) { var originalElement = element; while (element && element.parentNode) { var display = element.getStyle('display'); if (display === 'none') { return false; } element = $(element.parentNode); } return true; } var hasLayout = Prototype.K; if ('currentStyle' in document.documentElement) { hasLayout = function(element) { if (!element.currentStyle.hasLayout) { element.style.zoom = 1; } return element; }; } function cssNameFor(key) { if (key.includes('border')) return key + '-width'; return key; } Element.Layout = Class.create(Hash, { initialize: function($super, element, preCompute) { $super(); this.element = $(element); Element.Layout.PROPERTIES.each( function(property) { this._set(property, null); }, this); if (preCompute) { this._preComputing = true; this._begin(); Element.Layout.PROPERTIES.each( this._compute, this ); this._end(); this._preComputing = false; } }, _set: function(property, value) { return Hash.prototype.set.call(this, property, value); }, set: function(property, value) { throw "Properties of Element.Layout are read-only."; }, get: function($super, property) { var value = $super(property); return value === null ? this._compute(property) : value; }, _begin: function() { if (this._prepared) return; var element = this.element; if (isDisplayed(element)) { this._prepared = true; return; } var originalStyles = { position: element.style.position || '', width: element.style.width || '', visibility: element.style.visibility || '', display: element.style.display || '' }; element.store('prototype_original_styles', originalStyles); var position = element.getStyle('position'), width = element.getStyle('width'); element.setStyle({ position: 'absolute', visibility: 'hidden', display: 'block' }); var positionedWidth = element.getStyle('width'); var newWidth; if (width && (positionedWidth === width)) { newWidth = window.parseInt(width, 10); } else if (width && (position === 'absolute' || position === 'fixed')) { newWidth = window.parseInt(width, 10); } else { var parent = element.parentNode, pLayout = $(parent).getLayout(); newWidth = pLayout.get('width') - this.get('margin-left') - this.get('border-left') - this.get('padding-left') - this.get('padding-right') - this.get('border-right') - this.get('margin-right'); } element.setStyle({ width: newWidth + 'px' }); this._prepared = true; }, _end: function() { var element = this.element; var originalStyles = element.retrieve('prototype_original_styles'); element.store('prototype_original_styles', null); element.setStyle(originalStyles); this._prepared = false; }, _compute: function(property) { var COMPUTATIONS = Element.Layout.COMPUTATIONS; if (!(property in COMPUTATIONS)) { throw "Property not found."; } return this._set(property, COMPUTATIONS[property].call(this, this.element)); }, toCSS: function() { var args = $A(arguments); var keys = (args.length === 0) ? Element.Layout.PROPERTIES : args.join(' ').split(' '); var css = {}; keys.each( function(key) { if (!Element.Layout.PROPERTIES.include(key)) return; if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; var value = this.get(key); if (value) css[cssNameFor(key)] = value + 'px'; }); return css; }, inspect: function() { return "#"; } }); Object.extend(Element.Layout, { PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), COMPUTATIONS: { 'height': function(element) { if (!this._preComputing) this._begin(); var bHeight = this.get('border-box-height'); if (bHeight <= 0) return 0; var bTop = this.get('border-top'), bBottom = this.get('border-bottom'); var pTop = this.get('padding-top'), pBottom = this.get('padding-bottom'); if (!this._preComputing) this._end(); return bHeight - bTop - bBottom - pTop - pBottom; }, 'width': function(element) { if (!this._preComputing) this._begin(); var bWidth = this.get('border-box-width'); if (bWidth <= 0) return 0; var bLeft = this.get('border-left'), bRight = this.get('border-right'); var pLeft = this.get('padding-left'), pRight = this.get('padding-right'); if (!this._preComputing) this._end(); return bWidth - bLeft - bRight - pLeft - pRight; }, 'padding-box-height': function(element) { var height = this.get('height'), pTop = this.get('padding-top'), pBottom = this.get('padding-bottom'); return height + pTop + pBottom; }, 'padding-box-width': function(element) { var width = this.get('width'), pLeft = this.get('padding-left'), pRight = this.get('padding-right'); return width + pLeft + pRight; }, 'border-box-height': function(element) { return element.offsetHeight; }, 'border-box-width': function(element) { return element.offsetWidth; }, 'margin-box-height': function(element) { var bHeight = this.get('border-box-height'), mTop = this.get('margin-top'), mBottom = this.get('margin-bottom'); if (bHeight <= 0) return 0; return bHeight + mTop + mBottom; }, 'margin-box-width': function(element) { var bWidth = this.get('border-box-width'), mLeft = this.get('margin-left'), mRight = this.get('margin-right'); if (bWidth <= 0) return 0; return bWidth + mLeft + mRight; }, 'top': function(element) { var offset = element.positionedOffset(); return offset.top; }, 'bottom': function(element) { var offset = element.positionedOffset(), parent = element.getOffsetParent(), pHeight = parent.measure('height'); var mHeight = this.get('border-box-height'); return pHeight - mHeight - offset.top; }, 'left': function(element) { var offset = element.positionedOffset(); return offset.left; }, 'right': function(element) { var offset = element.positionedOffset(), parent = element.getOffsetParent(), pWidth = parent.measure('width'); var mWidth = this.get('border-box-width'); return pWidth - mWidth - offset.left; }, 'padding-top': function(element) { return getPixelValue(element, 'paddingTop'); }, 'padding-bottom': function(element) { return getPixelValue(element, 'paddingBottom'); }, 'padding-left': function(element) { return getPixelValue(element, 'paddingLeft'); }, 'padding-right': function(element) { return getPixelValue(element, 'paddingRight'); }, 'border-top': function(element) { return Object.isNumber(element.clientTop) ? element.clientTop : getPixelValue(element, 'borderTopWidth'); }, 'border-bottom': function(element) { return Object.isNumber(element.clientBottom) ? element.clientBottom : getPixelValue(element, 'borderBottomWidth'); }, 'border-left': function(element) { return Object.isNumber(element.clientLeft) ? element.clientLeft : getPixelValue(element, 'borderLeftWidth'); }, 'border-right': function(element) { return Object.isNumber(element.clientRight) ? element.clientRight : getPixelValue(element, 'borderRightWidth'); }, 'margin-top': function(element) { return getPixelValue(element, 'marginTop'); }, 'margin-bottom': function(element) { return getPixelValue(element, 'marginBottom'); }, 'margin-left': function(element) { return getPixelValue(element, 'marginLeft'); }, 'margin-right': function(element) { return getPixelValue(element, 'marginRight'); } } }); if ('getBoundingClientRect' in document.documentElement) { Object.extend(Element.Layout.COMPUTATIONS, { 'right': function(element) { var parent = hasLayout(element.getOffsetParent()); var rect = element.getBoundingClientRect(), pRect = parent.getBoundingClientRect(); return (pRect.right - rect.right).round(); }, 'bottom': function(element) { var parent = hasLayout(element.getOffsetParent()); var rect = element.getBoundingClientRect(), pRect = parent.getBoundingClientRect(); return (pRect.bottom - rect.bottom).round(); } }); } Element.Offset = Class.create({ initialize: function(left, top) { this.left = left.round(); this.top = top.round(); this[0] = this.left; this[1] = this.top; }, relativeTo: function(offset) { return new Element.Offset( this.left - offset.left, this.top - offset.top ); }, inspect: function() { return "#".interpolate(this); }, toString: function() { return "[#{left}, #{top}]".interpolate(this); }, toArray: function() { return [this.left, this.top]; } }); function getLayout(element, preCompute) { return new Element.Layout(element, preCompute); } function measure(element, property) { return $(element).getLayout().get(property); } function getDimensions(element) { var layout = $(element).getLayout(); return { width: layout.get('width'), height: layout.get('height') }; } function getOffsetParent(element) { if (element.offsetParent) return $(element.offsetParent); if (element === document.body) return $(element); while ((element = element.parentNode) && element !== document.body) { if (Element.getStyle(element, 'position') !== 'static') { return (element.nodeName === 'HTML') ? $(document.body) : $(element); } } return $(document.body); } function cumulativeOffset(element) { var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; } while (element); return new Element.Offset(valueL, valueT); } function positionedOffset(element) { var layout = element.getLayout(); var valueT = 0, valueL = 0; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { if (isBody(element)) break; var p = Element.getStyle(element, 'position'); if (p !== 'static') break; } } while (element); valueL -= layout.get('margin-top'); valueT -= layout.get('margin-left'); return new Element.Offset(valueL, valueT); } function cumulativeScrollOffset(element) { var valueT = 0, valueL = 0; do { valueT += element.scrollTop || 0; valueL += element.scrollLeft || 0; element = element.parentNode; } while (element); return new Element.Offset(valueL, valueT); } function viewportOffset(forElement) { var valueT = 0, valueL = 0, docBody = document.body; var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; if (element.offsetParent == docBody && Element.getStyle(element, 'position') == 'absolute') break; } while (element = element.offsetParent); element = forElement; do { if (element != docBody) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } } while (element = element.parentNode); return new Element.Offset(valueL, valueT); } function absolutize(element) { element = $(element); if (Element.getStyle(element, 'position') === 'absolute') { return element; } var offsetParent = getOffsetParent(element); var eOffset = element.viewportOffset(), pOffset = offsetParent.viewportOffset(); var offset = eOffset.relativeTo(pOffset); var layout = element.get('layout'); element.store('prototype_absolutize_original_styles', { left: element.getStyle('left'), top: element.getStyle('top'), width: element.getStyle('width'), height: element.getStyle('height') }); element.setStyle({ position: 'absolute', top: offset.top + 'px', left: offset.left + 'px', width: layout.get('width') + 'px', height: layout.get('height') + 'px' }); return element; } function relativize(element) { element = $(element); if (Element.getStyle(element, 'position') === 'relative') { return element; } var originalStyles = element.retrieve('prototype_absolutize_original_styles'); if (originalStyles) element.setStyle(originalStyles); return element; } Element.addMethods({ getLayout: getLayout, measure: measure, getDimensions: getDimensions, getOffsetParent: getOffsetParent, cumulativeOffset: cumulativeOffset, positionedOffset: positionedOffset, cumulativeScrollOffset: cumulativeScrollOffset, viewportOffset: viewportOffset, absolutize: absolutize, relativize: relativize }); function isBody(element) { return element.nodeName.toUpperCase() === 'BODY'; } if ('getBoundingClientRect' in document.documentElement) { Element.addMethods({ viewportOffset: function(element) { element = $(element); var rect = element.getBoundingClientRect(), docEl = document.documentElement; return new Element.Offset(rect.left - docEl.clientLeft, rect.top - docEl.clientTop); }, cumulativeOffset: function(element) { element = $(element); var docOffset = $(document.documentElement).viewportOffset(), elementOffset = element.viewportOffset(); return elementOffset.relativeTo(docOffset); }, positionedOffset: function(element) { element = $(element); var parent = element.getOffsetParent(); if (element.offsetParent && element.offsetParent.nodeName.toUpperCase() === 'HTML') { return positionedOffset(element); } var eOffset = element.viewportOffset(), pOffset = isBody(parent) ? viewportOffset(parent) : parent.viewportOffset(); var retOffset = eOffset.relativeTo(pOffset); var layout = element.getLayout(); var top = retOffset.top - layout.get('margin-top'); var left = retOffset.left - layout.get('margin-left'); return new Element.Offset(left, top); } }); } })(); window.$$ = function() { var expression = $A(arguments).join(', '); return Prototype.Selector.select(expression, document); }; if (!Prototype.Selector.find) { Prototype.Selector.find = function(elements, expression, index) { if (Object.isUndefined(index)) index = 0; var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; for (i = 0; i < length; i++) { if (match(elements[i], expression) && index == matchIndex++) { return Element.extend(elements[i]); } } } } var Form = { reset: function(form) { form = $(form); form.reset(); return form; }, serializeElements: function(elements, options) { if (typeof options != 'object') options = { hash: !!options }; else if (Object.isUndefined(options.hash)) options.hash = true; var key, value, submitted = false, submit = options.submit; var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { if (key in result) { if (!Object.isArray(result[key])) result[key] = [result[key]]; result[key].push(value); } else result[key] = value; } } return result; }); return options.hash ? data : Object.toQueryString(data); } }; Form.Methods = { serialize: function(form, options) { return Form.serializeElements(Form.getElements(form), options); }, getElements: function(form) { var elements = $(form).getElementsByTagName('*'), element, arr = [ ], serializers = Form.Element.Serializers; for (var i = 0; element = elements[i]; i++) { arr.push(element); } return arr.inject([], function(elements, child) { if (serializers[child.tagName.toLowerCase()]) elements.push(Element.extend(child)); return elements; }) }, getInputs: function(form, typeName, name) { form = $(form); var inputs = form.getElementsByTagName('input'); if (!typeName && !name) return $A(inputs).map(Element.extend); for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { var input = inputs[i]; if ((typeName && input.type != typeName) || (name && input.name != name)) continue; matchingInputs.push(Element.extend(input)); } return matchingInputs; }, disable: function(form) { form = $(form); Form.getElements(form).invoke('disable'); return form; }, enable: function(form) { form = $(form); Form.getElements(form).invoke('enable'); return form; }, findFirstElement: function(form) { var elements = $(form).getElements().findAll(function(element) { return 'hidden' != element.type && !element.disabled; }); var firstByIndex = elements.findAll(function(element) { return element.hasAttribute('tabIndex') && element.tabIndex >= 0; }).sortBy(function(element) { return element.tabIndex }).first(); return firstByIndex ? firstByIndex : elements.find(function(element) { return /^(?:input|select|textarea)$/i.test(element.tagName); }); }, focusFirstElement: function(form) { form = $(form); form.findFirstElement().activate(); return form; }, request: function(form, options) { form = $(form), options = Object.clone(options || { }); var params = options.parameters, action = form.readAttribute('action') || ''; if (action.blank()) action = window.location.href; options.parameters = form.serialize(true); if (params) { if (Object.isString(params)) params = params.toQueryParams(); Object.extend(options.parameters, params); } if (form.hasAttribute('method') && !options.method) options.method = form.method; return new Ajax.Request(action, options); } }; /*--------------------------------------------------------------------------*/ Form.Element = { focus: function(element) { $(element).focus(); return element; }, select: function(element) { $(element).select(); return element; } }; Form.Element.Methods = { serialize: function(element) { element = $(element); if (!element.disabled && element.name) { var value = element.getValue(); if (value != undefined) { var pair = { }; pair[element.name] = value; return Object.toQueryString(pair); } } return ''; }, getValue: function(element) { element = $(element); var method = element.tagName.toLowerCase(); return Form.Element.Serializers[method](element); }, setValue: function(element, value) { element = $(element); var method = element.tagName.toLowerCase(); Form.Element.Serializers[method](element, value); return element; }, clear: function(element) { $(element).value = ''; return element; }, present: function(element) { return $(element).value != ''; }, activate: function(element) { element = $(element); try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || !(/^(?:button|reset|submit)$/i.test(element.type)))) element.select(); } catch (e) { } return element; }, disable: function(element) { element = $(element); element.disabled = true; return element; }, enable: function(element) { element = $(element); element.disabled = false; return element; } }; /*--------------------------------------------------------------------------*/ var Field = Form.Element; var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ Form.Element.Serializers = { input: function(element, value) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': return Form.Element.Serializers.inputSelector(element, value); default: return Form.Element.Serializers.textarea(element, value); } }, inputSelector: function(element, value) { if (Object.isUndefined(value)) return element.checked ? element.value : null; else element.checked = !!value; }, textarea: function(element, value) { if (Object.isUndefined(value)) return element.value; else element.value = value; }, select: function(element, value) { if (Object.isUndefined(value)) return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); else { var opt, currentValue, single = !Object.isArray(value); for (var i = 0, length = element.length; i < length; i++) { opt = element.options[i]; currentValue = this.optionValue(opt); if (single) { if (currentValue == value) { opt.selected = true; return; } } else opt.selected = value.include(currentValue); } } }, selectOne: function(element) { var index = element.selectedIndex; return index >= 0 ? this.optionValue(element.options[index]) : null; }, selectMany: function(element) { var values, length = element.length; if (!length) return null; for (var i = 0, values = []; i < length; i++) { var opt = element.options[i]; if (opt.selected) values.push(this.optionValue(opt)); } return values; }, optionValue: function(opt) { return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } }; /*--------------------------------------------------------------------------*/ Abstract.TimedObserver = Class.create(PeriodicalExecuter, { initialize: function($super, element, frequency, callback) { $super(callback, frequency); this.element = $(element); this.lastValue = this.getValue(); }, execute: function() { var value = this.getValue(); if (Object.isString(this.lastValue) && Object.isString(value) ? this.lastValue != value : String(this.lastValue) != String(value)) { this.callback(this.element, value); this.lastValue = value; } } }); Form.Element.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(Abstract.TimedObserver, { getValue: function() { return Form.serialize(this.element); } }); /*--------------------------------------------------------------------------*/ Abstract.EventObserver = Class.create({ initialize: function(element, callback) { this.element = $(element); this.callback = callback; this.lastValue = this.getValue(); if (this.element.tagName.toLowerCase() == 'form') this.registerFormCallbacks(); else this.registerCallback(this.element); }, onElementEvent: function() { var value = this.getValue(); if (this.lastValue != value) { this.callback(this.element, value); this.lastValue = value; } }, registerFormCallbacks: function() { Form.getElements(this.element).each(this.registerCallback, this); }, registerCallback: function(element) { if (element.type) { switch (element.type.toLowerCase()) { case 'checkbox': case 'radio': Event.observe(element, 'click', this.onElementEvent.bind(this)); break; default: Event.observe(element, 'change', this.onElementEvent.bind(this)); break; } } } }); Form.Element.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.Element.getValue(this.element); } }); Form.EventObserver = Class.create(Abstract.EventObserver, { getValue: function() { return Form.serialize(this.element); } }); (function() { var Event = { KEY_BACKSPACE: 8, KEY_TAB: 9, KEY_RETURN: 13, KEY_ESC: 27, KEY_LEFT: 37, KEY_UP: 38, KEY_RIGHT: 39, KEY_DOWN: 40, KEY_DELETE: 46, KEY_HOME: 36, KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, KEY_INSERT: 45, cache: {} }; var docEl = document.documentElement; var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl && 'onmouseleave' in docEl; var _isButton; if (Prototype.Browser.IE) { var buttonMap = { 0: 1, 1: 4, 2: 2 }; _isButton = function(event, code) { return event.button === buttonMap[code]; }; } else if (Prototype.Browser.WebKit) { _isButton = function(event, code) { switch (code) { case 0: return event.which == 1 && !event.metaKey; case 1: return event.which == 1 && event.metaKey; default: return false; } }; } else { _isButton = function(event, code) { return event.which ? (event.which === code + 1) : (event.button === code); }; } function isLeftClick(event) { return _isButton(event, 0) } function isMiddleClick(event) { return _isButton(event, 1) } function isRightClick(event) { return _isButton(event, 2) } function element(event) { event = Event.extend(event); var node = event.target, type = event.type, currentTarget = event.currentTarget; if (currentTarget && currentTarget.tagName) { if (type === 'load' || type === 'error' || (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' && currentTarget.type === 'radio')) node = currentTarget; } if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; return Element.extend(node); } function findElement(event, expression) { var element = Event.element(event); if (!expression) return element; while (element) { if (Prototype.Selector.match(element, expression)) { return Element.extend(element); } element = element.parentNode; } } function pointer(event) { return { x: pointerX(event), y: pointerY(event) }; } function pointerX(event) { var docElement = document.documentElement, body = document.body || { scrollLeft: 0 }; return event.pageX || (event.clientX + (docElement.scrollLeft || body.scrollLeft) - (docElement.clientLeft || 0)); } function pointerY(event) { var docElement = document.documentElement, body = document.body || { scrollTop: 0 }; return event.pageY || (event.clientY + (docElement.scrollTop || body.scrollTop) - (docElement.clientTop || 0)); } function stop(event) { Event.extend(event); event.preventDefault(); event.stopPropagation(); event.stopped = true; } Event.Methods = { isLeftClick: isLeftClick, isMiddleClick: isMiddleClick, isRightClick: isRightClick, element: element, findElement: findElement, pointer: pointer, pointerX: pointerX, pointerY: pointerY, stop: stop }; var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { m[name] = Event.Methods[name].methodize(); return m; }); if (Prototype.Browser.IE) { function _relatedTarget(event) { var element; switch (event.type) { case 'mouseover': element = event.fromElement; break; case 'mouseout': element = event.toElement; break; default: return null; } return Element.extend(element); } Object.extend(methods, { stopPropagation: function() { this.cancelBubble = true }, preventDefault: function() { this.returnValue = false }, inspect: function() { return '[object Event]' } }); Event.extend = function(event, element) { if (!event) return false; if (event._extendedByPrototype) return event; event._extendedByPrototype = Prototype.emptyFunction; var pointer = Event.pointer(event); Object.extend(event, { target: event.srcElement || element, relatedTarget: _relatedTarget(event), pageX: pointer.x, pageY: pointer.y }); return Object.extend(event, methods); }; } else { Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; Object.extend(Event.prototype, methods); Event.extend = Prototype.K; } function _createResponder(element, eventName, handler) { var registry = Element.retrieve(element, 'prototype_event_registry'); if (Object.isUndefined(registry)) { CACHE.push(element); registry = Element.retrieve(element, 'prototype_event_registry', $H()); } var respondersForEvent = registry.get(eventName); if (Object.isUndefined(respondersForEvent)) { respondersForEvent = []; registry.set(eventName, respondersForEvent); } if (respondersForEvent.pluck('handler').include(handler)) return false; var responder; if (eventName.include(":")) { responder = function(event) { if (Object.isUndefined(event.eventName)) return false; if (event.eventName !== eventName) return false; Event.extend(event, element); handler.call(element, event); }; } else { if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && (eventName === "mouseenter" || eventName === "mouseleave")) { if (eventName === "mouseenter" || eventName === "mouseleave") { responder = function(event) { Event.extend(event, element); var parent = event.relatedTarget; while (parent && parent !== element) { try { parent = parent.parentNode; } catch(e) { parent = element; } } if (parent === element) return; handler.call(element, event); }; } } else { responder = function(event) { Event.extend(event, element); handler.call(element, event); }; } } responder.handler = handler; respondersForEvent.push(responder); return responder; } function _destroyCache() { for (var i = 0, length = CACHE.length; i < length; i++) { Event.stopObserving(CACHE[i]); CACHE[i] = null; } } var CACHE = []; if (Prototype.Browser.IE) window.attachEvent('onunload', _destroyCache); if (Prototype.Browser.WebKit) window.addEventListener('unload', Prototype.emptyFunction, false); var _getDOMEventName = Prototype.K, translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { _getDOMEventName = function(eventName) { return (translations[eventName] || eventName); }; } function observe(element, eventName, handler) { element = $(element); var responder = _createResponder(element, eventName, handler); if (!responder) return element; if (eventName.include(':')) { if (element.addEventListener) element.addEventListener("dataavailable", responder, false); else { element.attachEvent("ondataavailable", responder); element.attachEvent("onfilterchange", responder); } } else { var actualEventName = _getDOMEventName(eventName); if (element.addEventListener) element.addEventListener(actualEventName, responder, false); else element.attachEvent("on" + actualEventName, responder); } return element; } function stopObserving(element, eventName, handler) { element = $(element); var registry = Element.retrieve(element, 'prototype_event_registry'); if (!registry) return element; if (!eventName) { registry.each( function(pair) { var eventName = pair.key; stopObserving(element, eventName); }); return element; } var responders = registry.get(eventName); if (!responders) return element; if (!handler) { responders.each(function(r) { stopObserving(element, eventName, r.handler); }); return element; } var responder = responders.find( function(r) { return r.handler === handler; }); if (!responder) return element; if (eventName.include(':')) { if (element.removeEventListener) element.removeEventListener("dataavailable", responder, false); else { element.detachEvent("ondataavailable", responder); element.detachEvent("onfilterchange", responder); } } else { var actualEventName = _getDOMEventName(eventName); if (element.removeEventListener) element.removeEventListener(actualEventName, responder, false); else element.detachEvent('on' + actualEventName, responder); } registry.set(eventName, responders.without(responder)); return element; } function fire(element, eventName, memo, bubble) { element = $(element); if (Object.isUndefined(bubble)) bubble = true; if (element == document && document.createEvent && !element.dispatchEvent) element = document.documentElement; var event; if (document.createEvent) { event = document.createEvent('HTMLEvents'); event.initEvent('dataavailable', true, true); } else { event = document.createEventObject(); event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; } event.eventName = eventName; event.memo = memo || { }; if (document.createEvent) element.dispatchEvent(event); else element.fireEvent(event.eventType, event); return Event.extend(event); } Event.Handler = Class.create({ initialize: function(element, eventName, selector, callback) { this.element = $(element); this.eventName = eventName; this.selector = selector; this.callback = callback; this.handler = this.handleEvent.bind(this); }, start: function() { Event.observe(this.element, this.eventName, this.handler); return this; }, stop: function() { Event.stopObserving(this.element, this.eventName, this.handler); return this; }, handleEvent: function(event) { var element = this.selector ? event.findElement(this.selector) : this.element; if (element) this.callback.call(element, event, element); } }); function on(element, eventName, selector, callback) { element = $(element); if (Object.isFunction(selector) && Object.isUndefined(callback)) { callback = selector, selector = null; } return new Event.Handler(element, eventName, selector, callback).start(); } Object.extend(Event, Event.Methods); Object.extend(Event, { fire: fire, observe: observe, stopObserving: stopObserving, on: on }); Element.addMethods({ fire: fire, observe: observe, stopObserving: stopObserving, on: on }); Object.extend(document, { fire: fire.methodize(), observe: observe.methodize(), stopObserving: stopObserving.methodize(), on: on.methodize(), loaded: false }); if (window.Event) Object.extend(window.Event, Event); else window.Event = Event; })(); (function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ var timer; function fireContentLoadedEvent() { if (document.loaded) return; if (timer) window.clearTimeout(timer); document.loaded = true; document.fire('dom:loaded'); } function checkReadyState() { if (document.readyState === 'complete') { document.stopObserving('readystatechange', checkReadyState); fireContentLoadedEvent(); } } function pollDoScroll() { try { document.documentElement.doScroll('left'); } catch(e) { timer = pollDoScroll.defer(); return; } fireContentLoadedEvent(); } if (document.addEventListener) { document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); } else { document.observe('readystatechange', checkReadyState); if (window == top) timer = pollDoScroll.defer(); } Event.observe(window, 'load', fireContentLoadedEvent); })(); Element.addMethods(); /*------------------------------- DEPRECATED -------------------------------*/ Hash.toQueryString = Object.toQueryString; var Toggle = { display: Element.toggle }; Element.Methods.childOf = Element.Methods.descendantOf; var Insertion = { Before: function(element, content) { return Element.insert(element, {before:content}); }, Top: function(element, content) { return Element.insert(element, {top:content}); }, Bottom: function(element, content) { return Element.insert(element, {bottom:content}); }, After: function(element, content) { return Element.insert(element, {after:content}); } }; var $continue = new Error('"throw $continue" is deprecated, use "return" instead'); var Position = { includeScrollOffsets: false, prepare: function() { this.deltaX = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; this.deltaY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; }, within: function(element, x, y) { if (this.includeScrollOffsets) return this.withinIncludingScrolloffsets(element, x, y); this.xcomp = x; this.ycomp = y; this.offset = Element.cumulativeOffset(element); return (y >= this.offset[1] && y < this.offset[1] + element.offsetHeight && x >= this.offset[0] && x < this.offset[0] + element.offsetWidth); }, withinIncludingScrolloffsets: function(element, x, y) { var offsetcache = Element.cumulativeScrollOffset(element); this.xcomp = x + offsetcache[0] - this.deltaX; this.ycomp = y + offsetcache[1] - this.deltaY; this.offset = Element.cumulativeOffset(element); return (this.ycomp >= this.offset[1] && this.ycomp < this.offset[1] + element.offsetHeight && this.xcomp >= this.offset[0] && this.xcomp < this.offset[0] + element.offsetWidth); }, overlap: function(mode, element) { if (!mode) return 0; if (mode == 'vertical') return ((this.offset[1] + element.offsetHeight) - this.ycomp) / element.offsetHeight; if (mode == 'horizontal') return ((this.offset[0] + element.offsetWidth) - this.xcomp) / element.offsetWidth; }, cumulativeOffset: Element.Methods.cumulativeOffset, positionedOffset: Element.Methods.positionedOffset, absolutize: function(element) { Position.prepare(); return Element.absolutize(element); }, relativize: function(element) { Position.prepare(); return Element.relativize(element); }, realOffset: Element.Methods.cumulativeScrollOffset, offsetParent: Element.Methods.getOffsetParent, page: Element.Methods.viewportOffset, clone: function(source, target, options) { options = options || { }; return Element.clonePosition(target, source, options); } }; /*--------------------------------------------------------------------------*/ if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){ function iter(name) { return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]"; } instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ? function(element, className) { className = className.toString().strip(); var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className); return cond ? document._getElementsByXPath('.//*' + cond, element) : []; } : function(element, className) { className = className.toString().strip(); var elements = [], classNames = (/\s/.test(className) ? $w(className) : null); if (!classNames && !className) return elements; var nodes = $(element).getElementsByTagName('*'); className = ' ' + className + ' '; for (var i = 0, child, cn; child = nodes[i]; i++) { if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) || (classNames && classNames.all(function(name) { return !name.toString().blank() && cn.include(' ' + name + ' '); })))) elements.push(Element.extend(child)); } return elements; }; return function(className, parentElement) { return $(parentElement || document.body).getElementsByClassName(className); }; }(Element.Methods); /*--------------------------------------------------------------------------*/ Element.ClassNames = Class.create(); Element.ClassNames.prototype = { initialize: function(element) { this.element = $(element); }, _each: function(iterator) { this.element.className.split(/\s+/).select(function(name) { return name.length > 0; })._each(iterator); }, set: function(className) { this.element.className = className; }, add: function(classNameToAdd) { if (this.include(classNameToAdd)) return; this.set($A(this).concat(classNameToAdd).join(' ')); }, remove: function(classNameToRemove) { if (!this.include(classNameToRemove)) return; this.set($A(this).without(classNameToRemove).join(' ')); }, toString: function() { return $A(this).join(' '); } }; Object.extend(Element.ClassNames.prototype, Enumerable); /*--------------------------------------------------------------------------*/ (function() { window.Selector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); }, findElements: function(rootElement) { return Prototype.Selector.select(this.expression, rootElement); }, match: function(element) { return Prototype.Selector.match(element, this.expression); }, toString: function() { return this.expression; }, inspect: function() { return "#"; } }); Object.extend(Selector, { matchElements: function(elements, expression) { var match = Prototype.Selector.match, results = []; for (var i = 0, length = elements.length; i < length; i++) { var element = elements[i]; if (match(element, expression)) { results.push(Element.extend(element)); } } return results; }, findElement: function(elements, expression, index) { index = index || 0; var matchIndex = 0, element; for (var i = 0, length = elements.length; i < length; i++) { element = elements[i]; if (Prototype.Selector.match(element, expression) && index === matchIndex++) { return Element.extend(element); } } }, findChildElements: function(element, expressions) { var selector = expressions.toArray().join(', '); return Prototype.Selector.select(selector, element || document); } }); })(); String.prototype.parseColor = function() { var color = '#'; if (this.slice(0,4) == 'rgb(') { var cols = this.slice(4,this.length-1).split(','); var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); } else { if (this.slice(0,1) == '#') { if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); if (this.length==7) color = this.toLowerCase(); } } return (color.length==7 ? color : (arguments[0] || this)); }; /*--------------------------------------------------------------------------*/ Element.collectTextNodes = function(element) { return $A($(element).childNodes).collect( function(node) { return (node.nodeType==3 ? node.nodeValue : (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); }).flatten().join(''); }; Element.collectTextNodesIgnoreClass = function(element, className) { return $A($(element).childNodes).collect( function(node) { return (node.nodeType==3 ? node.nodeValue : ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? Element.collectTextNodesIgnoreClass(node, className) : '')); }).flatten().join(''); }; Element.setContentZoom = function(element, percent) { element = $(element); element.setStyle({fontSize: (percent/100) + 'em'}); if (Prototype.Browser.WebKit) window.scrollBy(0,0); return element; }; Element.getInlineOpacity = function(element){ return $(element).style.opacity || ''; }; Element.forceRerendering = function(element) { try { element = $(element); var n = document.createTextNode(' '); element.appendChild(n); element.removeChild(n); } catch(e) { } }; /*--------------------------------------------------------------------------*/ var Effect = { _elementDoesNotExistError: { name: 'ElementDoesNotExistError', message: 'The specified DOM element does not exist, but is required for this effect to operate' }, Transitions: { linear: Prototype.K, sinoidal: function(pos) { return (-Math.cos(pos*Math.PI)/2) + .5; }, reverse: function(pos) { return 1-pos; }, flicker: function(pos) { var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; return pos > 1 ? 1 : pos; }, wobble: function(pos) { return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; }, pulse: function(pos, pulses) { return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; }, spring: function(pos) { return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); }, none: function(pos) { return 0; }, full: function(pos) { return 1; } }, DefaultOptions: { duration: 1.0, // seconds fps: 100, // 100= assume 66fps max. sync: false, // true for combining from: 0.0, to: 1.0, delay: 0.0, queue: 'parallel' }, tagifyText: function(element) { var tagifyStyle = 'position:relative'; if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; element = $(element); $A(element.childNodes).each( function(child) { if (child.nodeType==3) { child.nodeValue.toArray().each( function(character) { element.insertBefore( new Element('span', {style: tagifyStyle}).update( character == ' ' ? String.fromCharCode(160) : character), child); }); Element.remove(child); } }); }, multiple: function(element, effect) { var elements; if (((typeof element == 'object') || Object.isFunction(element)) && (element.length)) elements = element; else elements = $(element).childNodes; var options = Object.extend({ speed: 0.1, delay: 0.0 }, arguments[2] || { }); var masterDelay = options.delay; $A(elements).each( function(element, index) { new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); }); }, PAIRS: { 'slide': ['SlideDown','SlideUp'], 'blind': ['BlindDown','BlindUp'], 'appear': ['Appear','Fade'] }, toggle: function(element, effect, options) { element = $(element); effect = (effect || 'appear').toLowerCase(); return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({ queue: { position:'end', scope:(element.id || 'global'), limit: 1 } }, options || {})); } }; Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; /* ------------- core effects ------------- */ Effect.ScopedQueue = Class.create(Enumerable, { initialize: function() { this.effects = []; this.interval = null; }, _each: function(iterator) { this.effects._each(iterator); }, add: function(effect) { var timestamp = new Date().getTime(); var position = Object.isString(effect.options.queue) ? effect.options.queue : effect.options.queue.position; switch(position) { case 'front': this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { e.startOn += effect.finishOn; e.finishOn += effect.finishOn; }); break; case 'with-last': timestamp = this.effects.pluck('startOn').max() || timestamp; break; case 'end': timestamp = this.effects.pluck('finishOn').max() || timestamp; break; } effect.startOn += timestamp; effect.finishOn += timestamp; if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) this.effects.push(effect); if (!this.interval) this.interval = setInterval(this.loop.bind(this), 15); }, remove: function(effect) { this.effects = this.effects.reject(function(e) { return e==effect }); if (this.effects.length == 0) { clearInterval(this.interval); this.interval = null; } }, loop: function() { var timePos = new Date().getTime(); for(var i=0, len=this.effects.length;i= this.startOn) { if (timePos >= this.finishOn) { this.render(1.0); this.cancel(); this.event('beforeFinish'); if (this.finish) this.finish(); this.event('afterFinish'); return; } var pos = (timePos - this.startOn) / this.totalTime, frame = (pos * this.totalFrames).round(); if (frame > this.currentFrame) { this.render(pos); this.currentFrame = frame; } } }, cancel: function() { if (!this.options.sync) Effect.Queues.get(Object.isString(this.options.queue) ? 'global' : this.options.queue.scope).remove(this); this.state = 'finished'; }, event: function(eventName) { if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); if (this.options[eventName]) this.options[eventName](this); }, inspect: function() { var data = $H(); for(property in this) if (!Object.isFunction(this[property])) data.set(property, this[property]); return '#'; } }); Effect.Parallel = Class.create(Effect.Base, { initialize: function(effects) { this.effects = effects || []; this.start(arguments[1]); }, update: function(position) { this.effects.invoke('render', position); }, finish: function(position) { this.effects.each( function(effect) { effect.render(1.0); effect.cancel(); effect.event('beforeFinish'); if (effect.finish) effect.finish(position); effect.event('afterFinish'); }); } }); Effect.Tween = Class.create(Effect.Base, { initialize: function(object, from, to) { object = Object.isString(object) ? $(object) : object; var args = $A(arguments), method = args.last(), options = args.length == 5 ? args[3] : null; this.method = Object.isFunction(method) ? method.bind(object) : Object.isFunction(object[method]) ? object[method].bind(object) : function(value) { object[method] = value }; this.start(Object.extend({ from: from, to: to }, options || { })); }, update: function(position) { this.method(position); } }); Effect.Event = Class.create(Effect.Base, { initialize: function() { this.start(Object.extend({ duration: 0 }, arguments[0] || { })); }, update: Prototype.emptyFunction }); Effect.Opacity = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) this.element.setStyle({zoom: 1}); var options = Object.extend({ from: this.element.getOpacity() || 0.0, to: 1.0 }, arguments[1] || { }); this.start(options); }, update: function(position) { this.element.setOpacity(position); } }); Effect.Move = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ x: 0, y: 0, mode: 'relative' }, arguments[1] || { }); this.start(options); }, setup: function() { this.element.makePositioned(); this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); this.originalTop = parseFloat(this.element.getStyle('top') || '0'); if (this.options.mode == 'absolute') { this.options.x = this.options.x - this.originalLeft; this.options.y = this.options.y - this.originalTop; } }, update: function(position) { this.element.setStyle({ left: (this.options.x * position + this.originalLeft).round() + 'px', top: (this.options.y * position + this.originalTop).round() + 'px' }); } }); Effect.MoveBy = function(element, toTop, toLeft) { return new Effect.Move(element, Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); }; Effect.Scale = Class.create(Effect.Base, { initialize: function(element, percent) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ scaleX: true, scaleY: true, scaleContent: true, scaleFromCenter: false, scaleMode: 'box', // 'box' or 'contents' or { } with provided values scaleFrom: 100.0, scaleTo: percent }, arguments[2] || { }); this.start(options); }, setup: function() { this.restoreAfterFinish = this.options.restoreAfterFinish || false; this.elementPositioning = this.element.getStyle('position'); this.originalStyle = { }; ['top','left','width','height','fontSize'].each( function(k) { this.originalStyle[k] = this.element.style[k]; }.bind(this)); this.originalTop = this.element.offsetTop; this.originalLeft = this.element.offsetLeft; var fontSize = this.element.getStyle('font-size') || '100%'; ['em','px','%','pt'].each( function(fontSizeType) { if (fontSize.indexOf(fontSizeType)>0) { this.fontSize = parseFloat(fontSize); this.fontSizeType = fontSizeType; } }.bind(this)); this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; this.dims = null; if (this.options.scaleMode=='box') this.dims = [this.element.offsetHeight, this.element.offsetWidth]; if (/^content/.test(this.options.scaleMode)) this.dims = [this.element.scrollHeight, this.element.scrollWidth]; if (!this.dims) this.dims = [this.options.scaleMode.originalHeight, this.options.scaleMode.originalWidth]; }, update: function(position) { var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); if (this.options.scaleContent && this.fontSize) this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); }, finish: function(position) { if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); }, setDimensions: function(height, width) { var d = { }; if (this.options.scaleX) d.width = width.round() + 'px'; if (this.options.scaleY) d.height = height.round() + 'px'; if (this.options.scaleFromCenter) { var topd = (height - this.dims[0])/2; var leftd = (width - this.dims[1])/2; if (this.elementPositioning == 'absolute') { if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; } else { if (this.options.scaleY) d.top = -topd + 'px'; if (this.options.scaleX) d.left = -leftd + 'px'; } } this.element.setStyle(d); } }); Effect.Highlight = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); this.start(options); }, setup: function() { if (this.element.getStyle('display')=='none') { this.cancel(); return; } this.oldStyle = { }; if (!this.options.keepBackgroundImage) { this.oldStyle.backgroundImage = this.element.getStyle('background-image'); this.element.setStyle({backgroundImage: 'none'}); } if (!this.options.endcolor) this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); if (!this.options.restorecolor) this.options.restorecolor = this.element.getStyle('background-color'); this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); }, update: function(position) { this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); }, finish: function() { this.element.setStyle(Object.extend(this.oldStyle, { backgroundColor: this.options.restorecolor })); } }); Effect.ScrollTo = function(element) { var options = arguments[1] || { }, scrollOffsets = document.viewport.getScrollOffsets(), elementOffsets = $(element).cumulativeOffset(); if (options.offset) elementOffsets[1] += options.offset; return new Effect.Tween(null, scrollOffsets.top, elementOffsets[1], options, function(p){ scrollTo(scrollOffsets.left, p.round()); } ); }; /* ------------- combination effects ------------- */ Effect.Fade = function(element) { element = $(element); var oldOpacity = element.getInlineOpacity(); var options = Object.extend({ from: element.getOpacity() || 1.0, to: 0.0, afterFinishInternal: function(effect) { if (effect.options.to!=0) return; effect.element.hide().setStyle({opacity: oldOpacity}); } }, arguments[1] || { }); return new Effect.Opacity(element,options); }; Effect.Appear = function(element) { element = $(element); var options = Object.extend({ from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), to: 1.0, afterFinishInternal: function(effect) { effect.element.forceRerendering(); }, beforeSetup: function(effect) { effect.element.setOpacity(effect.options.from).show(); }}, arguments[1] || { }); return new Effect.Opacity(element,options); }; Effect.Puff = function(element) { element = $(element); var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position'), top: element.style.top, left: element.style.left, width: element.style.width, height: element.style.height }; return new Effect.Parallel( [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], Object.extend({ duration: 1.0, beforeSetupInternal: function(effect) { Position.absolutize(effect.effects[0].element); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().setStyle(oldStyle); } }, arguments[1] || { }) ); }; Effect.BlindUp = function(element) { element = $(element); element.makeClipping(); return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, restoreAfterFinish: true, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); } }, arguments[1] || { }) ); }; Effect.BlindDown = function(element) { element = $(element); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { effect.element.undoClipping(); } }, arguments[1] || { })); }; Effect.SwitchOff = function(element) { element = $(element); var oldOpacity = element.getInlineOpacity(); return new Effect.Appear(element, Object.extend({ duration: 0.4, from: 0, transition: Effect.Transitions.flicker, afterFinishInternal: function(effect) { new Effect.Scale(effect.element, 1, { duration: 0.3, scaleFromCenter: true, scaleX: false, scaleContent: false, restoreAfterFinish: true, beforeSetup: function(effect) { effect.element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); } }); } }, arguments[1] || { })); }; Effect.DropOut = function(element) { element = $(element); var oldStyle = { top: element.getStyle('top'), left: element.getStyle('left'), opacity: element.getInlineOpacity() }; return new Effect.Parallel( [ new Effect.Move(element, {x: 0, y: 100, sync: true }), new Effect.Opacity(element, { sync: true, to: 0.0 }) ], Object.extend( { duration: 0.5, beforeSetup: function(effect) { effect.effects[0].element.makePositioned(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); } }, arguments[1] || { })); }; Effect.Shake = function(element) { element = $(element); var options = Object.extend({ distance: 20, duration: 0.5 }, arguments[1] || {}); var distance = parseFloat(options.distance); var split = parseFloat(options.duration) / 10.0; var oldStyle = { top: element.getStyle('top'), left: element.getStyle('left') }; return new Effect.Move(element, { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { new Effect.Move(effect.element, { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { effect.element.undoPositioned().setStyle(oldStyle); }}); }}); }}); }}); }}); }}); }; Effect.SlideDown = function(element) { element = $(element).cleanWhitespace(); var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: window.opera ? 0 : 1, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makePositioned(); effect.element.down().makePositioned(); if (window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { effect.element.undoClipping().undoPositioned(); effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } }, arguments[1] || { }) ); }; Effect.SlideUp = function(element) { element = $(element).cleanWhitespace(); var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, window.opera ? 0 : 1, Object.extend({ scaleContent: false, scaleX: false, scaleMode: 'box', scaleFrom: 100, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { effect.element.makePositioned(); effect.element.down().makePositioned(); if (window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().show(); }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned(); effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } }, arguments[1] || { }) ); }; Effect.Squish = function(element) { return new Effect.Scale(element, window.opera ? 1 : 0, { restoreAfterFinish: true, beforeSetup: function(effect) { effect.element.makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); } }); }; Effect.Grow = function(element) { element = $(element); var options = Object.extend({ direction: 'center', moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.full }, arguments[1] || { }); var oldStyle = { top: element.style.top, left: element.style.left, height: element.style.height, width: element.style.width, opacity: element.getInlineOpacity() }; var dims = element.getDimensions(); var initialMoveX, initialMoveY; var moveX, moveY; switch (options.direction) { case 'top-left': initialMoveX = initialMoveY = moveX = moveY = 0; break; case 'top-right': initialMoveX = dims.width; initialMoveY = moveY = 0; moveX = -dims.width; break; case 'bottom-left': initialMoveX = moveX = 0; initialMoveY = dims.height; moveY = -dims.height; break; case 'bottom-right': initialMoveX = dims.width; initialMoveY = dims.height; moveX = -dims.width; moveY = -dims.height; break; case 'center': initialMoveX = dims.width / 2; initialMoveY = dims.height / 2; moveX = -dims.width / 2; moveY = -dims.height / 2; break; } return new Effect.Move(element, { x: initialMoveX, y: initialMoveY, duration: 0.01, beforeSetup: function(effect) { effect.element.hide().makeClipping().makePositioned(); }, afterFinishInternal: function(effect) { new Effect.Parallel( [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), new Effect.Scale(effect.element, 100, { scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) ], Object.extend({ beforeSetup: function(effect) { effect.effects[0].element.setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); } }, options) ); } }); }; Effect.Shrink = function(element) { element = $(element); var options = Object.extend({ direction: 'center', moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.none }, arguments[1] || { }); var oldStyle = { top: element.style.top, left: element.style.left, height: element.style.height, width: element.style.width, opacity: element.getInlineOpacity() }; var dims = element.getDimensions(); var moveX, moveY; switch (options.direction) { case 'top-left': moveX = moveY = 0; break; case 'top-right': moveX = dims.width; moveY = 0; break; case 'bottom-left': moveX = 0; moveY = dims.height; break; case 'bottom-right': moveX = dims.width; moveY = dims.height; break; case 'center': moveX = dims.width / 2; moveY = dims.height / 2; break; } return new Effect.Parallel( [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) ], Object.extend({ beforeStartInternal: function(effect) { effect.effects[0].element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } }, options) ); }; Effect.Pulsate = function(element) { element = $(element); var options = arguments[1] || { }, oldOpacity = element.getInlineOpacity(), transition = options.transition || Effect.Transitions.linear, reverser = function(pos){ return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); }; return new Effect.Opacity(element, Object.extend(Object.extend({ duration: 2.0, from: 0, afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } }, options), {transition: reverser})); }; Effect.Fold = function(element) { element = $(element); var oldStyle = { top: element.style.top, left: element.style.left, width: element.style.width, height: element.style.height }; element.makeClipping(); return new Effect.Scale(element, 5, Object.extend({ scaleContent: false, scaleX: false, afterFinishInternal: function(effect) { new Effect.Scale(element, 1, { scaleContent: false, scaleY: false, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().setStyle(oldStyle); } }); }}, arguments[1] || { })); }; Effect.Morph = Class.create(Effect.Base, { initialize: function(element) { this.element = $(element); if (!this.element) throw(Effect._elementDoesNotExistError); var options = Object.extend({ style: { } }, arguments[1] || { }); if (!Object.isString(options.style)) this.style = $H(options.style); else { if (options.style.include(':')) this.style = options.style.parseStyle(); else { this.element.addClassName(options.style); this.style = $H(this.element.getStyles()); this.element.removeClassName(options.style); var css = this.element.getStyles(); this.style = this.style.reject(function(style) { return style.value == css[style.key]; }); options.afterFinishInternal = function(effect) { effect.element.addClassName(effect.options.style); effect.transforms.each(function(transform) { effect.element.style[transform.style] = ''; }); }; } } this.start(options); }, setup: function(){ function parseColor(color){ if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; color = color.parseColor(); return $R(0,2).map(function(i){ return parseInt( color.slice(i*2+1,i*2+3), 16 ); }); } this.transforms = this.style.map(function(pair){ var property = pair[0], value = pair[1], unit = null; if (value.parseColor('#zzzzzz') != '#zzzzzz') { value = value.parseColor(); unit = 'color'; } else if (property == 'opacity') { value = parseFloat(value); if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) this.element.setStyle({zoom: 1}); } else if (Element.CSS_LENGTH.test(value)) { var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); value = parseFloat(components[1]); unit = (components.length == 3) ? components[2] : null; } var originalValue = this.element.getStyle(property); return { style: property.camelize(), originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), targetValue: unit=='color' ? parseColor(value) : value, unit: unit }; }.bind(this)).reject(function(transform){ return ( (transform.originalValue == transform.targetValue) || ( transform.unit != 'color' && (isNaN(transform.originalValue) || isNaN(transform.targetValue)) ) ); }); }, update: function(position) { var style = { }, transform, i = this.transforms.length; while(i--) style[(transform = this.transforms[i]).style] = transform.unit=='color' ? '#'+ (Math.round(transform.originalValue[0]+ (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + (Math.round(transform.originalValue[1]+ (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + (Math.round(transform.originalValue[2]+ (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : (transform.originalValue + (transform.targetValue - transform.originalValue) * position).toFixed(3) + (transform.unit === null ? '' : transform.unit); this.element.setStyle(style, true); } }); Effect.Transform = Class.create({ initialize: function(tracks){ this.tracks = []; this.options = arguments[1] || { }; this.addTracks(tracks); }, addTracks: function(tracks){ tracks.each(function(track){ track = $H(track); var data = track.values().first(); this.tracks.push($H({ ids: track.keys().first(), effect: Effect.Morph, options: { style: data } })); }.bind(this)); return this; }, play: function(){ return new Effect.Parallel( this.tracks.map(function(track){ var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); var elements = [$(ids) || $$(ids)].flatten(); return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); }).flatten(), this.options ); } }); Element.CSS_PROPERTIES = $w( 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + 'fontSize fontWeight height left letterSpacing lineHeight ' + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + 'right textIndent top width wordSpacing zIndex'); Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; String.__parseStyleElement = document.createElement('div'); String.prototype.parseStyle = function(){ var style, styleRules = $H(); if (Prototype.Browser.WebKit) style = new Element('div',{style:this}).style; else { String.__parseStyleElement.innerHTML = '
    '; style = String.__parseStyleElement.childNodes[0].style; } Element.CSS_PROPERTIES.each(function(property){ if (style[property]) styleRules.set(property, style[property]); }); if (Prototype.Browser.IE && this.include('opacity')) styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); return styleRules; }; if (document.defaultView && document.defaultView.getComputedStyle) { Element.getStyles = function(element) { var css = document.defaultView.getComputedStyle($(element), null); return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { styles[property] = css[property]; return styles; }); }; } else { Element.getStyles = function(element) { element = $(element); var css = element.currentStyle, styles; styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { results[property] = css[property]; return results; }); if (!styles.opacity) styles.opacity = element.getOpacity(); return styles; }; } Effect.Methods = { morph: function(element, style) { element = $(element); new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); return element; }, visualEffect: function(element, effect, options) { element = $(element); var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); new Effect[klass](element, options); return element; }, highlight: function(element, options) { element = $(element); new Effect.Highlight(element, options); return element; } }; $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ 'pulsate shake puff squish switchOff dropOut').each( function(effect) { Effect.Methods[effect] = function(element, options){ element = $(element); Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); return element; }; } ); $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( function(f) { Effect.Methods[f] = Element[f]; } ); Element.addMethods(Effect.Methods); if(Object.isUndefined(Effect)) throw("dragdrop.js requires including script.aculo.us' effects.js library"); var Droppables = { drops: [], remove: function(element) { this.drops = this.drops.reject(function(d) { return d.element==$(element) }); }, add: function(element) { element = $(element); var options = Object.extend({ greedy: true, hoverclass: null, tree: false }, arguments[1] || { }); if(options.containment) { options._containers = []; var containment = options.containment; if(Object.isArray(containment)) { containment.each( function(c) { options._containers.push($(c)) }); } else { options._containers.push($(containment)); } } if(options.accept) options.accept = [options.accept].flatten(); Element.makePositioned(element); // fix IE options.element = element; this.drops.push(options); }, findDeepestChild: function(drops) { deepest = drops[0]; for (i = 1; i < drops.length; ++i) if (Element.isParent(drops[i].element, deepest.element)) deepest = drops[i]; return deepest; }, isContained: function(element, drop) { var containmentNode; if(drop.tree) { containmentNode = element.treeNode; } else { containmentNode = element.parentNode; } return drop._containers.detect(function(c) { return containmentNode == c }); }, isAffected: function(point, element, drop) { return ( (drop.element!=element) && ((!drop._containers) || this.isContained(element, drop)) && ((!drop.accept) || (Element.classNames(element).detect( function(v) { return drop.accept.include(v) } ) )) && Position.within(drop.element, point[0], point[1]) ); }, deactivate: function(drop) { if(drop.hoverclass) Element.removeClassName(drop.element, drop.hoverclass); this.last_active = null; }, activate: function(drop) { if(drop.hoverclass) Element.addClassName(drop.element, drop.hoverclass); this.last_active = drop; }, show: function(point, element) { if(!this.drops.length) return; var drop, affected = []; this.drops.each( function(drop) { if(Droppables.isAffected(point, element, drop)) affected.push(drop); }); if(affected.length>0) drop = Droppables.findDeepestChild(affected); if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); if (drop) { Position.within(drop.element, point[0], point[1]); if(drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); if (drop != this.last_active) Droppables.activate(drop); } }, fire: function(event, element) { if(!this.last_active) return; Position.prepare(); if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) if (this.last_active.onDrop) { this.last_active.onDrop(element, this.last_active.element, event); return true; } }, reset: function() { if(this.last_active) this.deactivate(this.last_active); } }; var Draggables = { drags: [], observers: [], register: function(draggable) { if(this.drags.length == 0) { this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.updateDrag.bindAsEventListener(this); this.eventKeypress = this.keyPress.bindAsEventListener(this); Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); Event.observe(document, "keypress", this.eventKeypress); } this.drags.push(draggable); }, unregister: function(draggable) { this.drags = this.drags.reject(function(d) { return d==draggable }); if(this.drags.length == 0) { Event.stopObserving(document, "mouseup", this.eventMouseUp); Event.stopObserving(document, "mousemove", this.eventMouseMove); Event.stopObserving(document, "keypress", this.eventKeypress); } }, activate: function(draggable) { if(draggable.options.delay) { this._timeout = setTimeout(function() { Draggables._timeout = null; window.focus(); Draggables.activeDraggable = draggable; }.bind(this), draggable.options.delay); } else { window.focus(); // allows keypress events if window isn't currently focused, fails for Safari this.activeDraggable = draggable; } }, deactivate: function() { this.activeDraggable = null; }, updateDrag: function(event) { if(!this.activeDraggable) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; this._lastPointer = pointer; this.activeDraggable.updateDrag(event, pointer); }, endDrag: function(event) { if(this._timeout) { clearTimeout(this._timeout); this._timeout = null; } if(!this.activeDraggable) return; this._lastPointer = null; this.activeDraggable.endDrag(event); this.activeDraggable = null; }, keyPress: function(event) { if(this.activeDraggable) this.activeDraggable.keyPress(event); }, addObserver: function(observer) { this.observers.push(observer); this._cacheObserverCallbacks(); }, removeObserver: function(element) { // element instead of observer fixes mem leaks this.observers = this.observers.reject( function(o) { return o.element==element }); this._cacheObserverCallbacks(); }, notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' if(this[eventName+'Count'] > 0) this.observers.each( function(o) { if(o[eventName]) o[eventName](eventName, draggable, event); }); if(draggable.options[eventName]) draggable.options[eventName](draggable, event); }, _cacheObserverCallbacks: function() { ['onStart','onEnd','onDrag'].each( function(eventName) { Draggables[eventName+'Count'] = Draggables.observers.select( function(o) { return o[eventName]; } ).length; }); } }; /*--------------------------------------------------------------------------*/ var Draggable = Class.create({ initialize: function(element) { var defaults = { handle: false, reverteffect: function(element, top_offset, left_offset) { var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, queue: {scope:'_draggable', position:'end'} }); }, endeffect: function(element) { var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, queue: {scope:'_draggable', position:'end'}, afterFinish: function(){ Draggable._dragging[element] = false } }); }, zindex: 1000, revert: false, quiet: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } delay: 0 }; if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) Object.extend(defaults, { starteffect: function(element) { element._opacity = Element.getOpacity(element); Draggable._dragging[element] = true; new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); } }); var options = Object.extend(defaults, arguments[1] || { }); this.element = $(element); if(options.handle && Object.isString(options.handle)) this.handle = this.element.down('.'+options.handle, 0); if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = this.element; if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { options.scroll = $(options.scroll); this._isScrollChild = Element.childOf(this.element, options.scroll); } Element.makePositioned(this.element); // fix IE this.options = options; this.dragging = false; this.eventMouseDown = this.initDrag.bindAsEventListener(this); Event.observe(this.handle, "mousedown", this.eventMouseDown); Draggables.register(this); }, destroy: function() { Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); Draggables.unregister(this); }, currentDelta: function() { return([ parseInt(Element.getStyle(this.element,'left') || '0'), parseInt(Element.getStyle(this.element,'top') || '0')]); }, initDrag: function(event) { if(!Object.isUndefined(Draggable._dragging[this.element]) && Draggable._dragging[this.element]) return; if(Event.isLeftClick(event)) { var src = Event.element(event); if((tag_name = src.tagName.toUpperCase()) && ( tag_name=='INPUT' || tag_name=='SELECT' || tag_name=='OPTION' || tag_name=='BUTTON' || tag_name=='TEXTAREA')) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; var pos = this.element.cumulativeOffset(); this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); Draggables.activate(this); Event.stop(event); } }, startDrag: function(event) { this.dragging = true; if(!this.delta) this.delta = this.currentDelta(); if(this.options.zindex) { this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); this.element.style.zIndex = this.options.zindex; } if(this.options.ghosting) { this._clone = this.element.cloneNode(true); this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); if (!this._originallyAbsolute) Position.absolutize(this.element); this.element.parentNode.insertBefore(this._clone, this.element); } if(this.options.scroll) { if (this.options.scroll == window) { var where = this._getWindowScroll(this.options.scroll); this.originalScrollLeft = where.left; this.originalScrollTop = where.top; } else { this.originalScrollLeft = this.options.scroll.scrollLeft; this.originalScrollTop = this.options.scroll.scrollTop; } } Draggables.notify('onStart', this, event); if(this.options.starteffect) this.options.starteffect(this.element); }, updateDrag: function(event, pointer) { if(!this.dragging) this.startDrag(event); if(!this.options.quiet){ Position.prepare(); Droppables.show(pointer, this.element); } Draggables.notify('onDrag', this, event); this.draw(pointer); if(this.options.change) this.options.change(this); if(this.options.scroll) { this.stopScrolling(); var p; if (this.options.scroll == window) { with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } } else { p = Position.page(this.options.scroll); p[0] += this.options.scroll.scrollLeft + Position.deltaX; p[1] += this.options.scroll.scrollTop + Position.deltaY; p.push(p[0]+this.options.scroll.offsetWidth); p.push(p[1]+this.options.scroll.offsetHeight); } var speed = [0,0]; if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); this.startScrolling(speed); } if(Prototype.Browser.WebKit) window.scrollBy(0,0); Event.stop(event); }, finishDrag: function(event, success) { this.dragging = false; if(this.options.quiet){ Position.prepare(); var pointer = [Event.pointerX(event), Event.pointerY(event)]; Droppables.show(pointer, this.element); } if(this.options.ghosting) { if (!this._originallyAbsolute) Position.relativize(this.element); delete this._originallyAbsolute; Element.remove(this._clone); this._clone = null; } var dropped = false; if(success) { dropped = Droppables.fire(event, this.element); if (!dropped) dropped = false; } if(dropped && this.options.onDropped) this.options.onDropped(this.element); Draggables.notify('onEnd', this, event); var revert = this.options.revert; if(revert && Object.isFunction(revert)) revert = revert(this.element); var d = this.currentDelta(); if(revert && this.options.reverteffect) { if (dropped == 0 || revert != 'failure') this.options.reverteffect(this.element, d[1]-this.delta[1], d[0]-this.delta[0]); } else { this.delta = d; } if(this.options.zindex) this.element.style.zIndex = this.originalZ; if(this.options.endeffect) this.options.endeffect(this.element); Draggables.deactivate(this); Droppables.reset(); }, keyPress: function(event) { if(event.keyCode!=Event.KEY_ESC) return; this.finishDrag(event, false); Event.stop(event); }, endDrag: function(event) { if(!this.dragging) return; this.stopScrolling(); this.finishDrag(event, true); Event.stop(event); }, draw: function(point) { var pos = this.element.cumulativeOffset(); if(this.options.ghosting) { var r = Position.realOffset(this.element); pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; } var d = this.currentDelta(); pos[0] -= d[0]; pos[1] -= d[1]; if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; } var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); if(this.options.snap) { if(Object.isFunction(this.options.snap)) { p = this.options.snap(p[0],p[1],this); } else { if(Object.isArray(this.options.snap)) { p = p.map( function(v, i) { return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); } else { p = p.map( function(v) { return (v/this.options.snap).round()*this.options.snap }.bind(this)); } }} var style = this.element.style; if((!this.options.constraint) || (this.options.constraint=='horizontal')) style.left = p[0] + "px"; if((!this.options.constraint) || (this.options.constraint=='vertical')) style.top = p[1] + "px"; if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering }, stopScrolling: function() { if(this.scrollInterval) { clearInterval(this.scrollInterval); this.scrollInterval = null; Draggables._lastScrollPointer = null; } }, startScrolling: function(speed) { if(!(speed[0] || speed[1])) return; this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; this.lastScrolled = new Date(); this.scrollInterval = setInterval(this.scroll.bind(this), 10); }, scroll: function() { var current = new Date(); var delta = current - this.lastScrolled; this.lastScrolled = current; if(this.options.scroll == window) { with (this._getWindowScroll(this.options.scroll)) { if (this.scrollSpeed[0] || this.scrollSpeed[1]) { var d = delta / 1000; this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); } } } else { this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; } Position.prepare(); Droppables.show(Draggables._lastPointer, this.element); Draggables.notify('onDrag', this); if (this._isScrollChild) { Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; if (Draggables._lastScrollPointer[0] < 0) Draggables._lastScrollPointer[0] = 0; if (Draggables._lastScrollPointer[1] < 0) Draggables._lastScrollPointer[1] = 0; this.draw(Draggables._lastScrollPointer); } if(this.options.change) this.options.change(this); }, _getWindowScroll: function(w) { var T, L, W, H; with (w.document) { if (w.document.documentElement && documentElement.scrollTop) { T = documentElement.scrollTop; L = documentElement.scrollLeft; } else if (w.document.body) { T = body.scrollTop; L = body.scrollLeft; } if (w.innerWidth) { W = w.innerWidth; H = w.innerHeight; } else if (w.document.documentElement && documentElement.clientWidth) { W = documentElement.clientWidth; H = documentElement.clientHeight; } else { W = body.offsetWidth; H = body.offsetHeight; } } return { top: T, left: L, width: W, height: H }; } }); Draggable._dragging = { }; /*--------------------------------------------------------------------------*/ var SortableObserver = Class.create({ initialize: function(element, observer) { this.element = $(element); this.observer = observer; this.lastValue = Sortable.serialize(this.element); }, onStart: function() { this.lastValue = Sortable.serialize(this.element); }, onEnd: function() { Sortable.unmark(); if(this.lastValue != Sortable.serialize(this.element)) this.observer(this.element) } }); var Sortable = { SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, sortables: { }, _findRootElement: function(element) { while (element.tagName.toUpperCase() != "BODY") { if(element.id && Sortable.sortables[element.id]) return element; element = element.parentNode; } }, options: function(element) { element = Sortable._findRootElement($(element)); if(!element) return; return Sortable.sortables[element.id]; }, destroy: function(element){ element = $(element); var s = Sortable.sortables[element.id]; if(s) { Draggables.removeObserver(s.element); s.droppables.each(function(d){ Droppables.remove(d) }); s.draggables.invoke('destroy'); delete Sortable.sortables[s.element.id]; } }, create: function(element) { element = $(element); var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' dropOnEmpty: false, tree: false, treeTag: 'ul', overlap: 'vertical', // one of 'vertical', 'horizontal' constraint: 'vertical', // one of 'vertical', 'horizontal', false containment: element, // also takes array of elements (or id's); or false handle: false, // or a CSS class only: false, delay: 0, hoverclass: null, ghosting: false, quiet: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, format: this.SERIALIZE_RULE, elements: false, handles: false, onChange: Prototype.emptyFunction, onUpdate: Prototype.emptyFunction }, arguments[1] || { }); this.destroy(element); var options_for_draggable = { revert: true, quiet: options.quiet, scroll: options.scroll, scrollSpeed: options.scrollSpeed, scrollSensitivity: options.scrollSensitivity, delay: options.delay, ghosting: options.ghosting, constraint: options.constraint, handle: options.handle }; if(options.starteffect) options_for_draggable.starteffect = options.starteffect; if(options.reverteffect) options_for_draggable.reverteffect = options.reverteffect; else if(options.ghosting) options_for_draggable.reverteffect = function(element) { element.style.top = 0; element.style.left = 0; }; if(options.endeffect) options_for_draggable.endeffect = options.endeffect; if(options.zindex) options_for_draggable.zindex = options.zindex; var options_for_droppable = { overlap: options.overlap, containment: options.containment, tree: options.tree, hoverclass: options.hoverclass, onHover: Sortable.onHover }; var options_for_tree = { onHover: Sortable.onEmptyHover, overlap: options.overlap, containment: options.containment, hoverclass: options.hoverclass }; Element.cleanWhitespace(element); options.draggables = []; options.droppables = []; if(options.dropOnEmpty || options.tree) { Droppables.add(element, options_for_tree); options.droppables.push(element); } (options.elements || this.findElements(element, options) || []).each( function(e,i) { var handle = options.handles ? $(options.handles[i]) : (options.handle ? $(e).select('.' + options.handle)[0] : e); options.draggables.push( new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); Droppables.add(e, options_for_droppable); if(options.tree) e.treeNode = element; options.droppables.push(e); }); if(options.tree) { (Sortable.findTreeElements(element, options) || []).each( function(e) { Droppables.add(e, options_for_tree); e.treeNode = element; options.droppables.push(e); }); } this.sortables[element.identify()] = options; Draggables.addObserver(new SortableObserver(element, options.onUpdate)); }, findElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.tag); }, findTreeElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.treeTag); }, onHover: function(element, dropon, overlap) { if(Element.isParent(dropon, element)) return; if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { return; } else if(overlap>0.5) { Sortable.mark(dropon, 'before'); if(dropon.previousSibling != element) { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, dropon); if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } else { Sortable.mark(dropon, 'after'); var nextElement = dropon.nextSibling || null; if(nextElement != element) { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, nextElement); if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } }, onEmptyHover: function(element, dropon, overlap) { var oldParentNode = element.parentNode; var droponOptions = Sortable.options(dropon); if(!Element.isParent(dropon, element)) { var index; var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); var child = null; if(children) { var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); for (index = 0; index < children.length; index += 1) { if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { offset -= Element.offsetSize (children[index], droponOptions.overlap); } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { child = index + 1 < children.length ? children[index + 1] : null; break; } else { child = children[index]; break; } } } dropon.insertBefore(element, child); Sortable.options(oldParentNode).onChange(element); droponOptions.onChange(element); } }, unmark: function() { if(Sortable._marker) Sortable._marker.hide(); }, mark: function(dropon, position) { var sortable = Sortable.options(dropon.parentNode); if(sortable && !sortable.ghosting) return; if(!Sortable._marker) { Sortable._marker = ($('dropmarker') || Element.extend(document.createElement('DIV'))). hide().addClassName('dropmarker').setStyle({position:'absolute'}); document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); } var offsets = dropon.cumulativeOffset(); Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); if(position=='after') if(sortable.overlap == 'horizontal') Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); else Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); Sortable._marker.show(); }, _tree: function(element, options, parent) { var children = Sortable.findElements(element, options) || []; for (var i = 0; i < children.length; ++i) { var match = children[i].id.match(options.format); if (!match) continue; var child = { id: encodeURIComponent(match ? match[1] : null), element: element, parent: parent, children: [], position: parent.children.length, container: $(children[i]).down(options.treeTag) }; /* Get the element containing the children and recurse over it */ if (child.container) this._tree(child.container, options, child); parent.children.push (child); } return parent; }, tree: function(element) { element = $(element); var sortableOptions = this.options(element); var options = Object.extend({ tag: sortableOptions.tag, treeTag: sortableOptions.treeTag, only: sortableOptions.only, name: element.id, format: sortableOptions.format }, arguments[1] || { }); var root = { id: null, parent: null, children: [], container: element, position: 0 }; return Sortable._tree(element, options, root); }, /* Construct a [i] index for a particular node */ _constructIndex: function(node) { var index = ''; do { if (node.id) index = '[' + node.position + ']' + index; } while ((node = node.parent) != null); return index; }, sequence: function(element) { element = $(element); var options = Object.extend(this.options(element), arguments[1] || { }); return $(this.findElements(element, options) || []).map( function(item) { return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; }); }, setSequence: function(element, new_sequence) { element = $(element); var options = Object.extend(this.options(element), arguments[2] || { }); var nodeMap = { }; this.findElements(element, options).each( function(n) { if (n.id.match(options.format)) nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; n.parentNode.removeChild(n); }); new_sequence.each(function(ident) { var n = nodeMap[ident]; if (n) { n[1].appendChild(n[0]); delete nodeMap[ident]; } }); }, serialize: function(element) { element = $(element); var options = Object.extend(Sortable.options(element), arguments[1] || { }); var name = encodeURIComponent( (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); if (options.tree) { return Sortable.tree(element, arguments[1]).children.map( function (item) { return [name + Sortable._constructIndex(item) + "[id]=" + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); }).flatten().join('&'); } else { return Sortable.sequence(element, arguments[1]).map( function(item) { return name + "[]=" + encodeURIComponent(item); }).join('&'); } } }; Element.isParent = function(child, element) { if (!child.parentNode || child == element) return false; if (child.parentNode == element) return true; return Element.isParent(child.parentNode, element); }; Element.findChildren = function(element, only, recursive, tagName) { if(!element.hasChildNodes()) return null; tagName = tagName.toUpperCase(); if(only) only = [only].flatten(); var elements = []; $A(element.childNodes).each( function(e) { if(e.tagName && e.tagName.toUpperCase()==tagName && (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) elements.push(e); if(recursive) { var grandchildren = Element.findChildren(e, only, recursive, tagName); if(grandchildren) elements.push(grandchildren); } }); return (elements.length>0 ? elements.flatten() : []); }; Element.offsetSize = function (element, type) { return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; }; /** * http://www.openjs.com/scripts/events/keyboard_shortcuts/ * Version : 2.01.B * By Binny V A * License : BSD */ shortcut = { 'all_shortcuts':{},//All the shortcuts are stored in this array 'add': function(shortcut_combination,callback,opt) { var default_options = { 'type':'keydown', 'propagate':false, 'disable_in_input':false, 'target':document, 'keycode':false } if(!opt) opt = default_options; else { for(var dfo in default_options) { if(typeof opt[dfo] == 'undefined') opt[dfo] = default_options[dfo]; } } var ele = opt.target; if(typeof opt.target == 'string') ele = document.getElementById(opt.target); var ths = this; shortcut_combination = shortcut_combination.toLowerCase(); var func = function(e) { e = e || window.event; if(opt['disable_in_input']) { //Don't enable shortcut keys in Input, Textarea fields var element; if(e.target) element=e.target; else if(e.srcElement) element=e.srcElement; if(element.nodeType==3) element=element.parentNode; if(element.tagName == 'INPUT' || element.tagName == 'TEXTAREA') return; } if (e.keyCode) code = e.keyCode; else if (e.which) code = e.which; var character = String.fromCharCode(code).toLowerCase(); if(code == 188) character=","; //If the user presses , when the type is onkeydown if(code == 190) character="."; //If the user presses , when the type is onkeydown var keys = shortcut_combination.split("+"); var kp = 0; var shift_nums = { "`":"~", "1":"!", "2":"@", "3":"#", "4":"$", "5":"%", "6":"^", "7":"&", "8":"*", "9":"(", "0":")", "-":"_", "=":"+", ";":":", "'":"\"", ",":"<", ".":">", "/":"?", "\\":"|" } var special_keys = { 'esc':27, 'escape':27, 'tab':9, 'space':32, 'return':13, 'enter':13, 'backspace':8, 'scrolllock':145, 'scroll_lock':145, 'scroll':145, 'capslock':20, 'caps_lock':20, 'caps':20, 'numlock':144, 'num_lock':144, 'num':144, 'pause':19, 'break':19, 'insert':45, 'home':36, 'delete':46, 'end':35, 'pageup':33, 'page_up':33, 'pu':33, 'pagedown':34, 'page_down':34, 'pd':34, 'left':37, 'up':38, 'right':39, 'down':40, 'f1':112, 'f2':113, 'f3':114, 'f4':115, 'f5':116, 'f6':117, 'f7':118, 'f8':119, 'f9':120, 'f10':121, 'f11':122, 'f12':123 } var modifiers = { shift: { wanted:false, pressed:false}, ctrl : { wanted:false, pressed:false}, alt : { wanted:false, pressed:false}, meta : { wanted:false, pressed:false} //Meta is Mac specific }; if(e.ctrlKey) modifiers.ctrl.pressed = true; if(e.shiftKey) modifiers.shift.pressed = true; if(e.altKey) modifiers.alt.pressed = true; if(e.metaKey) modifiers.meta.pressed = true; for(var i=0; k=keys[i],i 1) { //If it is a special key if(special_keys[k] == code) kp++; } else if(opt['keycode']) { if(opt['keycode'] == code) kp++; } else { //The special keys did not match if(character == k) kp++; else { if(shift_nums[character] && e.shiftKey) { //Stupid Shift key bug created by using lowercase character = shift_nums[character]; if(character == k) kp++; } } } } if(kp == keys.length && modifiers.ctrl.pressed == modifiers.ctrl.wanted && modifiers.shift.pressed == modifiers.shift.wanted && modifiers.alt.pressed == modifiers.alt.wanted && modifiers.meta.pressed == modifiers.meta.wanted) { callback(e); if(!opt['propagate']) { //Stop the event e.cancelBubble = true; e.returnValue = false; if (e.stopPropagation) { e.stopPropagation(); e.preventDefault(); } return false; } } } this.all_shortcuts[shortcut_combination] = { 'callback':func, 'target':ele, 'event': opt['type'] }; if(ele.addEventListener) ele.addEventListener(opt['type'], func, false); else if(ele.attachEvent) ele.attachEvent('on'+opt['type'], func); else ele['on'+opt['type']] = func; }, 'remove':function(shortcut_combination) { shortcut_combination = shortcut_combination.toLowerCase(); var binding = this.all_shortcuts[shortcut_combination]; delete(this.all_shortcuts[shortcut_combination]) if(!binding) return; var type = binding['event']; var ele = binding['target']; var callback = binding['callback']; if(ele.detachEvent) ele.detachEvent('on'+type, callback); else if(ele.removeEventListener) ele.removeEventListener(type, callback, false); else ele['on'+type] = false; } }; function str_repeat(i, m) { for (var o = []; m > 0; o[--m] = i); return o.join(''); } function sprintf() { var i = 0, a, f = arguments[i++], o = [], m, p, c, x, s = ''; while (f) { if (m = /^[^\x25]+/.exec(f)) { o.push(m[0]); } else if (m = /^\x25{2}/.exec(f)) { o.push('%'); } else if (m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f)) { if (((a = arguments[m[1] || i++]) == null) || (a == undefined)) { throw('Too few arguments.'); } if (/[^s]/.test(m[7]) && (typeof(a) != 'number')) { throw('Expecting number but found ' + typeof(a)); } switch (m[7]) { case 'b': a = a.toString(2); break; case 'c': a = String.fromCharCode(a); break; case 'd': a = parseInt(a); break; case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break; case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break; case 'o': a = a.toString(8); break; case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break; case 'u': a = Math.abs(a); break; case 'x': a = a.toString(16); break; case 'X': a = a.toString(16).toUpperCase(); break; } a = (/[def]/.test(m[7]) && m[2] && a >= 0 ? '+'+ a : a); c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' '; x = m[5] - String(a).length - s.length; p = m[5] ? str_repeat(c, x) : ''; o.push(s + (m[4] ? a + p : p + a)); } else { throw('Huh ?!'); } f = f.substring(m[0].length); } return o.join(''); } /* WysiHat - WYSIWYG JavaScript framework, version 0.2.1 * (c) 2008-2010 Joshua Peek * * WysiHat is freely distributable under the terms of an MIT-style license. *--------------------------------------------------------------------------*/ var WysiHat = {}; WysiHat.Editor = { attach: function(textarea) { var editArea; textarea = $(textarea); var id = textarea.id + '_editor'; if (editArea = $(id)) return editArea; editArea = new Element('div', { 'id': id, 'class': 'editor', 'contentEditable': 'true' }); editArea.update(WysiHat.Formatting.getBrowserMarkupFrom(textarea.value)); Object.extend(editArea, WysiHat.Commands); textarea.insert({before: editArea}); textarea.hide(); return editArea; } }; WysiHat.BrowserFeatures = (function() { function createTmpIframe(callback) { var frame, frameDocument; frame = new Element('iframe'); frame.setStyle({ position: 'absolute', left: '-1000px' }); frame.onFrameLoaded(function() { if (typeof frame.contentDocument !== 'undefined') { frameDocument = frame.contentDocument; } else if (typeof frame.contentWindow !== 'undefined' && typeof frame.contentWindow.document !== 'undefined') { frameDocument = frame.contentWindow.document; } frameDocument.designMode = 'on'; callback(frameDocument); frame.remove(); }); $(document.body).insert(frame); } var features = {}; function detectParagraphType(document) { document.body.innerHTML = ''; document.execCommand('insertparagraph', false, null); var tagName; element = document.body.childNodes[0]; if (element && element.tagName) tagName = element.tagName.toLowerCase(); if (tagName == 'div') features.paragraphType = "div"; else if (document.body.innerHTML == "


    ") features.paragraphType = "br"; else features.paragraphType = "p"; } function detectIndentType(document) { document.body.innerHTML = 'tab'; document.execCommand('indent', false, null); var tagName; element = document.body.childNodes[0]; if (element && element.tagName) tagName = element.tagName.toLowerCase(); features.indentInsertsBlockquote = (tagName == 'blockquote'); } features.run = function run() { if (features.finished) return; createTmpIframe(function(document) { detectParagraphType(document); detectIndentType(document); features.finished = true; }); } return features; })(); /* IE Selection and Range classes * * Original created by Tim Cameron Ryan * http://github.com/timcameronryan/IERange * Copyright (c) 2009 Tim Cameron Ryan * Released under the MIT/X License * * Modified by Joshua Peek */ if (!window.getSelection) { var DOMUtils = { isDataNode: function(node) { try { return node && node.nodeValue !== null && node.data !== null; } catch (e) { return false; } }, isAncestorOf: function(parent, node) { if (!parent) return false; return !DOMUtils.isDataNode(parent) && (parent.contains(DOMUtils.isDataNode(node) ? node.parentNode : node) || node.parentNode == parent); }, isAncestorOrSelf: function(root, node) { return DOMUtils.isAncestorOf(root, node) || root == node; }, findClosestAncestor: function(root, node) { if (DOMUtils.isAncestorOf(root, node)) while (node && node.parentNode != root) node = node.parentNode; return node; }, getNodeLength: function(node) { return DOMUtils.isDataNode(node) ? node.length : node.childNodes.length; }, splitDataNode: function(node, offset) { if (!DOMUtils.isDataNode(node)) return false; var newNode = node.cloneNode(false); node.deleteData(offset, node.length); newNode.deleteData(0, offset); node.parentNode.insertBefore(newNode, node.nextSibling); } }; window.Range = (function() { function Range(document) { this._document = document; this.startContainer = this.endContainer = document.body; this.endOffset = DOMUtils.getNodeLength(document.body); } Range.START_TO_START = 0; Range.START_TO_END = 1; Range.END_TO_END = 2; Range.END_TO_START = 3; function findChildPosition(node) { for (var i = 0; node = node.previousSibling; i++) continue; return i; } Range.prototype = { startContainer: null, startOffset: 0, endContainer: null, endOffset: 0, commonAncestorContainer: null, collapsed: false, _document: null, _toTextRange: function() { function adoptEndPoint(textRange, domRange, bStart) { var container = domRange[bStart ? 'startContainer' : 'endContainer']; var offset = domRange[bStart ? 'startOffset' : 'endOffset'], textOffset = 0; var anchorNode = DOMUtils.isDataNode(container) ? container : container.childNodes[offset]; var anchorParent = DOMUtils.isDataNode(container) ? container.parentNode : container; if (container.nodeType == 3 || container.nodeType == 4) textOffset = offset; var cursorNode = domRange._document.createElement('a'); if (anchorNode) anchorParent.insertBefore(cursorNode, anchorNode); else anchorParent.appendChild(cursorNode); var cursor = domRange._document.body.createTextRange(); cursor.moveToElementText(cursorNode); cursorNode.parentNode.removeChild(cursorNode); textRange.setEndPoint(bStart ? 'StartToStart' : 'EndToStart', cursor); textRange[bStart ? 'moveStart' : 'moveEnd']('character', textOffset); } var textRange = this._document.body.createTextRange(); adoptEndPoint(textRange, this, true); adoptEndPoint(textRange, this, false); return textRange; }, _refreshProperties: function() { this.collapsed = (this.startContainer == this.endContainer && this.startOffset == this.endOffset); var node = this.startContainer; while (node && node != this.endContainer && !DOMUtils.isAncestorOf(node, this.endContainer)) node = node.parentNode; this.commonAncestorContainer = node; }, setStart: function(container, offset) { this.startContainer = container; this.startOffset = offset; this._refreshProperties(); }, setEnd: function(container, offset) { this.endContainer = container; this.endOffset = offset; this._refreshProperties(); }, setStartBefore: function(refNode) { this.setStart(refNode.parentNode, findChildPosition(refNode)); }, setStartAfter: function(refNode) { this.setStart(refNode.parentNode, findChildPosition(refNode) + 1); }, setEndBefore: function(refNode) { this.setEnd(refNode.parentNode, findChildPosition(refNode)); }, setEndAfter: function(refNode) { this.setEnd(refNode.parentNode, findChildPosition(refNode) + 1); }, selectNode: function(refNode) { this.setStartBefore(refNode); this.setEndAfter(refNode); }, selectNodeContents: function(refNode) { this.setStart(refNode, 0); this.setEnd(refNode, DOMUtils.getNodeLength(refNode)); }, collapse: function(toStart) { if (toStart) this.setEnd(this.startContainer, this.startOffset); else this.setStart(this.endContainer, this.endOffset); }, cloneContents: function() { return (function cloneSubtree(iterator) { for (var node, frag = document.createDocumentFragment(); node = iterator.next(); ) { node = node.cloneNode(!iterator.hasPartialSubtree()); if (iterator.hasPartialSubtree()) node.appendChild(cloneSubtree(iterator.getSubtreeIterator())); frag.appendChild(node); } return frag; })(new RangeIterator(this)); }, extractContents: function() { var range = this.cloneRange(); if (this.startContainer != this.commonAncestorContainer) this.setStartAfter(DOMUtils.findClosestAncestor(this.commonAncestorContainer, this.startContainer)); this.collapse(true); return (function extractSubtree(iterator) { for (var node, frag = document.createDocumentFragment(); node = iterator.next(); ) { iterator.hasPartialSubtree() ? node = node.cloneNode(false) : iterator.remove(); if (iterator.hasPartialSubtree()) node.appendChild(extractSubtree(iterator.getSubtreeIterator())); frag.appendChild(node); } return frag; })(new RangeIterator(range)); }, deleteContents: function() { var range = this.cloneRange(); if (this.startContainer != this.commonAncestorContainer) this.setStartAfter(DOMUtils.findClosestAncestor(this.commonAncestorContainer, this.startContainer)); this.collapse(true); (function deleteSubtree(iterator) { while (iterator.next()) iterator.hasPartialSubtree() ? deleteSubtree(iterator.getSubtreeIterator()) : iterator.remove(); })(new RangeIterator(range)); }, insertNode: function(newNode) { if (DOMUtils.isDataNode(this.startContainer)) { DOMUtils.splitDataNode(this.startContainer, this.startOffset); this.startContainer.parentNode.insertBefore(newNode, this.startContainer.nextSibling); } else { var offsetNode = this.startContainer.childNodes[this.startOffset]; if (offsetNode) { this.startContainer.insertBefore(newNode, offsetNode); } else { this.startContainer.appendChild(newNode); } } this.setStart(this.startContainer, this.startOffset); }, surroundContents: function(newNode) { var content = this.extractContents(); this.insertNode(newNode); newNode.appendChild(content); this.selectNode(newNode); }, compareBoundaryPoints: function(how, sourceRange) { var containerA, offsetA, containerB, offsetB; switch (how) { case Range.START_TO_START: case Range.START_TO_END: containerA = this.startContainer; offsetA = this.startOffset; break; case Range.END_TO_END: case Range.END_TO_START: containerA = this.endContainer; offsetA = this.endOffset; break; } switch (how) { case Range.START_TO_START: case Range.END_TO_START: containerB = sourceRange.startContainer; offsetB = sourceRange.startOffset; break; case Range.START_TO_END: case Range.END_TO_END: containerB = sourceRange.endContainer; offsetB = sourceRange.endOffset; break; } return containerA.sourceIndex < containerB.sourceIndex ? -1 : containerA.sourceIndex == containerB.sourceIndex ? offsetA < offsetB ? -1 : offsetA == offsetB ? 0 : 1 : 1; }, cloneRange: function() { var range = new Range(this._document); range.setStart(this.startContainer, this.startOffset); range.setEnd(this.endContainer, this.endOffset); return range; }, detach: function() { }, toString: function() { return this._toTextRange().text; }, createContextualFragment: function(tagString) { var content = (DOMUtils.isDataNode(this.startContainer) ? this.startContainer.parentNode : this.startContainer).cloneNode(false); content.innerHTML = tagString; for (var fragment = this._document.createDocumentFragment(); content.firstChild; ) fragment.appendChild(content.firstChild); return fragment; } }; function RangeIterator(range) { this.range = range; if (range.collapsed) return; var root = range.commonAncestorContainer; this._next = range.startContainer == root && !DOMUtils.isDataNode(range.startContainer) ? range.startContainer.childNodes[range.startOffset] : DOMUtils.findClosestAncestor(root, range.startContainer); this._end = range.endContainer == root && !DOMUtils.isDataNode(range.endContainer) ? range.endContainer.childNodes[range.endOffset] : DOMUtils.findClosestAncestor(root, range.endContainer).nextSibling; } RangeIterator.prototype = { range: null, _current: null, _next: null, _end: null, hasNext: function() { return !!this._next; }, next: function() { var current = this._current = this._next; this._next = this._current && this._current.nextSibling != this._end ? this._current.nextSibling : null; if (DOMUtils.isDataNode(this._current)) { if (this.range.endContainer == this._current) (current = current.cloneNode(true)).deleteData(this.range.endOffset, current.length - this.range.endOffset); if (this.range.startContainer == this._current) (current = current.cloneNode(true)).deleteData(0, this.range.startOffset); } return current; }, remove: function() { if (DOMUtils.isDataNode(this._current) && (this.range.startContainer == this._current || this.range.endContainer == this._current)) { var start = this.range.startContainer == this._current ? this.range.startOffset : 0; var end = this.range.endContainer == this._current ? this.range.endOffset : this._current.length; this._current.deleteData(start, end - start); } else this._current.parentNode.removeChild(this._current); }, hasPartialSubtree: function() { return !DOMUtils.isDataNode(this._current) && (DOMUtils.isAncestorOrSelf(this._current, this.range.startContainer) || DOMUtils.isAncestorOrSelf(this._current, this.range.endContainer)); }, getSubtreeIterator: function() { var subRange = new Range(this.range._document); subRange.selectNodeContents(this._current); if (DOMUtils.isAncestorOrSelf(this._current, this.range.startContainer)) subRange.setStart(this.range.startContainer, this.range.startOffset); if (DOMUtils.isAncestorOrSelf(this._current, this.range.endContainer)) subRange.setEnd(this.range.endContainer, this.range.endOffset); return new RangeIterator(subRange); } }; return Range; })(); window.Range._fromTextRange = function(textRange, document) { function adoptBoundary(domRange, textRange, bStart) { var cursorNode = document.createElement('a'), cursor = textRange.duplicate(); cursor.collapse(bStart); var parent = cursor.parentElement(); do { parent.insertBefore(cursorNode, cursorNode.previousSibling); cursor.moveToElementText(cursorNode); } while (cursor.compareEndPoints(bStart ? 'StartToStart' : 'StartToEnd', textRange) > 0 && cursorNode.previousSibling); if (cursor.compareEndPoints(bStart ? 'StartToStart' : 'StartToEnd', textRange) == -1 && cursorNode.nextSibling) { cursor.setEndPoint(bStart ? 'EndToStart' : 'EndToEnd', textRange); domRange[bStart ? 'setStart' : 'setEnd'](cursorNode.nextSibling, cursor.text.length); } else { domRange[bStart ? 'setStartBefore' : 'setEndBefore'](cursorNode); } cursorNode.parentNode.removeChild(cursorNode); } var domRange = new Range(document); adoptBoundary(domRange, textRange, true); adoptBoundary(domRange, textRange, false); return domRange; } document.createRange = function() { return new Range(document); }; window.Selection = (function() { function Selection(document) { this._document = document; var selection = this; document.attachEvent('onselectionchange', function() { selection._selectionChangeHandler(); }); } Selection.prototype = { rangeCount: 0, _document: null, _selectionChangeHandler: function() { this.rangeCount = this._selectionExists(this._document.selection.createRange()) ? 1 : 0; }, _selectionExists: function(textRange) { return textRange.compareEndPoints('StartToEnd', textRange) != 0 || textRange.parentElement().isContentEditable; }, addRange: function(range) { var selection = this._document.selection.createRange(), textRange = range._toTextRange(); if (!this._selectionExists(selection)) { textRange.select(); } else { if (textRange.compareEndPoints('StartToStart', selection) == -1) if (textRange.compareEndPoints('StartToEnd', selection) > -1 && textRange.compareEndPoints('EndToEnd', selection) == -1) selection.setEndPoint('StartToStart', textRange); else if (textRange.compareEndPoints('EndToStart', selection) < 1 && textRange.compareEndPoints('EndToEnd', selection) > -1) selection.setEndPoint('EndToEnd', textRange); selection.select(); } }, removeAllRanges: function() { this._document.selection.empty(); }, getRangeAt: function(index) { var textRange = this._document.selection.createRange(); if (this._selectionExists(textRange)) return Range._fromTextRange(textRange, this._document); return null; }, toString: function() { return this._document.selection.createRange().text; } }; return Selection; })(); window.getSelection = (function() { var selection = new Selection(document); return function() { return selection; }; })(); } Object.extend(Range.prototype, (function() { function beforeRange(range) { if (!range || !range.compareBoundaryPoints) return false; return (this.compareBoundaryPoints(this.START_TO_START, range) == -1 && this.compareBoundaryPoints(this.START_TO_END, range) == -1 && this.compareBoundaryPoints(this.END_TO_END, range) == -1 && this.compareBoundaryPoints(this.END_TO_START, range) == -1); } function afterRange(range) { if (!range || !range.compareBoundaryPoints) return false; return (this.compareBoundaryPoints(this.START_TO_START, range) == 1 && this.compareBoundaryPoints(this.START_TO_END, range) == 1 && this.compareBoundaryPoints(this.END_TO_END, range) == 1 && this.compareBoundaryPoints(this.END_TO_START, range) == 1); } function betweenRange(range) { if (!range || !range.compareBoundaryPoints) return false; return !(this.beforeRange(range) || this.afterRange(range)); } function equalRange(range) { if (!range || !range.compareBoundaryPoints) return false; return (this.compareBoundaryPoints(this.START_TO_START, range) == 0 && this.compareBoundaryPoints(this.START_TO_END, range) == 1 && this.compareBoundaryPoints(this.END_TO_END, range) == 0 && this.compareBoundaryPoints(this.END_TO_START, range) == -1); } function getNode() { var parent = this.commonAncestorContainer; while (parent.nodeType == Node.TEXT_NODE) parent = parent.parentNode; var child = parent.childElements().detect(function(child) { var range = document.createRange(); range.selectNodeContents(child); return this.betweenRange(range); }.bind(this)); return $(child || parent); } return { beforeRange: beforeRange, afterRange: afterRange, betweenRange: betweenRange, equalRange: equalRange, getNode: getNode }; })()); if (Prototype.Browser.IE) { Object.extend(Selection.prototype, (function() { function getNode() { var range = this._document.selection.createRange(); return $(range.parentElement()); } function selectNode(element) { var range = this._document.body.createTextRange(); range.moveToElementText(element); range.select(); } return { getNode: getNode, selectNode: selectNode } })()); } else { if (typeof Selection == 'undefined') { var Selection = {} Selection.prototype = window.getSelection().__proto__; } Object.extend(Selection.prototype, (function() { function getNode() { if (this.rangeCount > 0) return this.getRangeAt(0).getNode(); else return null; } function selectNode(element) { var range = document.createRange(); range.selectNode(element); this.removeAllRanges(); this.addRange(range); } return { getNode: getNode, selectNode: selectNode } })()); } document.on("dom:loaded", function() { function fieldChangeHandler(event, element) { var value; if (element.contentEditable == 'true') value = element.innerHTML; else if (element.getValue) value = element.getValue(); if (value && element.previousValue != value) { element.fire("field:change"); element.previousValue = value; } } $(document.body).on("keyup", 'input,textarea,*[contenteditable=""],*[contenteditable=true]', fieldChangeHandler); }); WysiHat.Commands = (function(window) { function boldSelection() { this.execCommand('bold', false, null); } function boldSelected() { return this.queryCommandState('bold'); } function underlineSelection() { this.execCommand('underline', false, null); } function underlineSelected() { return this.queryCommandState('underline'); } function italicSelection() { this.execCommand('italic', false, null); } function italicSelected() { return this.queryCommandState('italic'); } function strikethroughSelection() { this.execCommand('strikethrough', false, null); } function indentSelection() { if (Prototype.Browser.Gecko) { var selection, range, node, blockquote; selection = window.getSelection(); range = selection.getRangeAt(0); node = selection.getNode(); if (range.collapsed) { range = document.createRange(); range.selectNodeContents(node); selection.removeAllRanges(); selection.addRange(range); } blockquote = new Element('blockquote'); range = selection.getRangeAt(0); range.surroundContents(blockquote); } else { this.execCommand('indent', false, null); } } function outdentSelection() { this.execCommand('outdent', false, null); } function toggleIndentation() { if (this.indentSelected()) { this.outdentSelection(); } else { this.indentSelection(); } } function indentSelected() { var node = window.getSelection().getNode(); return node.match("blockquote, blockquote *"); } function fontSelection(font) { this.execCommand('fontname', false, font); } function fontSizeSelection(fontSize) { this.execCommand('fontsize', false, fontSize); } function colorSelection(color) { this.execCommand('forecolor', false, color); } function backgroundColorSelection(color) { if(Prototype.Browser.Gecko) { this.execCommand('hilitecolor', false, color); } else { this.execCommand('backcolor', false, color); } } function alignSelection(alignment) { this.execCommand('justify' + alignment); } function alignSelected() { var node = window.getSelection().getNode(); return Element.getStyle(node, 'textAlign'); } function linkSelection(url) { this.execCommand('createLink', false, url); } function unlinkSelection() { var node = window.getSelection().getNode(); if (this.linkSelected()) window.getSelection().selectNode(node); this.execCommand('unlink', false, null); } function linkSelected() { var node = window.getSelection().getNode(); return node ? node.tagName.toUpperCase() == 'A' : false; } function formatblockSelection(element){ this.execCommand('formatblock', false, element); } function toggleOrderedList() { var selection, node; selection = window.getSelection(); node = selection.getNode(); if (this.orderedListSelected() && !node.match("ol li:last-child, ol li:last-child *")) { selection.selectNode(node.up("ol")); } else if (this.unorderedListSelected()) { selection.selectNode(node.up("ul")); } this.execCommand('insertorderedlist', false, null); } function insertOrderedList() { this.toggleOrderedList(); } function orderedListSelected() { var element = window.getSelection().getNode(); if (element) return element.match('*[contenteditable=""] ol, *[contenteditable=true] ol, *[contenteditable=""] ol *, *[contenteditable=true] ol *'); return false; } function toggleUnorderedList() { var selection, node; selection = window.getSelection(); node = selection.getNode(); if (this.unorderedListSelected() && !node.match("ul li:last-child, ul li:last-child *")) { selection.selectNode(node.up("ul")); } else if (this.orderedListSelected()) { selection.selectNode(node.up("ol")); } this.execCommand('insertunorderedlist', false, null); } function insertUnorderedList() { this.toggleUnorderedList(); } function unorderedListSelected() { var element = window.getSelection().getNode(); if (element) return element.match('*[contenteditable=""] ul, *[contenteditable=true] ul, *[contenteditable=""] ul *, *[contenteditable=true] ul *'); return false; } function insertImage(url) { this.execCommand('insertImage', false, url); } function insertHTML(html) { if (Prototype.Browser.IE) { var range = window.document.selection.createRange(); range.pasteHTML(html); range.collapse(false); range.select(); } else { this.execCommand('insertHTML', false, html); } } function execCommand(command, ui, value) { var handler = this.commands.get(command); if (handler) { handler.bind(this)(value); } else { try { window.document.execCommand(command, ui, value); } catch(e) { return null; } } document.activeElement.fire("field:change"); } function queryCommandState(state) { var handler = this.queryCommands.get(state); if (handler) { return handler.bind(this)(); } else { try { return window.document.queryCommandState(state); } catch(e) { return null; } } } function getSelectedStyles() { var styles = $H({}); var editor = this; editor.styleSelectors.each(function(style){ var node = editor.selection.getNode(); styles.set(style.first(), Element.getStyle(node, style.last())); }); return styles; } return { boldSelection: boldSelection, boldSelected: boldSelected, underlineSelection: underlineSelection, underlineSelected: underlineSelected, italicSelection: italicSelection, italicSelected: italicSelected, strikethroughSelection: strikethroughSelection, indentSelection: indentSelection, outdentSelection: outdentSelection, toggleIndentation: toggleIndentation, indentSelected: indentSelected, fontSelection: fontSelection, fontSizeSelection: fontSizeSelection, colorSelection: colorSelection, backgroundColorSelection: backgroundColorSelection, alignSelection: alignSelection, alignSelected: alignSelected, linkSelection: linkSelection, unlinkSelection: unlinkSelection, linkSelected: linkSelected, formatblockSelection: formatblockSelection, toggleOrderedList: toggleOrderedList, insertOrderedList: insertOrderedList, orderedListSelected: orderedListSelected, toggleUnorderedList: toggleUnorderedList, insertUnorderedList: insertUnorderedList, unorderedListSelected: unorderedListSelected, insertImage: insertImage, insertHTML: insertHTML, execCommand: execCommand, queryCommandState: queryCommandState, getSelectedStyles: getSelectedStyles, commands: $H({}), queryCommands: $H({ link: linkSelected, orderedlist: orderedListSelected, unorderedlist: unorderedListSelected }), styleSelectors: $H({ fontname: 'fontFamily', fontsize: 'fontSize', forecolor: 'color', hilitecolor: 'backgroundColor', backcolor: 'backgroundColor' }) }; })(window); if (Prototype.Browser.IE) { Object.extend(Selection.prototype, (function() { function setBookmark() { var bookmark = $('bookmark'); if (bookmark) bookmark.remove(); bookmark = new Element('span', { 'id': 'bookmark' }).update(" "); var parent = new Element('div'); parent.appendChild(bookmark); var range = this._document.selection.createRange(); range.collapse(); range.pasteHTML(parent.innerHTML); } function moveToBookmark() { var bookmark = $('bookmark'); if (!bookmark) return; var range = this._document.selection.createRange(); range.moveToElementText(bookmark); range.collapse(); range.select(); bookmark.remove(); } return { setBookmark: setBookmark, moveToBookmark: moveToBookmark } })()); } else { Object.extend(Selection.prototype, (function() { function setBookmark() { var bookmark = $('bookmark'); if (bookmark) bookmark.remove(); bookmark = new Element('span', { 'id': 'bookmark' }).update(" "); this.getRangeAt(0).insertNode(bookmark); } function moveToBookmark() { var bookmark = $('bookmark'); if (!bookmark) return; var range = document.createRange(); range.setStartBefore(bookmark); this.removeAllRanges(); this.addRange(range); bookmark.remove(); } return { setBookmark: setBookmark, moveToBookmark: moveToBookmark } })()); } (function() { function cloneWithAllowedAttributes(element, allowedAttributes) { var result = new Element(element.tagName), length = allowedAttributes.length, i; element = $(element); for (i = 0; i < allowedAttributes.length; i++) { attribute = allowedAttributes[i]; if (element.hasAttribute(attribute)) { result.writeAttribute(attribute, element.readAttribute(attribute)); } } return result; } function withEachChildNodeOf(element, callback) { var nodes = $A(element.childNodes), length = nodes.length, i; for (i = 0; i < length; i++) callback(nodes[i]); } function sanitizeNode(node, tagsToRemove, tagsToAllow, tagsToSkip) { var parentNode = node.parentNode; switch (node.nodeType) { case Node.ELEMENT_NODE: var tagName = node.tagName.toLowerCase(); if (tagsToSkip) { var newNode = node.cloneNode(false); withEachChildNodeOf(node, function(childNode) { newNode.appendChild(childNode); sanitizeNode(childNode, tagsToRemove, tagsToAllow, tagsToSkip); }); parentNode.insertBefore(newNode, node); } else if (tagName in tagsToAllow) { var newNode = cloneWithAllowedAttributes(node, tagsToAllow[tagName]); withEachChildNodeOf(node, function(childNode) { newNode.appendChild(childNode); sanitizeNode(childNode, tagsToRemove, tagsToAllow, tagsToSkip); }); parentNode.insertBefore(newNode, node); } else if (!(tagName in tagsToRemove)) { withEachChildNodeOf(node, function(childNode) { parentNode.insertBefore(childNode, node); sanitizeNode(childNode, tagsToRemove, tagsToAllow, tagsToSkip); }); } case Node.COMMENT_NODE: parentNode.removeChild(node); } } Element.addMethods({ sanitizeContents: function(element, options) { element = $(element); var tagsToRemove = {}; (options.remove || "").split(",").each(function(tagName) { tagsToRemove[tagName.strip()] = true; }); var tagsToAllow = {}; (options.allow || "").split(",").each(function(selector) { var parts = selector.strip().split(/[\[\]]/); var tagName = parts[0], allowedAttributes = parts.slice(1).grep(/./); tagsToAllow[tagName] = allowedAttributes; }); var tagsToSkip = options.skip; withEachChildNodeOf(element, function(childNode) { sanitizeNode(childNode, tagsToRemove, tagsToAllow, tagsToSkip); }); return element; } }); })(); (function() { function onReadyStateComplete(document, callback) { var handler; function checkReadyState() { if (document.readyState === 'complete') { if (handler) handler.stop(); callback(); return true; } else { return false; } } handler = Element.on(document, 'readystatechange', checkReadyState); checkReadyState(); } function observeFrameContentLoaded(element) { element = $(element); var loaded, contentLoadedHandler; loaded = false; function fireFrameLoaded() { if (loaded) return; loaded = true; if (contentLoadedHandler) contentLoadedHandler.stop(); element.fire('frame:loaded'); } if (window.addEventListener) { contentLoadedHandler = document.on("DOMFrameContentLoaded", function(event) { if (element == event.element()) fireFrameLoaded(); }); } element.on('load', function() { var frameDocument; if (typeof element.contentDocument !== 'undefined') { frameDocument = element.contentDocument; } else if (typeof element.contentWindow !== 'undefined' && typeof element.contentWindow.document !== 'undefined') { frameDocument = element.contentWindow.document; } onReadyStateComplete(frameDocument, fireFrameLoaded); }); return element; } function onFrameLoaded(element, callback) { element.on('frame:loaded', callback); element.observeFrameContentLoaded(); } Element.addMethods({ observeFrameContentLoaded: observeFrameContentLoaded, onFrameLoaded: onFrameLoaded }); })(); document.on("dom:loaded", function() { if ('onselectionchange' in document) { var selectionChangeHandler = function() { var range = document.selection.createRange(); var element = range.parentElement(); $(element).fire("selection:change"); } document.on("selectionchange", selectionChangeHandler); } else { var previousRange; var selectionChangeHandler = function() { var element = document.activeElement; var elementTagName = element.tagName.toLowerCase(); if (elementTagName == "textarea" || elementTagName == "input") { previousRange = null; $(element).fire("selection:change"); } else { var selection = window.getSelection(); if (selection.rangeCount < 1) return; var range = selection.getRangeAt(0); if (range && range.equalRange(previousRange)) return; previousRange = range; element = range.commonAncestorContainer; while (element.nodeType == Node.TEXT_NODE) element = element.parentNode; $(element).fire("selection:change"); } }; document.on("mouseup", selectionChangeHandler); document.on("keyup", selectionChangeHandler); } }); WysiHat.Formatting = (function() { var ACCUMULATING_LINE = {}; var EXPECTING_LIST_ITEM = {}; var ACCUMULATING_LIST_ITEM = {}; return { getBrowserMarkupFrom: function(applicationMarkup) { var container = new Element("div").update(applicationMarkup); function spanify(element, style) { element.replace( '' + element.innerHTML + '' ); } function convertStrongsToSpans() { container.select("strong").each(function(element) { spanify(element, "font-weight: bold"); }); } function convertEmsToSpans() { container.select("em").each(function(element) { spanify(element, "font-style: italic"); }); } function convertDivsToParagraphs() { container.select("div").each(function(element) { element.replace("

    " + element.innerHTML + "

    "); }); } if (Prototype.Browser.WebKit || Prototype.Browser.Gecko) { convertStrongsToSpans(); convertEmsToSpans(); } else if (Prototype.Browser.IE || Prototype.Browser.Opera) { convertDivsToParagraphs(); } return container.innerHTML; }, getApplicationMarkupFrom: function(element) { var mode = ACCUMULATING_LINE, result, container, line, lineContainer, previousAccumulation; function walk(nodes) { var length = nodes.length, node, tagName, i; for (i = 0; i < length; i++) { node = nodes[i]; if (node.nodeType == Node.ELEMENT_NODE) { tagName = node.tagName.toLowerCase(); open(tagName, node); walk(node.childNodes); close(tagName); } else if (node.nodeType == Node.TEXT_NODE) { read(node.nodeValue); } } } function open(tagName, node) { if (mode == ACCUMULATING_LINE) { if (isBlockElement(tagName)) { if (isEmptyParagraph(node)) { accumulate(new Element("br")); } flush(); if (isListElement(tagName)) { container = insertList(tagName); mode = EXPECTING_LIST_ITEM; } } else if (isLineBreak(tagName)) { if (isLineBreak(getPreviouslyAccumulatedTagName())) { previousAccumulation.parentNode.removeChild(previousAccumulation); flush(); } accumulate(node.cloneNode(false)); if (!previousAccumulation.previousNode) flush(); } else { accumulateInlineElement(tagName, node); } } else if (mode == EXPECTING_LIST_ITEM) { if (isListItemElement(tagName)) { mode = ACCUMULATING_LIST_ITEM; } } else if (mode == ACCUMULATING_LIST_ITEM) { if (isLineBreak(tagName)) { accumulate(node.cloneNode(false)); } else if (!isBlockElement(tagName)) { accumulateInlineElement(tagName, node); } } } function close(tagName) { if (mode == ACCUMULATING_LINE) { if (isLineElement(tagName)) { flush(); } if (line != lineContainer) { lineContainer = lineContainer.parentNode; } } else if (mode == EXPECTING_LIST_ITEM) { if (isListElement(tagName)) { container = result; mode = ACCUMULATING_LINE; } } else if (mode == ACCUMULATING_LIST_ITEM) { if (isListItemElement(tagName)) { flush(); mode = EXPECTING_LIST_ITEM; } if (line != lineContainer) { lineContainer = lineContainer.parentNode; } } } function isBlockElement(tagName) { return isLineElement(tagName) || isListElement(tagName); } function isLineElement(tagName) { return tagName == "p" || tagName == "div"; } function isListElement(tagName) { return tagName == "ol" || tagName == "ul"; } function isListItemElement(tagName) { return tagName == "li"; } function isLineBreak(tagName) { return tagName == "br"; } function isEmptyParagraph(node) { return node.tagName.toLowerCase() == "p" && node.childNodes.length == 0; } function read(value) { accumulate(document.createTextNode(value)); } function accumulateInlineElement(tagName, node) { var element = node.cloneNode(false); if (tagName == "span") { if ($(node).getStyle("fontWeight") == "bold") { element = new Element("strong"); } else if ($(node).getStyle("fontStyle") == "italic") { element = new Element("em"); } } accumulate(element); lineContainer = element; } function accumulate(node) { if (mode != EXPECTING_LIST_ITEM) { if (!line) line = lineContainer = createLine(); previousAccumulation = node; lineContainer.appendChild(node); } } function getPreviouslyAccumulatedTagName() { if (previousAccumulation && previousAccumulation.nodeType == Node.ELEMENT_NODE) { return previousAccumulation.tagName.toLowerCase(); } } function flush() { if (line && line.childNodes.length) { container.appendChild(line); line = lineContainer = null; } } function createLine() { if (mode == ACCUMULATING_LINE) { return new Element("div"); } else if (mode == ACCUMULATING_LIST_ITEM) { return new Element("li"); } } function insertList(tagName) { var list = new Element(tagName); result.appendChild(list); return list; } result = container = new Element("div"); walk(element.childNodes); flush(); return result.innerHTML; } }; })(); WysiHat.Toolbar = Class.create((function() { function initialize(editor) { this.editor = editor; this.element = this.createToolbarElement(); } function createToolbarElement() { var toolbar = new Element('div', { 'class': 'editor_toolbar' }); this.editor.insert({before: toolbar}); return toolbar; } function addButtonSet(set) { $A(set).each(function(button){ this.addButton(button); }.bind(this)); } function addButton(options, handler) { options = $H(options); if (!options.get('name')) options.set('name', options.get('label').toLowerCase()); var name = options.get('name'); var button = this.createButtonElement(this.element, options); var handler = this.buttonHandler(name, options); this.observeButtonClick(button, handler); var handler = this.buttonStateHandler(name, options); this.observeStateChanges(button, name, handler); } function createButtonElement(toolbar, options) { var button = new Element('a', { 'class': 'button', 'href': '#' }); button.update('' + options.get('label') + ''); button.addClassName(options.get('name')); toolbar.appendChild(button); return button; } function buttonHandler(name, options) { if (options.handler) return options.handler; else if (options.get('handler')) return options.get('handler'); else return function(editor) { editor.execCommand(name); }; } function observeButtonClick(element, handler) { element.on('click', function(event) { handler(this.editor); event.stop(); }.bind(this)); } function buttonStateHandler(name, options) { if (options.query) return options.query; else if (options.get('query')) return options.get('query'); else return function(editor) { return editor.queryCommandState(name); }; } function observeStateChanges(element, name, handler) { var previousState; this.editor.on("selection:change", function(event) { var state = handler(this.editor); if (state != previousState) { previousState = state; this.updateButtonState(element, name, state); } }.bind(this)); } function updateButtonState(element, name, state) { if (state) element.addClassName('selected'); else element.removeClassName('selected'); } return { initialize: initialize, createToolbarElement: createToolbarElement, addButtonSet: addButtonSet, addButton: addButton, createButtonElement: createButtonElement, buttonHandler: buttonHandler, observeButtonClick: observeButtonClick, buttonStateHandler: buttonStateHandler, observeStateChanges: observeStateChanges, updateButtonState: updateButtonState }; })()); WysiHat.Toolbar.ButtonSets = {}; WysiHat.Toolbar.ButtonSets.Basic = $A([ { label: "Bold" }, { label: "Underline" }, { label: "Italic" } ]); var Alice = { }; Object.extend(Alice, { uncacheGravatar: function(content) { if (!this.timestamp) { var date = new Date(); this.timestamp = date.getTime(); } return content.replace( /(src=".*?gravatar.com\/avatar\/[^?]*\?)/gi, "$1time=" + this.timestamp + "&" ); }, epochToLocal: function(epoch, format) { var date = new Date(parseInt(epoch) * 1000); if (!date) return epoch; var hours = date.getHours(); if (format == "12") { var ap; if (hours > 12) { hours -= 12; ap = "p"; } else { ap = "a" } return sprintf("%d:%02d%s", hours, date.getMinutes(), ap); } return sprintf("%02d:%02d", hours, date.getMinutes()); }, stripNick: function(html) { return html.replace(/
    .*<\/div>/, ''); }, growlNotify: function(message) { if (window.fluid) { window.fluid.showGrowlNotification({ title: message.window.title + ": " + message.nick, description: message.body.unescapeHTML(), priority: 1, sticky: false, identifier: message.msgid }); } else if (window.webkitNotifications) { if (window.webkitNotifications.checkPermission() == 0) { var popup = window.webkitNotifications.createNotification( "http://static.usealice.org/image/alice.png", message.window.title + ": " + message.nick, message.body.unescapeHTML() ); popup.ondisplay = function() { setTimeout(function () {popup.cancel();}, 3000); }; popup.show(); } } }, isSpecialKey: function(keyCode) { var special_keys = [ 16,27,9,32,13,8,145,20,144,19,45,36,46,35,33,34,37,38,39, 40,17,18,91,112,113,114,115,116,117,118,119,120,121,122,123 ]; return special_keys.indexOf(keyCode) > -1; }, loadInlineImage: function(image) { var maxWidth = arguments.callee.maxWidth || 300; var maxHeight = arguments.callee.maxHeight || 300; image.style.visibility = 'hidden'; if (image.height > image.width && image.height > maxHeight) { image.style.width = 'auto'; image.style.height = maxHeight + 'px'; } else if (image.width > maxWidth) { image.style.height = 'auto'; image.style.width = maxWidth + 'px'; } else { image.style.height = 'auto'; } image.style.display = 'block'; image.style.visibility = 'visible'; setTimeout(function () { var messagelist = image.up(".message_wrap"); messagelist.scrollTop = messagelist.scrollHeight; }, 50); }, playAudio: function(image, audio) { image.src = '/static/image/pause.png'; if (! audio) { var url = image.nextSibling.href; audio = new Audio(url); audio.addEventListener('ended', function () { image.src = '/static/image/play.png'; image.onclick = function () { Alice.playAudio(image, audio) }; }); } audio.play(); image.onclick = function() { audio.pause(); this.src = '/static/image/play.png'; this.onclick = function () { Alice.playAudio(this, audio) }; }; }, prefs: { addHighlight: function (alias) { var channel = prompt("Enter a word to highlight."); if (channel) $('highlights').insert(""); return false; }, removeHighlights: function (alias) { $A($('highlights').options).each(function (option) { if (option.selected) option.remove()}); return false; }, remove: function() { alice.windows().each(function(win) { win.input.disabled = false; }); $('prefs').remove(); }, submit: function(form) { var options = {highlights: []}; ["images", "avatars", "alerts"].each(function (pref) { options[pref] = $(pref).checked ? "show" : "hide"; }); $A($("highlights").options).each(function(option) { options.highlights.push(option.value); }); ["style", "timeformat"].each(function(pref) { options[pref] = $(pref).value; }); alice.options = options; new Ajax.Request('/save', { method: 'get', parameters: options, onSuccess: function(){Alice.prefs.remove()} }); return false; } }, connections: { disconnectServer: function (alias) { $(alias + "_status").className = "disconnected"; $(alias + "_status").innerHTML = "disconnected"; $(alias + "_connection").innerHTML = "connect"; $(alias + "_connection").onclick = function (e) { e.stop(); serverConnection(alias, "connect"); }; }, connectServer: function (alias) { $(alias + "_status").className = "connected"; $(alias + "_status").innerHTML = "connected"; $(alias + "_connection").innerHTML = "disconnect"; $(alias + "_connection").onclick = function (e) { e.stop(); serverConnection(alias, "disconnect"); }; }, showConnection: function (alias) { $$("div#servers .active").invoke("removeClassName","active"); $("setting_" + alias).addClassName("active"); $("menu_" + alias).addClassName("active"); }, addChannel: function (alias) { var channel = prompt("Please enter a channel name."); if (channel) $("channels_" + alias).insert(""); return false; }, addCommand: function (alias) { var command = prompt("Please enter a channel name."); if (command) $("on_connect_" + alias).insert(""); return false; }, removeCommands: function (alias) { $A($("on_connect_" + alias).options).each(function (option) { if (option.selected) option.remove()}); return false; }, removeChannels: function (alias) { $A($("channels_" + alias).options).each(function (option) { if (option.selected) option.remove()}); return false; }, addServer: function () { var name = prompt("Please enter a name for this server."); if (! name) return; new Ajax.Request("/serverconfig", { parameters: {name: name}, method: 'get', onSuccess: function (trans) { var data = trans.responseText.evalJSON(); $$('#config_data div.setting').invoke('removeClassName',"active"); $$('#servers li').invoke('removeClassName',"active"); $('config_data').insert(data.config); $('connections').insert(data.listitem); } }); }, removeServer: function () { var alias = $('connections').down('.active').id.replace(/^menu_/, ""); if (alias && confirm("Are you sure you want to remove "+alias+"?")) { $("menu_"+alias).remove(); $("setting_"+alias).remove(); $("connections").down("li", 1).addClassName("active"); $("config_data").down("div").addClassName("active"); } }, submit: function(form) { $$('#servers .channelselect').each(function(select) { $A(select.options).each(function(option) { option.selected = true; }); }); new Ajax.Request('/save', { method: 'get', parameters: form.serialize(), onSuccess: function(){Alice.connections.remove()} }); return false; }, remove: function() { alice.windows().each(function(win) { win.input.disabled = false; }); $('servers').remove(); }, serverConnection: function(alias, action) { new Ajax.Request('/say', { method: 'get', parameters: { msg: '/' + action + ' ' + alias, source: alice.activeWindow().id } }); return false; } } }); Element.addMethods({ redraw: function(element){ element = $(element); var n = document.createTextNode(' '); element.appendChild(n); (function(){n.parentNode.removeChild(n)}).defer(); return element; } }); Alice.Application = Class.create({ initialize: function() { this.isFocused = true; this.window_map = new Hash(); this.previousFocus = 0; this.connection = new Alice.Connection(this); this.filters = []; this.keyboard = new Alice.Keyboard(this); this.isPhone = window.navigator.platform.match(/(android|iphone)/i) ? 1 : 0; this.isMobile = this.isPhone || Prototype.Browser.MobileSafari; window.onload = function () { setTimeout(this.connection.connect.bind(this.connection), 1000); }.bind(this); this.makeSortable(); }, actionHandlers: { join: function (action) { var win = this.getWindow(action['window'].id); if (!win) { this.insertWindow(action['window'].id, action.html); win = new Alice.Window(this, action['window'].id, action['window'].title, false, action['window'].hashtag); this.addWindow(win); } else { win.enable(); } win.nicks = action.nicks; }, part: function (action) { this.closeWindow(action['window'].id); }, nicks: function (action) { var win = this.getWindow(action['window'].id); if (win) win.nicks = action.nicks; }, alert: function (action) { this.activeWindow().showAlert(action['body']); }, clear: function (action) { var win = this.getWindow(action['window'].id); if (win) { win.messages.down("ul").update(""); win.lastNick = ""; } }, connect: function (action) { action.windows.each(function (win_info) { var win = this.getWindow(win_info.id); if (win) { win.enable(); } }.bind(this)); if ($('servers')) { Alice.connections.connectServer(action.session); } }, disconnect: function (action) { action.windows.each(function (win_info) { var win = this.getWindow(win_info.id); if (win) { win.disable(); } }.bind(this)); if ($('servers')) { Alice.connections.disconnectServer(action.session); } }, focus: function (action) { if (!action.window_number) return; if (action.window_number == "next") { this.nextWindow(); } else if (action.window_number.match(/^prev/)) { this.previousWindow(); } else if (action.window_number.match(/^\d+$/)) { var tab = $('tabs').down('li', action.window_number); if (tab) { var window_id = tab.id.replace('_tab',''); this.getWindow(window_id).focus(); } } } }, toggleHelp: function() { var help = $('help'); help.visible() ? help.hide() : help.show(); }, toggleConfig: function(e) { this.connection.getConfig(function (transport) { alice.activeWindow().input.disabled = true; $('container').insert(transport.responseText); }.bind(this)); e.stop(); }, togglePrefs: function(e) { this.connection.getPrefs(function (transport) { alice.activeWindow().input.disabled = true; $('container').insert(transport.responseText); }.bind(this)); e.stop(); }, toggleLogs: function(e) { if (this.logWindow && !this.logWindow.closed && this.logWindow.focus) { this.logWindow.focus(); } else { this.logWindow = window.open(null, "logs", "resizable=no,scrollbars=no,statusbar=no, toolbar=no,location=no,width=500,height=480"); this.connection.getLog(function (transport) { this.logWindow.document.write(transport.responseText); }.bind(this)); } e.stop(); }, windows: function () { return this.window_map.values(); }, nth_window: function(n) { var tab = $('tabs').down('li', n); if (tab) { var m = tab.id.match(/([^_]+)_tab/); if (m) { return this.window_map.get(m[1]); } } }, openWindow: function(element, title, active, hashtag) { var win = new Alice.Window(this, element, title, active, hashtag); this.addWindow(win); if (active) { win.focus(); } return win; }, addWindow: function(win) { this.window_map.set(win.id, win); if (window.fluid) window.fluid.addDockMenuItem(win.title, win.focus.bind(win)); }, removeWindow: function(win) { if (win.active) this.focusLast(); if (window.fluid) window.fluid.removeDockMenuItem(win.title); if (win.id == this.previousFocus.id) { this.previousFocus = 0; } this.window_map.unset(win.id); this.connection.closeWindow(win); win = null; }, getWindow: function(windowId) { return this.window_map.get(windowId); }, activeWindow: function() { var windows = this.windows(); for (var i=0; i < windows.length; i++) { if (windows[i].active) return windows[i]; } if (windows[0]) return windows[0]; }, addFilters: function(list) { this.filters = this.filters.concat(list); }, applyFilters: function(content) { return this.filters.inject(content, function(value, filter) { return filter(value); }); }, nextWindow: function() { var active = this.activeWindow(); var nextTab = active.tab.next(); if (!nextTab) nextTab = $$('ul#tabs li').first(); if (!nextTab) return; var id = nextTab.id.replace('_tab',''); if (id != active.id) { this.getWindow(id).focus(); } }, focusLast: function() { if (this.previousFocus && this.previousFocus.id != this.activeWindow().id) this.previousFocus.focus(); else this.previousWindow(); }, previousWindow: function() { var active = this.activeWindow(); var previousTab = this.activeWindow().tab.previous(); if (!previousTab) previousTab = $$('ul#tabs li').last(); if (!previousTab) return; var id = previousTab.id.replace('_tab',''); if (id != active.id) this.getWindow(id).focus(); }, closeWindow: function(windowId) { var win = this.getWindow(windowId); if (win) win.close(); }, insertWindow: function(windowId, html) { if (!$(windowId)) { $('windows').insert(html['window']); $('tabs').insert(html.tab); $('tab_overflow_overlay').insert(html.select); $(windowId+"_tab_overflow_button").selected = false; this.activeWindow().tabOverflowButton.selected = true; this.makeSortable(); } }, highlightChannelSelect: function() { $('tab_overflow_button').addClassName('unread'); }, unHighlightChannelSelect: function() { $('tab_overflow_button').removeClassName('unread'); }, updateChannelSelect: function() { var windows = this.windows(); for (var i=0; i < windows.length; i++) { var win = windows[i]; if ((win.tab.hasClassName('unread') || win.tab.hasClassName('highlight')) && win.isTabWrapped()) { this.highlightChannelSelect(); return; } } this.unHighlightChannelSelect(); }, handleAction: function(action) { if (this.actionHandlers[action.event]) { this.actionHandlers[action.event].call(this,action); } }, displayMessage: function(message) { var win = this.getWindow(message['window'].id); if (win) { win.addMessage(message); } else { this.connection.requestWindow( message['window'].title, message['window'].id, message ); } }, focusHash: function(hash) { if (!hash) hash = window.location.hash; if (hash) { hash = decodeURIComponent(hash); hash = hash.replace(/^#/, ""); var windows = this.windows(); for (var i = 0; i < windows.length; i++) { var win = windows[i]; if (win.hashtag == hash) { if (win && !win.active) win.focus(); return; } } } }, makeSortable: function() { Sortable.create('tabs', { overlap: 'horizontal', constraint: 'horizontal', format: /(.+)/, onUpdate: function (res) { var tabs = res.childElements(); var order = tabs.collect(function(t){ var m = t.id.match(/([^_]+)_tab/); if (m) return m[1] }); if (order.length) this.connection.sendTabOrder(order); }.bind(this) }); }, addMissed: function() { if (!window.fluid) return; window.fluid.dockBadge ? window.fluid.dockBadge++ : window.fluid.dockBadge = 1; }, clearMissed: function() { if (!window.fluid) return; window.fluid.dockBadge = ""; } }); Alice.Connection = Class.create({ initialize: function(application) { this.application = application; this.len = 0; this.aborting = false; this.request = null; this.seperator = "--xalicex\n"; this.msgid = 0; this.reconnect_count = 0; this.reconnecting = false; }, closeConnection: function() { this.aborting = true; if (this.request && this.request.transport) this.request.transport.abort(); this.aborting = false; }, connect: function() { if (this.reconnect_count > 3) { this.aborting = true; this.application.activeWindow().showAlert("Alice server is not responding (reconnect)"); return; } this.closeConnection(); this.len = 0; this.reconnect_count++; var now = new Date(); console.log("opening new connection starting at message " + this.msgid); this.request = new Ajax.Request('/stream', { method: 'get', parameters: {msgid: this.msgid, t: now.getTime() / 1000}, onException: this.handleException.bind(this), onInteractive: this.handleUpdate.bind(this), onComplete: this.handleComplete.bind(this) }); }, reconnect: function () { this.reconnecting = true; this.reconnect_count = 0; this.connect(); }, handleException: function(request, exception) { console.log("encountered an error with stream."); if (!this.aborting) setTimeout(this.connect.bind(this), 2000); }, handleComplete: function(transport) { console.log("connection was closed cleanly."); if (!this.aborting) setTimeout(this.connect.bind(this), 2000); }, handleUpdate: function(transport) { if (this.reconnecting) { this.application.activeWindow().showHappyAlert("Reconnected to the Alice server"); this.reconnecting = false; } this.reconnect_count = 0; var time = new Date(); var data = transport.responseText.slice(this.len); var start, end; start = data.indexOf(this.seperator); if (start > -1) { start += this.seperator.length; end = data.indexOf(this.seperator, start); if (end == -1) return; } else return; this.len += (end + this.seperator.length) - start; data = data.slice(start, end); try { data = data.evalJSON(); var queue = data.queue; var length = queue.length; for (var i=0; i 5) { console.log("lag is " + Math.round(lag) + "s, reconnecting."); this.connect(); } }, requestWindow: function(title, windowId, message) { new Ajax.Request('/say', { method: 'post', parameters: {source: windowId, msg: "/create " + title}, onSuccess: function (transport) { this.handleUpdate(transport); if (message) { setTimeout(function() { this.application.displayMessage(message) }.bind(this), 1000); } }.bind(this) }); }, closeWindow: function(win) { new Ajax.Request('/say', { method: 'post', parameters: {source: win.id, msg: "/close"} }); }, getConfig: function(callback) { new Ajax.Request('/config', { method: 'get', onSuccess: callback }); }, getPrefs: function(callback) { new Ajax.Request('/prefs', { method: 'get', onSuccess: callback }); }, getLog: function(callback) { new Ajax.Request('/logs', { method: 'get', onSuccess: callback }); }, sendMessage: function(form) { new Ajax.Request('/say', { method: 'post', parameters: form.serialize(), onException: function (request, exception) { alert("There was an error sending a message."); } }); }, sendTabOrder: function (windows) { new Ajax.Request('/tabs', { method: 'post', parameters: {tabs: windows} }); }, sendPing: function() { new Ajax.Request('/ping'); } }); Alice.Window = Class.create({ initialize: function(application, element, title, active, hashtag) { this.application = application; this.element = $(element); this.title = title; this.hashtag = hashtag; this.id = this.element.identify(); this.active = active; this.tab = $(this.id + "_tab"); this.input = new Alice.Input(this, this.id + "_msg"); this.tabButton = $(this.id + "_tab_button"); this.tabOverflowButton = $(this.id + "_tab_overflow_button"); this.form = $(this.id + "_form"); this.topic = $(this.id + "_topic"); if (this.topic) { var orig_height = this.topic.getStyle("height"); this.topic.observe("click", function(e) { if (this.topic.getStyle("height") == orig_height) { this.topic.setStyle({height: "auto"}); } else { this.topic.setStyle({height: orig_height}); } }.bind(this)); } this.messages = this.element.down('.message_wrap'); this.submit = $(this.id + "_submit"); this.nicksVisible = false; this.visibleNick = ""; this.visibleNickTimeout = ""; this.nicks = []; this.messageLimit = 250; this.submit.observe("click", function (e) {this.input.send(); e.stop()}.bind(this)); this.tab.observe("mousedown", function(e) { if (!this.active) {this.focus(); this.focusing = true} }.bind(this)); this.tab.observe("click", function(e) {this.focusing = false}.bind(this)); this.tabButton.observe("click", function(e) { if (this.active && !this.focusing) this.close()}.bind(this)); this.messages.observe("mouseover", this.showNick.bind(this)); if (Prototype.Browser.Gecko) { this.resizeMessagearea(); this.scrollToBottom(); } else if (this.application.isMobile) { this.messageLimit = 50; this.messages.select("li").reverse().slice(50).invoke("remove"); } if (this.active) this.scrollToBottom(true); this.makeTopicClickable(); setTimeout(function () { this.messages.select('li.message div.msg').each(function (msg) { msg.innerHTML = application.applyFilters(msg.innerHTML); }); }.bind(this), 1000); }, isTabWrapped: function() { return this.tab.offsetTop > 0; }, unFocus: function() { this.active = false; this.input.uncancelNextFocus(); this.element.removeClassName('active'); this.tab.removeClassName('active'); this.tabOverflowButton.selected = false; }, showNick: function (e) { var li = e.findElement("#" + this.id + " ul.messages li.message"); if (li) { if (this.nicksVisible || li == this.visibleNick) return; clearTimeout(this.visibleNickTimeout); this.visibleNick = li; var nick; var time; if (li.hasClassName("consecutive")) { var stem = li.previous("li:not(.consecutive)"); if (!stem) return; nick = stem.down(".nickhint"); time = stem.down(".timehint"); } else { nick = li.down(".nickhint"); time = li.down(".timehint"); } if (nick || time) { this.visibleNickTimeout = setTimeout(function(nick, time) { if (nick) { nick.style.opacity = 1; nick.style.webkitTransition = "opacity 0.1s ease-in-out"; } if (time) { time.style.webkitTransition = "opacity 0.1s ease-in-out"; time.style.opacity = 1; } setTimeout(function(){ if (this.nicksVisible) return; if (nick) { nick.style.webkitTransition = "opacity 0.25s ease-in"; nick.style.opacity = 0; } if (time) { time.style.webkitTransition = "opacity 0.25s ease-in"; time.style.opacity = 0; } }.bind(this, nick, time) , 1000); }.bind(this, nick, time), 500); } } else { this.visibleNick = ""; clearTimeout(this.visibleNickTimeout); } }, toggleNicks: function () { if (this.nicksVisible) { this.messages.select("span.nickhint").each(function(span){ span.style.webkitTransition = "opacity 0.1s ease-in"; span.style.opacity = 0; }); this.messages.select("div.timehint").each(function(span){ span.style.webkitTransition = "opacity 0.1s ease-in"; span.style.opacity = 0; }); } else { this.messages.select("span.nickhint").each(function(span){ span.style.webkitTransition = "opacity 0.1s ease-in-out"; span.style.opacity = 1; }); this.messages.select("div.timehint").each(function(span){ span.style.webkitTransition = "opacity 0.1s ease-in-out"; span.style.opacity = 1; }); } this.nicksVisible = !this.nicksVisible; }, focus: function(event) { document.title = this.title; this.application.previousFocus = this.application.activeWindow(); this.application.windows().invoke("unFocus"); this.active = true; this.tab.addClassName('active'); this.element.addClassName('active'); this.tabOverflowButton.selected = true; this.markRead(); this.scrollToBottom(true); if (!this.application.isMobile) this.input.focus(); if (Prototype.Browser.Gecko) { this.resizeMessagearea(); this.scrollToBottom(); } this.element.redraw(); window.location.hash = this.hashtag; window.location = window.location.toString(); this.application.updateChannelSelect(); }, markRead: function () { this.tab.removeClassName("unread"); this.tab.removeClassName("highlight"); this.tabOverflowButton.removeClassName("unread"); }, disable: function () { this.markRead(); this.tab.addClassName('disabled'); }, enable: function () { this.tab.removeClassName('disabled'); }, close: function(event) { this.application.removeWindow(this); this.tab.remove(); this.element.remove(); this.tabOverflowButton.remove(); }, displayTopic: function(topic) { this.topic.update(topic); this.makeTopicClickable(); }, makeTopicClickable: function() { if (!this.topic) return; this.topic.innerHTML = this.topic.innerHTML.replace(/(https?:\/\/[^\s]+)/ig, '$1'); }, resizeMessagearea: function() { var top = this.messages.up().cumulativeOffset().top; var bottom = this.input.element.getHeight() + 14; this.messages.setStyle({ position: 'absolute', top: top+"px", bottom: bottom + "px", right: "0px", left: "0px", height: 'auto' }); }, showHappyAlert: function (message) { this.messages.down('ul').insert( "
  • "+message+"
  • " ); this.scrollToBottom(); }, showAlert: function (message) { this.messages.down('ul').insert( "
  • "+message+"
  • " ); this.scrollToBottom(); }, addMessage: function(message) { if (!message.html) return; this.messages.down('ul').insert(message.html); var li = this.messages.down('ul.messages > li:last-child'); if (message.consecutive) { var prev = li.previous(); if (prev && prev.hasClassName("avatar") && !prev.hasClassName("consecutive")) { prev.down('div.msg').setStyle({minHeight: '0px'}); } } if (message.event == "say") { var msg = li.down('div.msg'); msg.innerHTML = this.application.applyFilters(msg.innerHTML); var nick = li.down('span.nickhint'); if (nick && this.nicksVisible) { nick.style.webkitTransition = 'none 0 linear'; nick.style.opacity = 1; } var time = li.down('div.timehint'); if (time && this.nicksVisible) { time.style.webkitTransition = 'none 0 linear'; time.style.opacity = 1; } if (message.consecutive) { var avatar = li.previous(".avatar:not(.consecutive)"); if (avatar) avatar.down(".timehint").innerHTML = message.timestamp; } } else if (message.event == "topic") { this.displayTopic(message.body.escapeHTML()); } if (!this.application.isFocused && message.highlight && message.window.title != "info") { message.body = li.down(".msg").innerHTML.stripTags(); Alice.growlNotify(message); this.application.addMissed(); } if (message.nicks && message.nicks.length) this.nicks = message.nicks; if (this.element.hasClassName('active')) this.scrollToBottom(); else if (this.title != "info") { if (message.event == "say") { this.tab.addClassName("unread"); this.tabOverflowButton.addClassName("unread"); if (this.isTabWrapped()) this.application.highlightChannelSelect(); } if (message.highlight) { this.tab.addClassName("highlight"); } } var messages = this.messages.down('ul').childElements(); if (messages.length > this.messageLimit) messages.first().remove(); li.select("span.timestamp").each(function(elem) { elem.innerHTML = Alice.epochToLocal(elem.innerHTML.strip(), this.application.options.timeformat); elem.style.opacity = 1; }.bind(this)); this.element.redraw(); }, scrollToBottom: function(force) { var bottom, height; if (!force) { var lastmsg = this.messages.down('ul.messages > li:last-child'); if (!lastmsg) return; var msgheight = lastmsg.offsetHeight; bottom = this.messages.scrollTop + this.messages.offsetHeight; height = this.messages.scrollHeight; } if (force || bottom + msgheight + 100 >= height) { this.messages.scrollTop = this.messages.scrollHeight; this.element.redraw(); } }, getNicknames: function() { return this.nicks; } }); Alice.Toolbar = Class.create(WysiHat.Toolbar, { createButtonElement: function(toolbar, options) { var button = Element('button'); button.update(options.get('label')); button.addClassName(options.get('name')); toolbar.appendChild(button); return button; }, observeButtonClick: function(element, handler) { element.on('click', function(event) { handler(this.editor, element, this); this.editor.fire("selection:change"); event.stop(); }.bind(this)); } }); Alice.Toolbar.ButtonSet = WysiHat.Toolbar.ButtonSets.Basic.concat( [ { label: "Colors", handler: function (editor, button, toolbar) { var cb = function (color, fg) { fg ? editor.colorSelection(color) : editor.backgroundColorSelection(color) }; if (toolbar.picker) { toolbar.picker.remove(); toolbar.picker = undefined; } else { toolbar.picker = new Alice.Colorpicker(button, cb); } } }, { label: "»", handler: function (editor, button, toolbar) { button.up("div.editor_toolbar").removeClassName("visible"); if (toolbar.picker) { toolbar.picker.remove(); toolbar.picker = undefined; } } } ] ); Alice.Colorpicker = Class.create({ initialize: function(button, callback) { var elem = new Element("div").addClassName("color_picker"); var toggle = new Element("div").addClassName("toggle"); var blank = new Element("span").addClassName("blank").addClassName("color"); blank.setStyle({"background-color": "none"}); blank.insert("⃠"); toggle.insert('fgbg'); toggle.insert(blank); elem.insert(toggle); var colorcontainer = new Element("div").addClassName("colors"); this.colors().each(function(color) { var box = new Element("span").addClassName("color"); box.setStyle({"background-color": color}); colorcontainer.insert(box); }); elem.insert(colorcontainer); button.up('.window').insert(elem); elem.observe("mousedown", this.clicked.bind(this)); this.elem = elem; this.cb = callback; this.fg = true; }, clicked: function(e) { e.stop(); var box = e.findElement("span.color"); if (box) { var color = box.getStyle("background-color"); if (color) this.cb(color, this.fg); return; } if (e.findElement("span#fg")) { this.elem.down("#bg").removeClassName("active"); this.elem.down("#fg").addClassName("active"); this.fg = true; return; } if (e.findElement("span#bg")) { this.elem.down("#fg").removeClassName("active"); this.elem.down("#bg").addClassName("active"); this.fg = false; return; } }, remove: function() { this.elem.remove(); }, colors: function() { return ["#fff", "#000", "#008", "#080", "#f00", "#800", "#808", "#f80", "#ff0", "#0f0", "#088", "#0ff", "#00f", "#f0f", "#888", "#ccc"]; } }); Alice.Input = Class.create({ initialize: function(win, element) { this.window = win; this.application = this.window.application; this.textarea = $(element); this.disabled = false; if (this.canContentEditable()) { this.editor = WysiHat.Editor.attach(this.textarea); this.element = this.editor; this.toolbar = new Alice.Toolbar(this.element) this.toolbar.addButtonSet(Alice.Toolbar.ButtonSet); this.toolbar.element.observe("click", function(e) { if (this.toolbar.element.hasClassName("visible")) return; this.toolbar.element.addClassName("visible"); this.focus(); }.bind(this)); var input = new Element("input", {type: "hidden", name: "html", value: 1}); this.textarea.form.appendChild(input); document.observe("mousedown", function(e) { if (!e.findElement(".editor")) this.uncancelNextFocus(); }.bind(this)); this.editor.observe("keydown", function(){this.cancelNextFocus()}.bind(this)); this.editor.observe("keyup", this.updateRange.bind(this)); this.editor.observe("mouseup", this.updateRange.bind(this)); this.editor.observe("paste", this.pasteHandler.bind(this)); this.toolbar.element.on("mouseup","button",function(){ this.cancelNextFocus(); }.bind(this)); } else { this.element = this.textarea; } this.history = []; this.index = -1; this.buffer = ""; this.completion = false; this.focused = false; this.element.observe("keypress", this.onKeyPress.bind(this)); this.element.observe("blur", this.onBlur.bind(this)); this.element.observe("keydown", this.resize.bind(this)); this.element.observe("cut", this.resize.bind(this)); this.element.observe("paste", this.resize.bind(this)); this.element.observe("change", this.resize.bind(this)); }, setValue: function(value) { this.editor ? this.editor.update(value) : this.textarea.setValue(value); }, getValue: function() { if (this.editor) { return this.editor.innerHTML; } return this.textarea.getValue(); }, onKeyPress: function(event) { if (event.keyCode != Event.KEY_TAB) { this.completion = false; } }, uncancelNextFocus: function() { this.skipThisFocus = false; }, cancelNextFocus: function() { this.skipThisFocus = true; }, focus: function(force) { if (this.disabled) return; if (!force) { if (this.focused) return; if (this.skipThisFocus) { this.skipThisFocus = false; return; } } this.focused = true; if (this.editor) { var selection = window.getSelection(); selection.removeAllRanges(); if (this.range) { selection.addRange(this.range); } else { var text = document.createTextNode(""); this.editor.appendChild(text); selection.selectNode(text); this.range = selection.getRangeAt(0); } this.editor.focus(); } else { this.textarea.focus(); } }, onBlur: function(e) { this.focused = false; }, previousCommand: function() { if (this.index-- == -1) { this.index = this.history.length - 1; this.stash(); } this.update(); }, nextCommand: function() { if (this.index++ == -1) { this.stash(); } else if (this.index == this.history.length) { this.index = -1; } this.update(); }, newLine: function() { console.log("newLine"); }, send: function() { this.application.connection.sendMessage(this.textarea.form); this.history.push(this.getValue()); this.setValue(""); if (this.editor) this.editor.update(); this.index = -1; this.stash(); this.update(); this.focus(1); }, completeNickname: function() { if (this.disabled) return; if (!this.completion) { this.completion = new Alice.Completion(this.window.getNicknames()); } this.completion.next(); }, stopCompletion: function() { if (this.completion) { this.completion.restore(); this.completion = false; } }, stash: function() { this.buffer = this.getValue(); }, update: function() { this.setValue(this.getCommand(this.index)); }, getCommand: function(index) { if (index == -1) { return this.buffer; } else { return this.history[index]; } }, resize: function() { if (this.editor) { this.textarea.setValue(this.editor.innerHTML); } (function() { if (!this.window.active) return; var height = this.getContentHeight(); if (height == 0) { this.element.setStyle({ height: null, top: 0 }); } else if (height <= 150) { this.element.setStyle({ height: height + "px", top: "-1px" }); } }).bind(this).defer(); }, getContentHeight: function() { var element = new Element("div").setStyle({ position: "absolute", visibility: "hidden", left: "-" + this.element.getWidth() + "px", width: this.element.getWidth() - 7 + "px", fontFamily: this.element.getStyle("fontFamily"), fontSize: this.element.getStyle("fontSize"), lineHeight: this.element.getStyle("lineHeight"), whiteSpace: "pre-wrap", wordWrap: "break-word" }); if (this.editor) element.addClassName("editor"); var value = this.getValue(); element.update(value.replace(/\n$/, "\n\n").replace("\n", "
    ")); $(document.body).insert(element); var height = element.getHeight(); element.remove(); return height; }, canContentEditable: function() { var element = new Element("div", {contentEditable: "true"}); return element.contentEditable != null && ! this.application.isMobile; }, updateRange: function (e) { var selection = window.getSelection(); if (selection.rangeCount > 0) { var range = selection.getRangeAt(0); this.range = range; } }, pasteHandler: function(e) { var url = e.clipboardData.getData("URL"); if (url) { e.preventDefault(); this.editor.insertHTML(url); this.updateRange(); return; } var text = e.clipboardData.getData("Text"); if (text) { e.preventDefault(); text = text.escapeHTML().replace(/\n+/g, "
    "); this.editor.insertHTML(text); this.updateRange(); return; } } }); Alice.Keyboard = Class.create({ initialize: function(application) { this.application = application; this.isMac = navigator.platform.match(/mac/i); this.enable(); this.shortcut("Cmd+C", { propagate: true }); this.shortcut("Ctrl+C", { propagate: true }); this.shortcut("Cmd+K"); this.shortcut("Cmd+B"); this.shortcut("Cmd+F"); this.shortcut("Opt+Up"); this.shortcut("Opt+Down"); this.shortcut("Opt+Enter"); this.shortcut("Cmd+Shift+M"); this.shortcut("Cmd+Shift+J"); this.shortcut("Cmd+Shift+K"); this.shortcut("Cmd+Shift+H"); this.shortcut("Enter"); this.shortcut("Esc"); this.shortcut("Tab"); for (var i = 0; i < 10; i++) { this.shortcut("Cmd+"+i); if (!this.isMac) this.shortcut("Opt+"+i); } }, shortcut: function(name, options) { var meta = this.isMac ? "Meta" : "Ctrl"; var keystroke = name.replace("Cmd", meta).replace("Opt", "Alt"), method = "on" + name.replace(/\+/g, ""); window.shortcut.add(keystroke, function(event) { if (this.enabled) { this.activeWindow = this.application.activeWindow(); if (method.match(/\d$/)) { this.onNumeric.call(this, event, method.substr(-1)); } else { this[method].call(this, event); } delete this.activeWindow; } }.bind(this), options); }, onNumeric: function(event, number) { var win = this.application.nth_window(number); if (win) win.focus(); }, onCmdC: function(event) { if (!this.activeWindow.input.focused) this.activeWindow.input.cancelNextFocus(); }, onCtrlC: function(event) { this.onCmdC(event); }, onCmdK: function() { this.activeWindow.messages.down("ul").update(""); this.activeWindow.lastNick = ""; }, onCmdShiftM: function() { this.application.windows().invoke('markRead'); }, onCmdShiftJ: function() { this.activeWindow.scrollToBottom(1); }, onCmdShiftK: function() { this.activeWindow.toggleNicks(); }, onCmdShiftH: function() { this.application.toggleHelp(); }, onCmdB: function() { this.application.previousWindow(); }, onCmdF: function() { this.application.nextWindow(); }, onOptUp: function() { this.activeWindow.input.previousCommand(); }, onOptDown: function() { this.activeWindow.input.nextCommand(); }, onOptEnter: function() { this.activeWindow.input.newLine(); }, onEnter: function() { this.activeWindow.input.send(); }, onTab: function() { this.activeWindow.input.completeNickname(); }, onEsc: function() { this.activeWindow.input.stopCompletion(); }, enable: function() { this.enabled = true; }, disable: function() { this.enabled = false; } }); Alice.Completion = Class.create({ initialize: function(candidates) { var range = this.getRange(); if (!range) return; this.element = range.startContainer; if (this.element.nodeName == "DIV") { this.element.innerHTML = ""; // removes any leading
    s var node = document.createTextNode(""); this.element.appendChild(node); var selection = window.getSelection(); selection.removeAllRanges(); selection.selectNode(node); range = selection.getRangeAt(0); this.element = node; } this.value = this.element.data; this.index = range.startOffset; this.findStem(); this.matches = this.matchAgainst(candidates); this.matchIndex = -1; }, getRange: function() { var selection = window.getSelection(); if (selection.rangeCount > 0) { return selection.getRangeAt(0); } if (document.createRange) { return document.createRange(); } return null; }, setRange: function(range) { if (!range) return; var selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); }, next: function() { if (!this.matches.length) return; if (++this.matchIndex == this.matches.length) this.matchIndex = 0; var match = this.matches[this.matchIndex]; match += this.leftOffset == 0 ? ": " : " "; this.restore(match, this.leftOffset + match.length); }, restore: function(stem, index) { this.element.data = this.stemLeft + (stem || this.stem) + this.stemRight; this.setCursorToIndex(Object.isUndefined(index) ? this.index : index); }, setCursorToIndex: function(index) { var range = this.getRange(); range.setStart(this.element, index); range.setEnd(this.element, index); this.setRange(range); }, findStem: function() { var left = [], right = [], chr, index, length = this.value.length; for (index = this.index - 1; index >= 0; index--) { chr = this.value.charAt(index); if (!Alice.Completion.PATTERN.test(chr)) break; left.unshift(chr); } for (index = this.index; index < length; index++) { chr = this.value.charAt(index); if (!Alice.Completion.PATTERN.test(chr)) break; right.push(chr); } this.stem = left.concat(right).join(""); this.stemLeft = this.value.substr(0, this.index - left.length); this.stemRight = this.value.substr(this.index + right.length); this.leftOffset = this.index - left.length; }, matchAgainst: function(candidates) { return candidates.grep(new RegExp("^" + RegExp.escape(this.stem), "i")).sortBy(function(candidate) { return candidate.toLowerCase(); }); } }); Alice.Completion.PATTERN = /[A-Za-z0-9\[\\\]^_{|}-]/; if (window == window.parent) { document.observe("dom:loaded", function () { var alice = new Alice.Application(); window.alice = alice; var options = { images: 'show', avatars: 'show', timeformat: '12' }; var js = /alice\.js\?(.*)?$/; $$('script[src]').findAll(function(s) { return s.src.match(js); }).each(function(s) { var params = s.src.match(js)[1]; params.split("&").each(function(o) { var kv = o.split("="); options[kv[0]] = kv[1]; }); }); alice.options = options; if (navigator.platform.match(/iphone/i)) { alice.options.images = "hide"; } var orig_console; if (window.console) { orig_console = window.console; window.console = {}; } else { window.console = {}; } window.console.log = function () { var win = alice.activeWindow(); for (var i=0; i < arguments.length; i++) { if (orig_console && orig_console.log) orig_console.log(arguments[i]); if (win && options.debug == "true") win.addMessage({ html: '
  • console
    '+arguments[i].toString()+'
  • ' }); } }; $$('ul.messages li.avatar:not(.consecutive) + li.consecutive').each(function (li) { li.previous().down('div.msg').setStyle({minHeight:'0px'}); }); $$('span.timestamp').each(function(elem) { if (elem.innerHTML) { elem.innerHTML = Alice.epochToLocal(elem.innerHTML.strip(), alice.options.timeformat); elem.style.opacity = 1; } }); $('helpclose').observe("click", function () { $('help').hide(); }); $$('#config_overlay option').each(function(opt){opt.selected = false}); $('tab_overflow_overlay').observe("change", function (e) { var win = alice.getWindow($('tab_overflow_overlay').value); if (win) win.focus(); }); $('config_overlay').observe("change", function (e) { switch ($('config_overlay').value) { case "Logs": alice.toggleLogs(e); break; case "Connections": alice.toggleConfig(e); break; case "Preferences": alice.togglePrefs(e); break; case "Logout": if (confirm("Logout?")) window.location = "/logout"; break; case "Help": alice.toggleHelp(); break; } $$('#config_overlay option').each(function(opt){opt.selected = false}); }); window.onkeydown = function (e) { var win = alice.activeWindow(); if (win && !$('config') && !Alice.isSpecialKey(e.which)) win.input.focus(); }; window.onresize = function () { if (alice.activeWindow()) { if (Prototype.Browser.Gecko) alice.activeWindow().resizeMessagearea(); alice.activeWindow().scrollToBottom(); } }; window.onfocus = function () { if (!alice.isMobile) window.document.body.removeClassName("blurred"); if (alice.activeWindow()) alice.activeWindow().input.focus(); alice.isFocused = true alice.clearMissed(); }; window.status = " "; window.onblur = function () { if (!alice.isMobile) window.document.body.addClassName("blurred"); alice.isFocused = false }; window.onhashchange = alice.focusHash.bind(alice); window.onorientationchange = function() { alice.activeWindow().scrollToBottom(true); }; alice.addFilters([ function(content) { var filtered = content; filtered = filtered.replace( /($1"); return filtered; }, function (content) { var filtered = content; if (alice.options.images == "show") { filtered = filtered.replace( /(]*>)([^<]*\.(:?jpe?g|gif|png|bmp|svg)(:?\?v=0)?)<"); } return filtered; } ]); }); } App-Alice-0.19/share/static/alice-default.css0000644000175000017500000004257111437015710017752 0ustar leedoleedodiv.window.active { display: block; } div.window { display: none; } input[type="hidden"] { display: none; } body { margin: 0; padding: 0; color: black; } ul#tabs { z-index: 20; } ul#tabs li:hover { cursor: default; } ul#controls { z-index: 30; background: #dddddd; } body.blurred ul#controls { background: #eeeeee; } ul#controls li { position: relative; } select.select_overlay { display: block; position: absolute; top: 0px; left: 0px; bottom: 0px; width: 22px; border: none; padding: 0; margin: 0; opacity: 0; font-size: 12px; } select#tab_overflow_overlay option.unread { font-weight: bold; } li#tab_overflow_button { background: url(image/sprites.png) 3px -174px no-repeat; } li#tab_overflow_button.unread { background: url(image/sprites.png) 3px -194px no-repeat; } li#config_button { background: url(image/sprites.png) 4px -216px no-repeat; } body { font-family: "Lucida Grande", Helvetica, sans-serif; text-rendering: optimizeLegibility; background: white; } a:link, a:visited, a:hover, a:active { color: #0033cc; } a:hover { text-shadow: rgba(0, 0, 0, 0.3) 0 1px 1px; } div.window { position: fixed; top: 0px; left: 0px; right: 0px; bottom: 23px; } div.window table { table-layout: fixed; width: 100%; height: 100%; padding: 0; } tr.input td { height: 20px; background: #efefef; border-bottom: 1px solid #999999; border-top: 1px solid #aaaaaa; } body.blurred tr.input td { border-bottom: 1px solid #aaaaaa; } tr.input form { display: block; margin: 0; padding: 0; height: auto; line-height: 0px; } tr.input div { padding: 0 3px; background: white; margin: 3px 0; position: relative; } tr.input div.editor, tr.input textarea { line-height: 1.2em; min-height: 1.2em; width: 100%; padding: 3px 0; border: none; margin: 0; outline: 0 none; font-size: 12px; overflow: hidden; resize: none; font-family: "Lucida Grande", Helvetica, sans-serif; } div.editor { white-space: pre-wrap; -khtml-nbsp-mode: space; } tr.input div.editor_toolbar { overflow: hidden; position: absolute; right: -170px; padding-right: 8px; top: -3px; width: 180px; height: 20px; -webkit-border-radius: 3px 0 0 3px; -moz-border-radius: 3px 0 0 3px; border-radius: 3px 0 0 3px; background: url(image/sprites.png) 0px -240px no-repeat black; z-index: 902; opacity: 0.2; -webkit-transition-property: right; -webkit-transition-duration: 0.3s; } tr.input div.editor_toolbar button { visibility: hidden; display: block; float: left; color: #eeeeee; line-height: 10px; font-size: 11px; margin: 0; padding: 4px 3px 5px 3px; text-align: center; background: none; cursor: pointer; border: none; } tr.input div.editor_toolbar button:hover { color: white; } tr.input div.editor_toolbar button.selected { color: yellow; } .bold { font-weight: bold; } .italic { font-style: italic; } .underline { text-decoration: underline; } tr.input div.editor_toolbar:hover { opacity: 0.4; cursor: pointer; } tr.input div.editor_toolbar.visible:hover { opacity: 1; cursor: inherit; } tr.input div.editor_toolbar.visible { background-color: rgba(0, 0, 0, 0.6); background-image: none; right: -4px; -webkit-transition-property: right; -webkit-transition-duration: 0.3s; opacity: 1; } tr.input div.editor_toolbar.visible button { visibility: visible; } div.editor div, div.editor p, div.editor h1, div.editor h2, div.editor h3, div.editor h4, div.editor h5, div.editor span { font-size: 1em !important; margin: 0 !important; padding: 0 !important; } div.editor img { display: none !important; } tr.input input.send { display: none; } div#tab_container { position: fixed; bottom: 0px; left: 0px; right: 0px; height: 23px; background: #dddddd; } body.blurred div#tab_container { background: #eeeeee; } ul#tabs { list-style: none; margin: 0; padding: 0; font-size: 11px; position: relative; float: left; margin-right: 62px; padding-left: 3px; } body.blurred ul#tabs li { background: #eeeeee; color: #777777; } ul#tabs li { float: left; margin: 0; margin-left: -1px; color: #333333; background: #dddddd; } body.blurred ul#tabs li:first-child div.hit_area { border-left: 1px solid #eeeeee; } ul#tabs li:first-child div.hit_area { border-left: 1px solid #dddddd; } ul#tabs li div.hit_area { border: 1px solid #999999; border-bottom: none; border-top: none; float: left; height: 14px; padding: 2px 10px 8px 4px; } body.blurred ul#tabs li div.hit_area { border: 1px solid #aaaaaa; border-bottom: none; border-top: none; } body.blurred ul#tabs li.active div.hit_area { border: 1px solid #aaaaaa; border-top: none; } ul#tabs li.active div.hit_area { border: 1px solid #999999; border-top: none; background: #efefef; height: 11px; padding-top: 3px; margin-top: -1px; } ul#tabs li.active { height: 23px; z-index: 21; } ul#tabs li.active:hover div.hit_area { background: #efefef; } ul#tabs li:hover div.hit_area { background: #cccccc; } ul#controls { position: fixed; bottom: 0px; right: 0px; height: 23px; margin: 0; padding: 0 18px 0 0; list-style: none; } ul#controls li { float: right; margin: 0; padding: 0; height: 16px; width: 16px; padding: 4px 3px 3px 3px; cursor: pointer; } ul#controls li:hover { background-color: #cccccc; } tr.topic td { width: 100%; background: #efefef; font-size: 11px; text-align: center; border-top: 1px solid white; border-bottom: 1px solid #aaaaaa; padding: 1px 0; height: 1.2em; } tr.topic td div.topic { height: 1.2em; overflow: hidden; } tr.topic a { text-decoration: none; } div.message_wrap { height: 100%; overflow-x: hidden; overflow-y: auto; background: white; } ul.messages { list-style: none; margin: 0; padding: 0; overflow: hidden; margin: 3px 0; } ul.messages > li { font-size: 13px; display: block; position: relative; background-color: #eaeaea; } ul.messages > li:not(.consecutive) { clear: both; } ul.messages li.highlight { border-right: 5px solid #eb2222; } .info ul.messages li.highlight, ul.messages li.self { background: #fffbd0; border-right: none; } .info ul.messages li.highlight div.msg, ul.messages li.self div.msg { background: #fffbd0; } ul.messages li div.msg { background: white; } ul.messages li.consecutive div.left { display: none; } div.left { float: left; width: 95px; font-weight: bold; text-align: right; padding: 4px 0; padding-right: 4px; padding-bottom: 0; text-overflow: ellipsis; position: relative; } li.event a, li.event { color: black; } li.event div.left { padding-top: 5px; } li.event div.msg { border-top: none; } ul.messages li.avatar div.left { padding-bottom: 3px; padding-top: 3px; } li.event div.left { padding: 5px 0; width: 95px; } li.event div.left span.timestamp { opacity: 0; -webkit-transition: opacity 0.1s ease-in-out; } ul.messages li.avatar:not(.consecutive) { z-index: 900; } div.left img { border: 1px solid white; display: block; max-width: 32px; max-height: 32px; float: right; } div.left span.nickhint { position: absolute; z-index: 901; right: 5px; top: 4px; padding: 0 2px; min-width: 28px; background: rgba(0, 0, 0, 0.5); color: #fff; text-shadow: black 0 1px 1px; opacity: 0; -webkit-transition: opacity 0.5s ease-in-out; } div.timehint { position: absolute; font-size: 10px; right: 0px; top: 1px; padding: 6px 4px 2px 4px; color: #555555; opacity: 0; text-shadow: white 0 0 3px; background: white; } .info ul.messages li.highlight div.timehint, li.self div.timehint { background: #fffbd0; } div.msg { margin-left: 100px; padding: 3px 5px; word-wrap: break-word; -khtml-line-break: after-white-space; -khtml-nbsp-mode: space; border-top: 1px solid #eeeeee; border-left: 1px solid #c1c1c1; } li.event div.msg { padding: 5px; } li.monospace div.msg { padding: 6px 5px; } li.event + li.message div.msg { border-top: 1px solid transparent; } ul.messages li:first-child div.msg { border-top: 1px solid transparent; } /* so the last avatar doesn't hang off the edge */ ul.messages li.avatar:not(.consecutive) div.msg { min-height: 35px; } ul.messages li.monospace { min-height: 0px; } ul.messages li.message:last-child { border-bottom: 1px solid #eeeeee; } ul.messages li.consecutive div.msg { border-top: none; } ul.messages li + li.consecutive div.msg { padding-top: 0px; } div.msg a { word-break: break-all; } div.msg img { display: block; border: none; } ul.messages li.monospace + li.monospace.consecutive { margin-top: -5px; } ul.messages li.monospace div.msg { font-family: Monaco, monospace; font-size: 10px; line-height: 12px; word-wrap: normal; white-space: pre-wrap; } li.monospace div.msg span[style*="bold"] { letter-spacing: -1px; } ul.messages li.event { font-size: 11px; background: #d9e7fb; border-bottom: 1px solid white; } ul.messages li.event.notice { background: #eb2222; color: #fff; } ul.messages li.event.notice a { color: #fffc00; text-decoration: none; } ul.messages li.event.notice a:hover { text-shadow: none; } ul.messages li.event.happynotice { background: #3fc842; color: #fff; } ul.messages li.event div.msg { background: none; border-left: none; } ul.messages li.event div.left { font-weight: normal; } ul.messages li.announce div.msg { color: #777; white-space: pre-wrap; } ul.messages li.announce div.msg ul.avatar_grid { margin: 0; padding: 0; list-style: none; width: 100%; overflow: hidden; word-wrap: normal; white-space: normal; } ul.messages li.announce div.msg ul.avatar_grid li { width: 50px; height: 50px; float: left; margin: 0; padding: 0; text-align: center; clear: none; display: block; overflow: hidden; font-size: 10px; text-overflow: ellipsis; } ul.messages li.announce div.msg ul.avatar_grid li div.gridimg { width: 32px; height: 32px; margin: 0 auto; } ul.messages li.announce div.msg ul.avatar_grid li img { max-width: 32px; max-height: 32px; margin: 0 auto; padding: 0; position: relative; display: block; } div.msg img.audio { cursor: pointer; display: inline; padding: 0 4px; width: 16px; height: 16px; margin-bottom: -3px; } a.nick { color: black; text-decoration: none; } div.tab_button { width: 16px; height: 16px; float: left; margin-right: 6px; } li.channel_tab div.tab_button { background: url(image/sprites.png) 0px -80px no-repeat; } li.privmsg_tab div.tab_button { background: url(image/sprites.png) 0px -120px no-repeat; } li.info_tab div.tab_button { background: url(image/sprites.png) 0px 0px no-repeat; } ul#tabs li.active div.tab_button:hover { background: url(image/sprites.png) 1px -260px no-repeat; } ul#tabs li.unread.channel_tab div.tab_button { background: url(image/sprites.png) 0px -20px no-repeat; } ul#tabs li.unread.privmsg_tab div.tab_button { background: url(image/sprites.png) 0 -100px no-repeat; } ul#tabs li.highlight.channel_tab div.tab_button { background: url(image/sprites.png) 0 -40px no-repeat; } ul#tabs li.disabled.channel_tab div.tab_button { background: url(image/sprites.png) 0 -60px no-repeat; } div#logsearch { background: #eee; border-bottom: 1px solid #999; position: fixed; top: 0px; left: 0px; right: 0px; height: 28px; padding: 5px 0 0 0; } div#logsearch label { font-size: 0.7em; padding-right: 2px; padding-left: 10px; color: #666; } div#logsearch form { display: block; margin: 0; } ul#logresults { list-style: none; margin: 0; padding: 0; position: absolute; top: 34px; bottom: 0px; left: 0px; right: 0px; overflow: auto; } ul#logresults li { border-bottom: 1px solid #eee; padding: 0; font-size: 0.8em; cursor: pointer; } ul#logresults div.focus:hover { background: #eee; } ul#logresults li div.focus { background: #fff; padding: 5px 10px; } ul#logresults li.context div.focus:hover { background: #fff; } ul#logresults li.context ul { margin: 0; padding: 5px 10px; list-style: none; } ul#logresults li.context ul li { border: none; margin: 0; padding: 2px 0; } ul#logresults li.context { border-top: 1px solid #999; border-bottom: 1px solid #999; background: url(image/shadow-bottom.png) bottom left repeat-x, url(image/shadow-top.png) top left repeat-x #eeeeee; } ul#logresults span.metadata { font-size: 0.7em; color: #666; } div#help { position: absolute; z-index: 999; background: rgba(0, 0, 0, 0.8); color: #fff; left: 20px; right: 20px; top: 25px; bottom: 60px; padding: 10px 20px; -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; text-shadow: 0 0 3px rgba(0, 0, 0, 0.9); } div#help div#helpclose { position: absolute; top: 17px; right: 20px; color: rgba(255, 255, 255, 0.75); font-size: 9px; letter-spacing: 1px; text-shadow: none; text-transform: uppercase; } div#help div#helpclose:hover { cursor: pointer; color: #fff; } div#help div#topics { position: absolute; top: 34px; bottom: 15px; left: 20px; right: 20px; overflow-y: auto; } div#help h1 { margin: 0; font-size: 20px; border-bottom: 1px solid #999; font-weight: normal; } div#help dl { width: 48%; font-size: 12px; margin-top: 0; padding-top: 10px; float: left; display: block; border-right: 1px solid #666; padding-right: 2%; } div#help dl#shortcuts { border-right: none; float: right; padding-right: 0; padding-left: 2%; } div#help h2 { color: yellow; margin-top: 0; font-weight: normal; } div#help dt { font-weight: bold; } div#help dd { font-weight: normal; margin: 0; margin-bottom: 10px; } div.color_picker { position: absolute; right: 25px; bottom: 27px; width: 80px; border-bottom: none; margin: 0; padding: 0; z-index: 903; } div.color_picker span { cursor: pointer; } div.color_picker div.colors { clear: both; } div.color_picker div.colors span { float: left; display: block; width: 20px; height: 20px; margin: 0; -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6); -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.6); box-shadow: 0 0 3px rgba(0, 0, 0, 0.6); } div.color_picker div.toggle { height: 18px; } div.color_picker div.toggle span.blank { float: right; color: red; font-weight: bold; text-align: center; width: 20px; line-height: 9px; } div.color_picker div.toggle span#fg, div.color_picker div.toggle span#bg { float: left; font-size: 9px; background: none; -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; color: black; text-align: center; padding: 2px 5px; } div.color_picker div.toggle span#fg.active, div.color_picker div.toggle span#bg.active { background: rgba(0, 0, 0, 0.6); color: white; } @media screen and (max-device-width: 480px) { div.left { width: 40px !important; } div.msg { margin-left: 52px !important; } div.topic { height: 1.2em; overflow: hidden; } tr.input div { margin-right: 55px; background: none; } tr.input input.send { display: block; position: absolute; bottom: 2px; right: 0px; z-index: 999; } div.msg.monospace, div.msg.announce { line-height: 11px; } } div.config { position: absolute; bottom: 55px; right: 20px; background: #efefef; padding: 10px 10px; z-index: 999; border: 10px solid rgba(0, 0, 0, 0.5); -webkit-border-radius: 10px; -moz-border-radius: 5px; border-radius: 5px; } div.config div.buttons { clear: both; } div.config div.buttons button { float: right; } div.config div.setting { margin-top: 0.5em; margin-left: 160px; display: none; } div.config div.setting.active { display: block; } div.config div.field { float: left; margin-bottom: 10px; margin-right: 8px; } div.config div.field.clear { clear: both; } div.config label, div.config input { display: block; font-size: 0.8em; } div.config span { font-size: 0.8em; } div.config select { width: 135px; font-size: 0.8em; outline: 0 none; } div.config[multiple] { height: 100px; } div.config div#server_controls { position: absolute; bottom: 18px; left: 6px; } div.config div.controls { clear: both; font-size: 0.7em; } div.config div.controls a { text-decoration: none; margin: 0 5px 0 0; } div.config div.controls a:hover { text-shadow: none; text-decoration: underline; } div.config label { color: #666; } div.config input[type="checkbox"] { float: left; margin-right: 4px; } div.config ul#connections { list-style: none; position: absolute; top: 5px; bottom: 32px; left: 5px; margin: 0; padding: 0px; width: 150px; background: #fff; border-right: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.config ul#connections li.header { background: #efefef; text-transform: uppercase; color: #777; letter-spacing: 1px; font-size: 0.7em; cursor: default; } div.config ul#connections li.header:hover { background: #efefef; } div.config ul#connections li { padding: 5px 8px; font-size: 0.9em; cursor: pointer; border: 1px solid #fff; border-bottom: none; } div.config ul#connections li:hover { background: #ffffcc; } div.config ul#connections li.disconnected.active, div.config ul#connections li.connected.active { background: #3875d7; color: #fff; } div.config ul#connections li.disconnected { color: #999; } div.config ul#connections li.connected { color: #222; } body.config { background: #efefef; } App-Alice-0.19/share/commands.pl0000644000175000017500000002102211406714342015377 0ustar leedoleedomy $SRVOPT = qr/(?:\-(\S+)\s+)?/; my $commands = [ { name => 'say', re => qr{^([^/].*)}s, code => sub { my ($self, $window, $msg) = @_; if ($window->type eq "info") { $self->reply($window, "You can't talk here!"); return; } elsif (!$window->irc->is_connected) { $self->reply($window, "You are not connected to ".$window->irc->alias."."); return; } $self->app->store(nick => $window->nick, channel => $window->title, body => $msg); $self->show($window, $msg); $window->irc->send_srv(PRIVMSG => $window->title, $msg); }, }, { name => 'msg', re => qr{^/(?:msg|query)\s+$SRVOPT(\S+)(.*)}, eg => "/MSG [-] ", desc => "Sends a message to a nick.", code => sub { my $self = shift; my $window = shift; my ($msg, $nick, $network); if (@_ == 3) { ($msg, $nick, $network) = @_; } elsif (@_ == 2) { ($msg, $nick) = @_; } $msg =~ s/^\s+//; my $irc = $window->irc; if ($network and $self->app->has_irc($network)) { $irc = $self->app->get_irc($network); } return unless $irc; my $new_window = $self->app->find_or_create_window($nick, $irc); my @msgs = ($new_window->join_action); if ($msg) { push @msgs, $new_window->format_message($new_window->nick, $msg); $irc->send_srv(PRIVMSG => $nick, $msg) if $msg; } $self->broadcast(@msgs); }, }, { name => 'nick', re => qr{^/nick\s+$SRVOPT(\S+)}, eg => "/NICK [-] ", desc => "Changes your nick.", code => sub { my ($self, $window, $nick, $network) = @_; my $irc; if ($network and $self->app->has_irc($network)) { $irc = $self->app->get_irc($network); } else { $irc = $window->irc; } $irc->log(info => "now known as $nick"); $irc->send_srv(NICK => $nick); }, }, { name => 'names', re => qr{^/n(?:ames)?(?:\s(-a(?:vatars)?))?}, in_channel => 1, eg => "/NAMES [-avatars]", desc => "Lists nicks in current channel. Pass the -avatars option to display avatars with the nicks.", code => sub { my ($self, $window, $avatars) = @_; $self->reply($window, $window->nick_table($avatars)); }, }, { name => 'join', re => qr{^/j(?:oin)?\s+$SRVOPT(.+)}, eg => "/JOIN [-] []", desc => "Joins the specified channel.", code => sub { my ($self, $window, $channel, $network) = @_; my $irc; if ($network and $self->app->has_irc($network)) { $irc = $self->app->get_irc($network); } else { $irc = $window->irc; } my @params = split /\s+/, $channel; if ($irc and $irc->cl->is_channel_name($params[0])) { $irc->log(info => "joining $params[0]"); $irc->send_srv(JOIN => @params); } }, }, { name => 'create', re => qr{^/create\s+(\S+)}, code => sub { my ($self, $window, $name) = @_; my $new_window = $self->app->find_or_create_window($name, $window->irc); $self->broadcast($new_window->join_action); }, }, { name => 'part', re => qr{^/(?:close|wc|part)}, eg => "/PART", desc => "Leaves and closes the focused window.", code => sub { my ($self, $window) = @_; $window->is_channel ? $window->irc->send_srv(PART => $window->title) : $self->app->close_window($window); }, }, { name => 'clear', re => qr{^/clear}, eg => "/CLEAR", desc => "Clears lines from current window.", code => sub { my ($self, $window) = @_; $window->buffer->clear; $self->broadcast($window->clear_action); }, }, { name => 'topic', re => qr{^/t(?:opic)?(?:\s+(.+))?}, in_channel => 1, eg => "/TOPIC []", desc => "Shows and/or changes the topic of the current channel.", code => sub { my ($self, $window, $new_topic) = @_; if ($new_topic) { $window->topic({string => $new_topic, nick => $window->nick, time => time}); $window->irc->send_srv(TOPIC => $window->title, $new_topic); } else { my $topic = $window->topic; $self->broadcast($window->format_event("topic", $topic->{author}, $topic->{string})); } }, }, { name => 'whois', re => qr{^/whois(?:\s+(-f(?:orce)?))?\s+(\S+)}, eg => "/WHOIS [-force] ", desc => "Shows info about the specified nick. Use -force option to refresh", code => sub { my ($self, $window, $nick, $force) = @_; if (!$force and $window->irc->includes_nick($nick)) { $self->reply($window, $window->irc->whois_table($nick)); } else { $window->irc->add_whois_cb($nick => sub { $self->reply($window, $window->irc->whois_table($nick)); }); } }, }, { name => 'me', re => qr{^/me\s+(.+)}, eg => "/ME ", desc => "Sends a CTCP ACTION to the current window.", code => sub { my ($self, $window, $action) = @_; $self->show($window, "• $action"); $window->irc->send_srv(PRIVMSG => $window->title, chr(01) . "ACTION $action" . chr(01)); }, }, { name => 'quote', re => qr{^/(?:quote|raw)\s+(.+)}, eg => "/QUOTE ", desc => "Sends the server raw data without parsing.", code => sub { my ($self, $window, $command) = @_; $window->irc->send_raw($command); }, }, { name => 'disconnect', re => qr{^/disconnect\s+(\S+)}, eg => "/DISCONNECT ", desc => "Disconnects from the specified server.", code => sub { my ($self, $window, $network) = @_; my $irc = $self->app->get_irc($network); if ($irc and $irc->is_connected) { $irc->disconnect; } elsif ($irc->reconnect_timer) { $irc->cancel_reconnect; $irc->log(info => "canceled reconnect"); } else { $self->reply($window, "already disconnected"); } }, }, { name => 'connect', re => qr{^/connect\s+(\S+)}, eg => "/CONNECT ", desc => "Connects to the specified server.", code => sub { my ($self, $window, $network) = @_; my $irc = $self->app->get_irc($network); if ($irc and !$irc->is_connected) { $irc->connect; } }, }, { name => 'ignore', re => qr{^/ignore\s+(\S+)}, eg => "/IGNORE ", desc => "Adds nick to ignore list.", code => sub { my ($self, $window, $nick) = @_; $self->app->add_ignore($nick); $self->reply($window, "Ignoring $nick"); }, }, { name => 'unignore', re => qr{^/unignore\s+(\S+)}, eg => "/UNIGNORE ", desc => "Removes nick from ignore list.", code => sub { my ($self, $window, $nick) = @_; $self->app->remove_ignore($nick); $self->reply($window, "No longer ignoring $nick"); }, }, { name => 'ignores', re => qr{^/ignores?}, eg => "/IGNORES", desc => "Lists ignored nicks.", code => sub { my ($self, $window) = @_; my $msg = join ", ", $self->app->ignores; $msg = "none" unless $msg; $self->reply($window, "Ignoring:\n$msg"); }, }, { name => 'window', re => qr{^/w(?:indow)?\s*(\d+|next|prev(?:ious)?)}, eg => "/WINDOW ", desc => "Focuses the provided window number", code => sub { my ($self, $window, $window_number) = @_; $self->broadcast({ type => "action", event => "focus", window_number => $window_number, }); }, }, { name => 'reload commands', re => qr{^/reload commands$}, code => sub { my ($self, $window) = @_; $self->reload_handlers; } }, { name => 'help', re => qr{^/help(?:\s+(\S+))?}, code => sub { my ($self, $window, $command) = @_; if (!$command) { $self->reply($window, '/HELP for help with a specific command'); $self->reply($window, "Available commands: " . join " ", map { uc $_->{name}; } grep {$_->{eg}} @{$self->handlers}); return; } for (@{$self->handlers}) { if ($_->{name} eq lc $command) { $self->reply($window, "$_->{eg}\n$_->{desc}"); return; } } $self->reply($window, "No help for ".uc $command); }, }, { name => 'notfound', re => qr{^/(.+)(?:\s.*)?}, code => sub { my ($self, $window, $command) = @_; $self->reply($window, "Invalid command $command"); }, }, ]; $commands; App-Alice-0.19/share/log.db0000644000175000017500000001400011373075677014345 0ustar leedoleedoSQLite format 3@  HP++Ytablesqlite_sequencesqlite_sequenceCREATE TABLE sqlite_sequence(name,seq)5=tablemessagesmessagesCREATE TABLE messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, time INT, user VARCHAR(16), nick VARCHAR(16), channel VARCHAR(16), body TEXT )  App-Alice-0.19/lib/0000755000175000017500000000000011437215373012714 5ustar leedoleedoApp-Alice-0.19/lib/App/0000755000175000017500000000000011437215373013434 5ustar leedoleedoApp-Alice-0.19/lib/App/Alice/0000755000175000017500000000000011437215373014451 5ustar leedoleedoApp-Alice-0.19/lib/App/Alice/Stream.pm0000644000175000017500000000471411435242024016237 0ustar leedoleedopackage App::Alice::Stream; use JSON; use Time::HiRes qw/time/; use Try::Tiny; use Any::Moose; use strict; use warnings; has queue => ( is => 'rw', isa => 'ArrayRef[HashRef]', default => sub { [] }, ); sub clear_queue {$_[0]->queue([])} sub enqueue {push @{shift->queue}, @_} sub queue_empty {return @{$_[0]->queue} == 0} has [qw/offset last_send start_time/]=> ( is => 'rw', isa => 'Num', default => 0, ); has [qw/delayed started closed/] => ( is => 'rw', isa => 'Bool', default => 0, ); has 'seperator' => ( is => 'ro', isa => 'Str', default => 'xalicex', ); has 'timer' => ( is => 'rw', ); has 'writer' => ( is => 'rw', required => 1, ); has min_bytes => ( is => 'ro', default => 1024, ); sub BUILD { my $self = shift; my $local_time = time; my $remote_time = $self->start_time || $local_time; $self->offset($local_time - $remote_time); my $writer = $self->writer->( [200, ['Content-Type' => 'multipart/mixed; boundary='.$self->seperator.'; charset=utf-8']] ); $self->writer($writer); $self->_send; } sub _send { my $self = shift; try { $self->send } catch { $self->close }; } sub send { my ($self, @messages) = @_; die "Sending on a closed stream" if $self->closed; $self->enqueue(@messages) if @messages; return if $self->delayed or $self->queue_empty; if (my $delay = $self->flooded) { $self->delay($delay); return; } $self->writer->write( $self->to_string ); $self->flush; } sub close { my $self = shift; try {$self->writer->write($self->to_string)}; $self->flush; $self->writer->close; $self->timer(undef); $self->closed(1); } sub flooded { my $self = shift; my $diff = time - $self->last_send; if ($diff < 0.2) { return 0.2 - $diff; } return 0; } sub delay { my ($self, $delay) = @_; $self->delayed(1); $self->timer(AnyEvent->timer( after => $delay, cb => sub { $self->delayed(0); $self->timer(undef); $self->_send; }, )); } sub flush { my $self = shift; $self->clear_queue; $self->last_send(time); } sub to_string { my $self = shift; my $output; if (! $self->started) { $output .= "--".$self->seperator."\n"; $self->started(1); } $output .= to_json({ queue => $self->queue, time => time - $self->offset, }, {utf8 => 1}); $output .= "\n--" . $self->seperator . "\n"; $output .= " " x ($self->min_bytes - length $output); return $output } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/History.pm0000644000175000017500000000301011401234663016434 0ustar leedoleedopackage App::Alice::History; use Any::Moose; use AnyEvent::DBI; use AnyEvent::IRC::Util qw/filter_colors/; use SQL::Abstract; has dbi => ( is => 'ro', isa => 'AnyEvent::DBI', lazy => 1, default => sub { my $self = shift; AnyEvent::DBI->new("DBI:SQLite:dbname=".$self->dbfile,"",""); } ); has sql => ( is => 'ro', isa => 'SQL::Abstract', default => sub {SQL::Abstract->new(cmp => "like")}, ); has dbfile => ( is => 'ro', isa => 'Str', #required => 1, ); sub store { my ($self, %fields) = @_; my ($stmt, @bind) = $self->sql->insert("messages", \%fields); $self->dbi->exec($stmt, @bind, sub {}); } sub range { my $cb = pop; my ($self, $user, $channel, $id, $limit) = @_; $limit ||=5; $self->dbi->exec( "SELECT * FROM messages WHERE id < ? AND channel=? AND user=? ORDER BY id DESC LIMIT ?", $id, $channel, $user, $limit, sub { my $before = [ reverse @{$_[1]} ]; $self->dbi->exec( "SELECT * FROM messages WHERE id > ? AND channel=? AND user=? ORDER BY id ASC LIMIT ?", $id, $channel, $user, $limit, sub { my $after = $_[1]; $cb->($before, $after); } ); } ); } sub search { my $cb = pop; my ($self, %query) = @_; %query = map {$_ => "%$query{$_}%"} grep {$query{$_}} qw/body channel nick user/; my ($stmt, @bind) = $self->sql->select("messages", '*', \%query, {-desc => 'id'}); $self->dbi->exec($stmt, @bind, sub { my ($db, $rows, $rv) = @_; $cb->($rows); }); } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/MessageStore/0000755000175000017500000000000011437215373017052 5ustar leedoleedoApp-Alice-0.19/lib/App/Alice/MessageStore/Redis.pm0000644000175000017500000000227711435242024020455 0ustar leedoleedopackage App::Alice::MessageStore::Redis; use Any::Moose; use AnyEvent::Redis; use JSON; my $redis = AnyEvent::Redis->new; has id => ( is => 'ro', required => 1 ); has buffersize => ( is => 'ro', default => 100 ); has lrange_size => ( is => 'ro', default => 15 ); sub add { my ($self, $message) = @_; return unless $message; $redis->rpush($self->id, encode_json $message); $redis->llen($self->id, sub { $redis->lpop($self->id) if $_[0] > $self->buffersize; }); } sub clear { my $self = shift; $redis->del($self->id); } sub with_messages { my ($self, $cb, $start, $complete_cb) = @_; $start ||= 0; my $end = $start + $self->lrange_size - 1; $end = $self->buffersize if $end > $self->buffersize; $redis->lrange( $self->id, $start, $end, sub { my $msgs = ref $_[0] eq 'ARRAY' ? $_[0] : []; $cb->( grep {$_} map {my $msg = eval {decode_json $_ }; $@ ? undef : $msg} @$msgs ); if ($end == $self->buffersize or @$msgs != $self->lrange_size) { $complete_cb->() if $complete_cb; } else { $self->with_messages($cb, $end + 1, $complete_cb); } } ); } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/MessageStore/Memory.pm0000644000175000017500000000112011366325415020652 0ustar leedoleedopackage App::Alice::MessageStore::Memory; use Any::Moose; has msgbuffer => ( is => 'rw', isa => 'ArrayRef', default => sub {[]} ); has buffersize => ( is => 'ro', default => 100 ); sub add { my ($self, $message) = @_; push @{$self->msgbuffer}, $message; if (@{$self->msgbuffer} > $self->buffersize) { shift @{$self->msgbuffer}; } } sub clear { my $self = shift; $self->msgbuffer([]); } sub with_messages { my ($self, $cb, $start, $complete_cb) = @_; $cb->(@{ $self->msgbuffer }); $complete_cb->() if $complete_cb; } __PACKAGE__->meta->make_immutable; 1;App-Alice-0.19/lib/App/Alice/HTTPD.pm0000644000175000017500000002527211437213356015701 0ustar leedoleedopackage App::Alice::HTTPD; use AnyEvent; use AnyEvent::HTTP; use Twiggy::Server; use Plack::Request; use Plack::Builder; use Plack::Middleware::Static; use Plack::Session::Store::File; use IRC::Formatting::HTML qw/html_to_irc/; use App::Alice::Stream; use App::Alice::Commands; use JSON; use Encode; use utf8; use Any::Moose; use Try::Tiny; has 'app' => ( is => 'ro', isa => 'App::Alice', required => 1, ); has 'httpd' => (is => 'rw'); has 'ping_timer' => (is => 'rw'); has 'config' => ( is => 'ro', isa => 'App::Alice::Config', lazy => 1, default => sub {shift->app->config}, ); my $url_handlers = [ [ qr{^/$} => \&send_index ], [ qr{^/say/?$} => \&handle_message ], [ qr{^/stream/?$} => \&setup_stream ], [ qr{^/config/?$} => \&send_config ], [ qr{^/prefs/?$} => \&send_prefs ], [ qr{^/serverconfig/?$} => \&server_config ], [ qr{^/save/?$} => \&save_config ], [ qr{^/tabs/?$} => \&tab_order ], [ qr{^/login/?$} => \&login ], [ qr{^/logout/?$} => \&logout ], [ qr{^/logs/?$} => \&send_logs ], [ qr{^/search/?$} => \&send_search ], [ qr{^/range/?$} => \&send_range ], [ qr{^/view/?$} => \&send_index ], [ qr{^/get} => \&image_proxy ], ]; sub url_handlers { return $url_handlers } has 'streams' => ( is => 'rw', auto_deref => 1, isa => 'ArrayRef[App::Alice::Stream]', default => sub {[]}, ); sub add_stream {push @{shift->streams}, @_} sub no_streams {@{$_[0]->streams} == 0} sub stream_count {scalar @{$_[0]->streams}} sub BUILD { my $self = shift; my $httpd = Twiggy::Server->new( host => $self->config->http_address, port => $self->config->http_port, ); $httpd->register_service( builder { if ($self->app->auth_enabled) { mkdir $self->config->path."/sessions" unless -d $self->config->path."/sessions"; enable "Session", store => Plack::Session::Store::File->new(dir => $self->config->path), expires => "24h"; } enable "Static", path => qr{^/static/}, root => $self->config->assetdir; sub {$self->dispatch(shift)} } ); $self->httpd($httpd); $self->ping; } sub dispatch { my ($self, $env) = @_; my $req = Plack::Request->new($env); if ($self->app->auth_enabled) { unless ($req->path eq "/login" or $self->is_logged_in($req)) { my $res = $req->new_response; $res->redirect("/login"); return $res->finalize; } } for my $handler (@{$self->url_handlers}) { my $re = $handler->[0]; if ($req->path_info =~ /$re/) { return $handler->[1]->($self, $req); } } return $self->not_found($req); } sub is_logged_in { my ($self, $req) = @_; my $session = $req->env->{"psgix.session"}; return $session->{is_logged_in}; } sub login { my ($self, $req) = @_; my $res = $req->new_response; if (!$self->app->auth_enabled or $self->is_logged_in($req)) { $res->redirect("/"); return $res->finalize; } elsif (my $user = $req->param("username") and my $pass = $req->param("password")) { if ($self->app->authenticate($user, $pass)) { $req->env->{"psgix.session"}->{is_logged_in} = 1; $res->redirect("/"); return $res->finalize; } $res->body($self->app->render("login", "bad username or password")); } else { $res->body($self->app->render("login")); } $res->status(200); return $res->finalize; } sub logout { my ($self, $req) = @_; my $res = $req->new_response; if (!$self->app->auth_enabled) { $res->redirect("/"); } else { $req->env->{"psgix.session"}{is_logged_in} = 0; $req->env->{"psgix.session.options"}{expire} = 1; $res->redirect("/login"); } return $res->finalize; } sub ping { my $self = shift; $self->ping_timer(AnyEvent->timer( after => 5, interval => 10, cb => sub { $self->broadcast({ type => "action", event => "ping", }); } )); } sub shutdown { my $self = shift; $_->close for $self->streams; $self->streams([]); $self->ping_timer(undef); $self->httpd(undef); } sub image_proxy { my ($self, $req) = @_; my $url = $req->request_uri; $url =~ s/^\/get\///; return sub { my $respond = shift; http_get $url, sub { my ($data, $headers) = @_; my $res = $req->new_response($headers->{Status}); $res->headers($headers); $res->body($data); $respond->($res->finalize); }; } } sub broadcast { my ($self, @data) = @_; return if $self->no_streams or !@data; my $purge = 0; for my $stream ($self->streams) { try { $stream->send(@data); } catch { $stream->close; $purge = 1; }; } $self->purge_disconnects if $purge; }; sub setup_stream { my ($self, $req) = @_; $self->app->log(info => "opening new stream"); my $min = $req->param('msgid') || 0; return sub { my $respond = shift; my $stream = App::Alice::Stream->new( queue => [ map({$_->join_action} $self->app->windows) ], writer => $respond, start_time => $req->param('t'), # android requires 4K updates to trigger loading event min_bytes => $req->user_agent =~ /android/i ? 4096 : 0, ); $self->add_stream($stream); $self->app->with_messages(sub { return unless @_; $stream->enqueue( map {$_->{buffered} = 1; $_} grep {$_->{msgid} > $min} @_ ); $stream->send; }); } } sub purge_disconnects { my ($self) = @_; $self->app->log(debug => "removing broken streams"); $self->streams([grep {!$_->closed} $self->streams]); } sub handle_message { my ($self, $req) = @_; my $msg = $req->param('msg'); my $is_html = $req->param('html'); utf8::decode($msg) unless utf8::is_utf8($msg); $msg = html_to_irc($msg) if $is_html; my $source = $req->param('source'); my $window = $self->app->get_window($source); if ($window) { for (split /\n/, $msg) { try { $self->app->handle_command($_, $window) if length $_ } catch { $self->app->log(info => $_); } } } my $res = $req->new_response(200); $res->content_type('text/plain'); $res->content_length(2); $res->body('ok'); return $res->finalize; } sub send_index { my ($self, $req) = @_; return sub { my $respond = shift; my $writer = $respond->([200, ["Content-type" => "text/html; charset=utf-8"]]); my @windows = $self->app->sorted_windows; @windows > 1 ? $windows[1]->{active} = 1 : $windows[0]->{active} = 1; $writer->write(encode_utf8 $self->app->render('index_head', @windows)); $self->send_windows($writer, sub { $writer->write(encode_utf8 $self->app->render('index_footer', @windows)); $writer->close; delete $_->{active} for @windows; }, @windows); } } sub send_windows { my ($self, $writer, $cb, @windows) = @_; if (!@windows) { $cb->(); } else { my $window = pop @windows; $writer->write(encode_utf8 $self->app->render('window_head', $window)); $window->buffer->with_messages(sub { my @messages = @_; $writer->write(encode_utf8 $_->{html}) for @messages; }, 0, sub { $writer->write(encode_utf8 $self->app->render('window_footer', $window)); $self->send_windows($writer, $cb, @windows); }); } } sub send_logs { my ($self, $req) = @_; my $output = $self->app->render('logs'); my $res = $req->new_response(200); $res->body(encode_utf8 $output); return $res->finalize; } sub send_search { my ($self, $req) = @_; return sub { my $respond = shift; $self->app->history->search( user => $self->app->user, %{$req->parameters}, sub { my $rows = shift; my $content = $self->app->render('results', $rows); my $res = $req->new_response(200); $res->body(encode_utf8 $content); $respond->($res->finalize); }); } } sub send_range { my ($self, $req) = @_; return sub { my $respond = shift; $self->app->history->range( $self->app->user, $req->param('channel'), $req->param('id'), sub { my ($before, $after) = @_; $before = $self->app->render('range', $before, 'before'); $after = $self->app->render('range', $after, 'after'); my $res = $req->new_response(200); $res->body(to_json [$before, $after]); $respond->($res->finalize); } ); } } sub send_config { my ($self, $req) = @_; $self->app->log(info => "serving config"); my $output = $self->app->render('servers'); my $res = $req->new_response(200); $res->body($output); return $res->finalize; } sub send_prefs { my ($self, $req) = @_; $self->app->log(info => "serving prefs"); my $output = $self->app->render('prefs'); my $res = $req->new_response(200); $res->body($output); return $res->finalize; } sub server_config { my ($self, $req) = @_; $self->app->log(info => "serving blank server config"); my $name = $req->param('name'); $name =~ s/\s+//g; my $config = $self->app->render('new_server', $name); my $listitem = $self->app->render('server_listitem', $name); my $res = $req->new_response(200); $res->body(to_json({config => $config, listitem => $listitem})); $res->header("Cache-control" => "no-cache"); return $res->finalize; } sub save_config { my ($self, $req) = @_; $self->app->log(info => "saving config"); my $new_config = {}; if ($req->parameters->{has_servers}) { $new_config->{servers} = {}; } for my $name (keys %{$req->parameters}) { next unless $req->parameters->{$name}; next if $name eq "has_servers"; if ($name =~ /^(.+?)_(.+)/ and exists $new_config->{servers}) { if ($2 eq "channels" or $2 eq "on_connect") { $new_config->{servers}{$1}{$2} = [$req->parameters->get_all($name)]; } else { $new_config->{servers}{$1}{$2} = $req->param($name); } } elsif ($name eq "highlights") { $new_config->{$name} = [$req->parameters->get_all($name)]; } else { $new_config->{$name} = $req->param($name); } } $self->app->reload_config($new_config); $self->app->broadcast( $self->app->format_info("config", "saved") ); my $res = $req->new_response(200); $res->content_type('text/plain'); $res->content_length(2); $res->body('ok'); return $res->finalize; } sub tab_order { my ($self, $req) = @_; $self->app->log(debug => "updating tab order"); $self->app->tab_order([grep {defined $_} $req->parameters->get_all('tabs')]); my $res = $req->new_response(200); $res->content_type('text/plain'); $res->content_length(2); $res->body('ok'); return $res->finalize; } sub not_found { my ($self, $req) = @_; $self->app->log(debug => "sending 404 " . $req->path_info); my $res = $req->new_response(404); return $res->finalize; } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/Test/0000755000175000017500000000000011437215373015370 5ustar leedoleedoApp-Alice-0.19/lib/App/Alice/Test/NullHistory.pm0000644000175000017500000000017311366325415020223 0ustar leedoleedopackage App::Alice::Test::NullHistory; use Any::Moose; sub store {} sub search {} __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/Test/MockIRC.pm0000644000175000017500000000547711366325415017172 0ustar leedoleedopackage App::Alice::Test::MockIRC; use Any::Moose; use AnyEvent::IRC::Util qw/parse_irc_msg prefix_nick mk_msg/; use Try::Tiny; has cbs => (is => 'rw', default => sub {{}}); has nick => (is => 'rw'); has user_prefix => ( is => 'rw', lazy => 1, default => sub{$_[0]->nick."!".$_[0]->nick."\@host"} ); has events => ( is => 'rw', default => sub { my $self = shift; { TOPIC => sub { my $msg = shift; my $nick = prefix_nick($msg->{prefix}); $self->cbs->{channel_topic}->($self, @{$msg->{params}}, $nick); }, JOIN => sub { my $msg = shift; my $nick = prefix_nick($msg->{prefix}); $self->cbs->{join}->($self, $nick, $msg->{params}[0], $nick eq $self->nick); $self->cbs->{channel_add}->($self, $msg, $msg->{params}[0], $nick); $self->send_srv(WHO => $msg->{params}[0]); }, NICK => sub { my $msg = shift; my $nick = prefix_nick($msg->{prefix}); $self->cbs->{nick_change}->($self, $nick, ${$msg->{params}}[0], $nick eq $self->nick); }, PART => sub { my $msg = shift; my $nick = prefix_nick($msg->{prefix}); $self->cbs->{part}->($self, $nick, $msg->{params}[0], $nick eq $self->nick); $self->cbs->{channel_remove}->($self, $msg, $msg->{params}[0], $nick); }, PRIVMSG => sub { my $msg = shift; my $nick = prefix_nick($msg->{prefix}); $self->cbs->{privatemsg}->($self, $nick, $msg); }, numeric => sub { my ($msg, $number) = @_; $self->cbs->{"irc_$number"}->($self, $msg); }, } } ); sub send_srv { my ($self, $command, @args) = @_; my $echo = sub {mk_msg($self->user_prefix, $command, @args)}; my $map = { map({$_ => $echo} qw/TOPIC JOIN PART NICK/), WHO => sub{ my $user = ($args[0] =~ /^#/ ? "test" : $args[0]); ":local.irc 352 ".$self->nick." #test $user il.comcast.net local.irc $user H :0 $user"; }, }; $map->{$command} ? $self->send_cl($map->{$command}->()) : warn "no line mapped for $command\n" } sub send_raw { my ($self, $line) = @_; $self->send_srv(split ' ', $line); } sub send_cl { my ($self, $line) = @_; my $msg = parse_irc_msg($line); my $cmd = ($msg->{command} =~ /^\d+/ ? 'numeric' : $msg->{command}); try { $self->events->{$cmd}->($msg, $msg->{command}) if $self->events->{$cmd} } catch { warn "$_\n" }; } sub enable_ssl {} sub isupport {} sub ctcp_auto_reply {} sub connect { my $self = shift; $self->cbs->{connect}->(); } sub register { my $self = shift; $self->cbs->{registered}->(); } sub disconnect { my $self = shift; $self->cbs->{disconnect}->(); } sub enable_ping {} sub reg_cb { my ($self, %callbacks) = @_; for (keys %callbacks) { $self->cbs->{$_} = $callbacks{$_}; } } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/MessageBuffer.pm0000644000175000017500000000147711366325415017536 0ustar leedoleedopackage App::Alice::MessageBuffer; use Any::Moose; has store => ( is => 'ro', lazy => 1, default => sub { my $self = shift; eval "require App::Alice::MessageStore::".$self->store_class; ("App::Alice::MessageStore::".$self->store_class)->new(id => $self->id); } ); has id => ( is => 'ro', required => 1, ); has store_class => ( is => 'ro', default => 'Memory', ); has previous_nick => ( is => 'rw', default => "", ); sub clear { my $self = shift; $self->previous_nick(""); $self->store->clear; } sub add { my ($self, $message) = @_; $message->{event} ne "say" ? $self->previous_nick("") : $self->previous_nick($message->{nick}); $self->store->add($message); } sub with_messages { my $self = shift; $self->store->with_messages(@_); } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/Window.pm0000644000175000017500000001502411436011370016246 0ustar leedoleedopackage App::Alice::Window; use Encode; use utf8; use App::Alice::MessageBuffer; use Text::MicroTemplate qw/encoded_string/; use IRC::Formatting::HTML qw/irc_to_html/; use Any::Moose; my $url_regex = qr/\b(https?:\/\/(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i; has type => ( is => 'ro', isa => 'Str', lazy => 1, default => sub {return shift->title =~ /^[#&]/ ? "channel" : "privmsg"} ); has is_channel => ( is => 'ro', isa => 'Bool', lazy => 1, default => sub {return shift->type eq "channel"} ); has assetdir => ( is => 'ro', isa => 'Str', required => 1, ); has buffer => ( is => 'rw', isa => 'App::Alice::MessageBuffer', lazy => 1, default => sub { my $self = shift; App::Alice::MessageBuffer->new( store_class => $self->app->config->message_store, id => $self->id, ); }, ); has title => ( is => 'ro', isa => 'Str', required => 1, ); has sort_name => ( is => 'ro', lazy => 1, default => sub { my $name = $_[0]->title; $name =~ s/^#//; $name; } ); has topic => ( is => 'rw', isa => 'HashRef[Str|Undef]', default => sub {{ string => 'no topic set', author => '', time => time, }} ); has id => ( is => 'ro', isa => 'Str', lazy => 1, default => sub { return $_[0]->app->_build_window_id($_[0]->title, $_[0]->session); }, ); has session => ( is => 'ro', isa => 'Str', lazy => 1, default => sub {return shift->irc->alias} ); has _irc => ( is => 'ro', isa => 'App::Alice::IRC', required => 1, weak_ref => 1, ); has app => ( is => 'ro', isa => 'App::Alice', weak_ref => 1, required => 1, ); # move irc arg to _irc, which is wrapped in a method # because infowindow has logic to choose which irc # connection to return sub BUILDARGS { my $class = shift; my $args = ref $_[0] ? $_[0] : {@_}; $args->{_irc} = $args->{irc}; delete $args->{irc}; return $args; } sub irc { $_[0]->_irc } sub serialized { my ($self) = @_; return { id => $self->id, session => $self->session, title => $self->title, is_channel => $self->is_channel, type => $self->type, hashtag => $self->hashtag, }; } sub nick { my $self = shift; decode_utf8($self->irc->nick) unless utf8::is_utf8($self->irc->nick); } sub all_nicks { my $self = shift; return $self->is_channel ? $self->irc->channel_nicks($self->title) : [ $self->title, $self->nick ]; } sub join_action { my $self = shift; my $action = { type => "action", event => "join", nicks => $self->all_nicks, window => $self->serialized, }; $action->{html}{window} = $self->app->render("window", $self); $action->{html}{tab} = $self->app->render("tab", $self); $action->{html}{select} = $self->app->render("select", $self); return $action; } sub nicks_action { my $self = shift; return { type => "action", event => "nicks", nicks => $self->all_nicks, window => $self->serialized, }; } sub clear_action { my $self = shift; return { type => "action", event => "clear", window => $self->serialized, }; } sub format_event { my ($self, $event, $nick, $body) = @_; my $message = { type => "message", event => $event, nick => $nick, window => $self->serialized, body => $body, msgid => $self->app->next_msgid, timestamp => time, nicks => $self->all_nicks, }; $message->{html} = make_links_clickable( $self->app->render("event", $message) ); $self->buffer->add($message); return $message; } sub format_message { my ($self, $nick, $body) = @_; $body = decode_utf8($body) unless utf8::is_utf8($body); my $monospace = $self->app->is_monospace_nick($nick); # pass the inverse => italic option if this is NOT monospace my $html = irc_to_html($body, ($monospace ? () : (invert => "italic"))); $html = make_links_clickable($html); my $own_nick = $self->nick; my $message = { type => "message", event => "say", nick => $nick, avatar => $self->irc->nick_avatar($nick), window => $self->serialized, html => encoded_string($html), self => $own_nick eq $nick, msgid => $self->app->next_msgid, timestamp => time, monospaced => $monospace, consecutive => $nick eq $self->buffer->previous_nick ? 1 : 0, }; unless ($message->{self}) { $message->{highlight} = $self->app->is_highlight($own_nick, $body); } $message->{html} = $self->app->render("message", $message); $self->buffer->add($message); return $message; } sub format_announcement { my ($self, $msg) = @_; $msg = decode_utf8($msg) unless utf8::is_utf8($msg) or ref $msg eq "Text::MicroTemplate::EncodedString"; my $message = { type => "message", event => "announce", window => $self->serialized, message => $msg, }; $message->{html} = $self->app->render('announcement', $message); $message->{message} = "$message->{message}"; $self->reset_previous_nick; return $message; } sub close_action { my $self = shift; my $action = { type => "action", event => "part", window => $self->serialized, }; return $action; } sub nick_table { my ($self, $avatars) = @_; if ($avatars) { return encoded_string($self->app->render("avatargrid", $self)); } return _format_nick_table($self->all_nicks); } sub make_links_clickable { my $html = shift; $html =~ s/$url_regex/$1<\/a>/gi; return $html; } sub _format_nick_table { my $nicks = shift; return "" unless @$nicks; my $maxlen = 0; for (@$nicks) { my $length = length $_; $maxlen = $length if $length > $maxlen; } my $cols = int(74 / $maxlen + 2); my (@rows, @row); for (sort {lc $a cmp lc $b} @$nicks) { push @row, $_ . " " x ($maxlen - length $_); if (@row >= $cols) { push @rows, [@row]; @row = (); } } push @rows, [@row] if @row; return join "\n", map {join " ", @$_} @rows; } sub reset_previous_nick { my $self = shift; $self->buffer->previous_nick(""); } sub previous_nick { my $self = shift; return $self->buffer->previous_nick; } sub hashtag { my $self = shift; if ($self->type eq "info") { return "/" . $self->title; } return "/" . $self->session . "/" . $self->title; } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/Signal.pm0000644000175000017500000000065611373075677016245 0ustar leedoleedopackage App::Alice::Signal; use AnyEvent; use Any::Moose; has type => ( is => 'ro', isa => 'Str', required => 1, ); has app => ( is => 'ro', isa => 'App::Alice', weak_ref => 1, required => 1, ); sub BUILD { my $self = shift; my $method = "sig" . lc $self->type; $self->$method(); } sub sigint {$_[0]->app->init_shutdown}; sub sigquit {$_[0]->app->init_shutdown}; __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/IRC.pm0000644000175000017500000004065411435242024015424 0ustar leedoleedopackage App::Alice::IRC; use AnyEvent; use AnyEvent::IRC::Client; use List::Util qw/min first/; use List::MoreUtils qw/uniq/; use Digest::MD5 qw/md5_hex/; use Any::Moose; use utf8; use Encode; has 'cl' => ( is => 'rw', default => sub {AnyEvent::IRC::Client->new}, ); has 'alias' => ( isa => 'Str', is => 'ro', required => 1, ); has 'nick_cached' => ( isa => 'Str', is => 'rw', lazy => 1, default => sub { my $self = shift; return $self->config->{nick}; }, ); sub config { $_[0]->app->config->servers->{$_[0]->alias}; } has 'app' => ( isa => 'App::Alice', is => 'ro', weak_ref => 1, required => 1, ); has 'reconnect_timer' => ( is => 'rw' ); has [qw/reconnect_count connect_time/] => ( is => 'rw', isa => 'Int', default => 0, ); sub increase_reconnect_count {$_[0]->reconnect_count($_[0]->reconnect_count + 1)} sub reset_reconnect_count {$_[0]->reconnect_count(0)} has [qw/is_connected disabled removed/] => ( is => 'rw', isa => 'Bool', default => 0, ); has _nicks => ( is => 'rw', isa => 'ArrayRef[HashRef|Undef]', default => sub {[]}, ); sub nicks {@{$_[0]->_nicks}} sub all_nicks {[map {$_->{nick}} @{$_[0]->_nicks}]} sub add_nick {push @{$_[0]->_nicks}, $_[1]} sub remove_nick {$_[0]->_nicks([grep {$_->{nick} ne $_[1]} $_[0]->nicks])} sub get_nick_info {first {$_->{nick} eq $_[1]} $_[0]->nicks} sub includes_nick {$_[0]->get_nick_info($_[1])} sub all_nick_info {$_[0]->nicks} sub set_nick_info {$_[0]->remove_nick($_[1]); $_[0]->add_nick($_[2]);} sub clear_nicks {$_[0]->_nicks([])} has whois_cbs => ( is => 'rw', isa => 'HashRef[CodeRef]', default => sub {{}}, ); sub add_whois_cb { my ($self, $nick, $cb) = @_; $self->whois_cbs->{$nick} = $cb; $self->send_srv(WHO => $nick); } sub BUILD { my $self = shift; $self->cl->enable_ssl if $self->config->{ssl}; $self->disabled(1) unless $self->config->{autoconnect}; $self->cl->reg_cb( registered => sub{$self->registered($_)}, channel_add => sub{$self->channel_add(@_)}, channel_remove => sub{$self->channel_remove(@_)}, channel_topic => sub{$self->channel_topic(@_)}, join => sub{$self->_join(@_)}, part => sub{$self->part(@_)}, nick_change => sub{$self->nick_change(@_)}, ctcp_action => sub{$self->ctcp_action(@_)}, publicmsg => sub{$self->publicmsg(@_)}, privatemsg => sub{$self->privatemsg(@_)}, connect => sub{$self->connected(@_)}, disconnect => sub{$self->disconnected(@_)}, irc_001 => sub{$self->log_message($_[1])}, irc_352 => sub{$self->irc_352(@_)}, # WHO info irc_366 => sub{$self->irc_366(@_)}, # end of NAMES irc_372 => sub{$self->log_message(mono => 1, $_[1])}, # MOTD info irc_377 => sub{$self->log_message(mono => 1, $_[1])}, # MOTD info irc_378 => sub{$self->log_message(mono => 1, $_[1])}, # MOTD info irc_401 => sub{$self->irc_401(@_)}, irc_432 => sub{$self->nick; $self->log_message($_[1])}, # Bad nick irc_433 => sub{$self->nick; $self->log_message($_[1])}, # Bad nick irc_464 => sub{$self->disconnect("bad USER/PASS")}, ); $self->cl->ctcp_auto_reply ('VERSION', ['VERSION', "alice $App::Alice::VERSION"]); $self->connect unless $self->disabled; } sub send_srv { my ($self, $cmd, @params) = @_; $self->cl->send_srv($cmd => map {encode_utf8($_)} @params); } sub send_raw { my ($self, $cmd) = @_; $self->cl->send_raw(encode_utf8($cmd)); } sub broadcast { my $self = shift; $self->app->broadcast(@_); } sub init_shutdown { my ($self, $msg) = @_; $self->disabled(1); if ($self->is_connected) { $self->disconnect($msg); return; } $self->shutdown; } sub shutdown { my $self = shift; $self->cl(undef); $self->app->remove_irc($self->alias); $self->app->shutdown if !$self->app->ircs; } sub log { my $messages = pop; $messages = [ $messages ] unless ref $messages eq "ARRAY"; my ($self, $level, %options) = @_; my @lines = map {$self->format_info($_, %options)} @$messages; $self->broadcast(@lines); $self->app->log($level => "[".$self->alias . "] $_") for @$messages; } sub log_message { my $message = pop; my ($self, %options) = @_; if (@{$message->{params}}) { $self->log("debug", %options, [ pop @{$message->{params}} ]); } } sub format_info { my ($self, $message, %options) = @_; $self->app->format_info($self->alias, $message, %options); } sub window { my ($self, $title) = @_; return $self->app->find_or_create_window($title, $self); } sub find_window { my ($self, $title) = @_; return $self->app->find_window($title, $self); } sub nick { my $self = shift; my $nick = $self->cl->nick; if ($nick and $nick ne "") { $self->nick_cached($nick); return $nick; } return $self->nick_cached || "Failure"; } sub windows { my $self = shift; return grep {$_->type ne "info" && $_->irc->alias eq $self->alias} $self->app->windows; } sub channels { my $self = shift; return map {$_->title} grep {$_->is_channel} $self->windows; } sub connect { my $self = shift; $self->disabled(0); $self->increase_reconnect_count; $self->cl->{enable_ssl} = $self->config->{ssl} ? 1 : 0; # some people don't set these, wtf if (!$self->config->{host} or !$self->config->{port}) { $self->log(info => "can't connect: missing either host or port"); return; } $self->reconnect_count > 1 ? $self->log(info => "reconnecting: attempt " . $self->reconnect_count) : $self->log(debug => "connecting"); $self->cl->connect( $self->config->{host}, $self->config->{port} ); } sub connected { my ($self, $cl, $err) = @_; if (defined $err) { $self->log(info => "connect error: $err"); $self->reconnect(); return; } $self->log(info => "connected"); $self->reset_reconnect_count; $self->connect_time(time); $self->is_connected(1); $self->cl->register( $self->nick, $self->config->{username}, $self->config->{ircname}, $self->config->{password} ); $self->broadcast({ type => "action", event => "connect", session => $self->alias, windows => [map {$_->serialized} $self->windows], }); } sub reconnect { my ($self, $time) = @_; my $interval = time - $self->connect_time; if ($interval < 15) { $time = 15 - $interval; $self->log(debug => "last attempt was within 15 seconds, delaying $time seconds") } if (!defined $time) { # increase timer by 15 seconds each time, until it hits 5 minutes $time = min 60 * 5, 15 * $self->reconnect_count; } $self->log(debug => "reconnecting in $time seconds"); $self->reconnect_timer( AnyEvent->timer(after => $time, cb => sub { $self->connect unless $self->is_connected; }) ); } sub cancel_reconnect { my $self = shift; $self->reconnect_timer(undef); $self->reset_reconnect_count; } sub registered { my $self = shift; my @log; $self->cl->enable_ping (300, sub { $self->is_connected(0); $self->log(debug => "ping timeout"); $self->reconnect(0); }); for (@{$self->config->{on_connect}}) { push @log, "sending $_"; $self->send_raw($_); } # merge auto-joined channel list with existing channels my @channels = uniq @{$self->config->{channels}}, $self->channels; for (@channels) { push @log, "joining $_"; $self->send_srv("JOIN", split /\s+/); } $self->log(debug => \@log); }; sub disconnected { my ($self, $cl, $reason) = @_; delete $self->{disconnect_timer} if $self->{disconnect_timer}; $reason = "" unless $reason; return if $reason eq "reconnect requested."; $self->log(info => "disconnected: $reason"); $self->broadcast(map { $_->format_event("disconnect", $self->nick, $reason), } $self->windows); $self->broadcast({ type => "action", event => "disconnect", session => $self->alias, windows => [map {$_->serialized} $self->windows], }); $self->is_connected(0); $self->clear_nicks; if ($self->app->shutting_down and !$self->app->connected_ircs) { $self->shutdown; return; } $self->reconnect(0) unless $self->disabled; if ($self->removed) { $self->app->remove_irc($self->alias); undef $self; } } sub disconnect { my ($self, $msg) = @_; $self->disabled(1); if (!$self->app->shutting_down) { $self->app->remove_window($_) for $self->windows; } $msg ||= $self->app->config->quitmsg; $self->log(debug => "disconnecting: $msg") if $msg; $self->send_srv(QUIT => $msg); $self->{disconnect_timer} = AnyEvent->timer( after => 1, cb => sub { delete $self->{disconnect_timer}; $self->cl->disconnect($msg); } ); } sub remove { my $self = shift; $self->removed(1); $self->disconnect; } sub publicmsg { my ($self, $cl, $channel, $msg) = @_; utf8::decode($channel); if (my $window = $self->find_window($channel)) { my $nick = (split '!', $msg->{prefix})[0]; return if $self->app->is_ignore($nick); my $text = $msg->{params}[1]; utf8::decode($_) for ($text, $nick); $self->app->store(nick => $nick, channel => $channel, body => $text); $self->broadcast($window->format_message($nick, $text)); } } sub privatemsg { my ($self, $cl, $nick, $msg) = @_; my $text = $msg->{params}[1]; utf8::decode($_) for ($nick, $text); if ($msg->{command} eq "PRIVMSG") { my $from = (split /!/, $msg->{prefix})[0]; utf8::decode($from); return if $self->app->is_ignore($from); my $window = $self->window($from); $self->app->store(nick => $from, channel => $from, body => $text); $self->broadcast($window->format_message($from, $text)); $self->send_srv(WHO => $from) unless $self->includes_nick($from); } elsif ($msg->{command} eq "NOTICE") { $self->log(debug => $text); } } sub ctcp_action { my ($self, $cl, $nick, $channel, $msg, $type) = @_; utf8::decode($_) for ($nick, $msg, $channel); return if $self->app->is_ignore($nick); if (my $window = $self->find_window($channel)) { my $text = "• $msg"; $self->app->store(nick => $nick, channel => $channel, body => $text); $self->broadcast($window->format_message($nick, $text)); } } sub nick_change { my ($self, $cl, $old_nick, $new_nick, $is_self) = @_; utf8::decode($_) for ($old_nick, $new_nick); $self->nick_cached($new_nick) if $is_self; $self->rename_nick($old_nick, $new_nick); $self->broadcast( map {$_->format_event("nick", $old_nick, $new_nick)} $self->nick_windows($new_nick) ); } sub _join { my ($self, $cl, $nick, $channel, $is_self) = @_; utf8::decode($_) for ($nick, $channel); if (!$self->includes_nick($nick)) { $self->add_nick({nick => $nick, real => "", channels => {$channel => ''}}); } else { $self->get_nick_info($nick)->{channels}{$channel} = ''; } if ($is_self) { # self->window uses find_or_create, so we don't create # duplicate windows here my $window = $self->window($channel); $self->broadcast($window->join_action); # client library only sends WHO if the server doesn't # send hostnames with NAMES list (UHNAMES), we to WHO always $self->send_srv("WHO" => $channel) if $cl->isupport("UHNAMES"); } elsif (my $window = $self->find_window($channel)) { $self->send_srv("WHO" => $nick); $self->broadcast($window->format_event("joined", $nick)); } } sub channel_add { my ($self, $cl, $msg, $channel, @nicks) = @_; utf8::decode($_) for (@nicks, $channel); if (my $window = $self->find_window($channel)) { for (@nicks) { if (!$self->includes_nick($_)) { $self->add_nick({nick => $_, real => "", channels => {$channel => ''}}); } else { $self->get_nick_info($_)->{channels}{$channel} = ''; } } } } sub part { my ($self, $cl, $nick, $channel, $is_self, $msg) = @_; utf8::decode($_) for ($channel, $nick, $msg); if ($is_self and my $window = $self->find_window($channel)) { $self->log(debug => "leaving $channel"); $self->app->close_window($window); for ($self->all_nick_info) { delete $_->{channels}{$channel} if exists $_->{channels}{$channel}; } } } sub channel_remove { my ($self, $cl, $msg, $channel, @nicks) = @_; utf8::decode($_) for ($channel, @nicks); return if !@nicks or grep {$_ eq $self->nick} @nicks; if (my $window = $self->find_window($channel)) { my $body; if ($msg->{command} and $msg->{command} eq "PART") { for (@nicks) { next unless $self->includes_nick($_); delete $self->get_nick_info($_)->{channels}{$channel}; $self->remove_nick($_) unless $self->nick_channels($_); } } else { $self->remove_nicks(@nicks); $body = $msg->{params}[0]; utf8::decode($body); } $self->broadcast(map {$window->format_event("left", $_, $body)} @nicks); } } sub channel_topic { my ($self, $cl, $channel, $topic, $nick) = @_; utf8::decode($_) for ($channel, $nick, $topic); if (my $window = $self->find_window($channel)) { $window->topic({string => $topic, author => $nick, time => time}); $self->broadcast($window->format_event("topic", $nick, $topic)); } } sub channel_nicks { my ($self, $channel) = @_; return [ map {$_->{nick}} grep {exists $_->{channels}{$channel}} $self->all_nick_info ]; } sub nick_channels { my ($self, $nick) = @_; my $info = $self->get_nick_info($nick); return keys %{$info->{channels}} if $info->{channels}; } sub nick_windows { my ($self, $nick) = @_; if ($self->nick_channels($nick)) { return grep {$_} map {$self->find_window($_)} $self->nick_channels($nick); } return; } sub irc_352 { my ($self, $cl, $msg) = @_; # ignore the first param if it is our own nick, some servers include it shift @{$msg->{params}} if $msg->{params}[0] eq $self->nick; my ($channel, $user, $ip, $server, $nick, $flags, @real) = @{$msg->{params}}; my $real = join " ", @real; return unless $nick; $real =~ s/^\d // if $real; utf8::decode($_) for ($channel, $user, $nick, $real); my $info = { IP => $ip || "", user => $user || "", server => $server || "", real => $real || "", channels => {$channel => $flags}, nick => $nick, }; if ($self->includes_nick($nick)) { my $prev_info = $self->get_nick_info($nick); $info->{channels} = { %{$prev_info->{channels}}, %{$info->{channels}}, }; if ($info->{real} ne $prev_info->{real}) { for (grep {$_->previous_nick eq $nick} $self->windows) { $_->reset_previous_nick; } } } $self->set_nick_info($nick, $info); if ($self->whois_cbs->{$nick}) { $self->whois_cbs->{$nick}->(); delete $self->whois_cbs->{$nick}; } } sub irc_366 { my ($self, $cl, $msg) = @_; utf8::decode($msg->{params}[1]); if (my $window = $self->find_window($msg->{params}[1])) { $self->broadcast($window->nicks_action); } } sub irc_401 { my ($self, $cl, $msg) = @_; utf8::decode($msg->{params}[1]); if (my $window = $self->find_window($msg->{params}[1])) { $self->broadcast($window->format_announcement("No such nick.")); } } sub rename_nick { my ($self, $nick, $new_nick) = @_; return unless $self->includes_nick($nick); my $info = $self->get_nick_info($nick); $info->{nick} = $new_nick; $self->set_nick_info($new_nick, $info); $self->remove_nick($nick); } sub remove_nicks { my ($self, @nicks) = @_; $self->_nicks( grep { my $nick = $_; first {$nick eq $_} @nicks ? 0 : 1; } $self->nicks ); } sub nick_avatar { my ($self, $nick) = @_; my $info = $self->get_nick_info($nick); if ($info and $info->{real}) { if ($info->{real} =~ /([^<\s]+@[^\s>]+\.[^\s>]+)/) { my $email = $1; return "http://www.gravatar.com/avatar/" . md5_hex($email) . "?s=32&r=x"; } elsif ($info->{real} =~ /(https?:\/\/\S+(?:jpe?g|png|gif))/) { return $1; } else { return undef; } } } sub whois_table { my ($self, $nick) = @_; my $info = $self->get_nick_info($nick); return "No info for user \"$nick\"" if !$info; return "real: $info->{real}\nuser: $info->{user}\n" . "host: $info->{IP}\nserver: $info->{server}\nchannels: " . join " ", keys %{$info->{channels}}; } sub update_realname { my ($self, $realname) = @_; my $nick = $self->nick_cached; $self->send_srv(REALNAME => $realname); if (my $info = $self->get_nick_info($nick)) { $info->{real} = $realname; } for (grep {$_->previous_nick eq $nick} $self->windows) { $_->reset_previous_nick; } } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/Logger.pm0000644000175000017500000000164711366325415016236 0ustar leedoleedopackage App::Alice::Logger; use Any::Moose; has callbacks => ( is => 'ro', isa => 'HashRef', default => sub { my $hashref = {map {uc $_ => [\&print_line]} qw/debug info warn error fatal/}; } ); sub add_cb { my ($self, $level, $cb) = @_; return unless $self->callbacks->{$level}; push @{$self->callbacks->{$level}}, $cb; } sub log { my ($self, $level, $message) = @_; $level = uc $level; return unless @{$self->callbacks->{$level}}; $_->($level, $message) for @{$self->callbacks->{$level}}; } sub print_line { my ($level, $message) = @_; my ($sec, $min, $hour, $day, $mon, $year) = localtime(time); my $datestring = sprintf "%02d:%02d:%02d %02d/%02d/%02d", $hour, $min, $sec, $mon, $day, $year % 100; print STDERR substr($level, 0, 1) . ", [$datestring] " . sprintf("% 5s", $level) . " -- : $message\n"; } __PACKAGE__->meta->make_immutable; 1;App-Alice-0.19/lib/App/Alice/Commands.pm0000644000175000017500000000265311406220522016542 0ustar leedoleedopackage App::Alice::Commands; use Any::Moose; use Encode; has 'handlers' => ( is => 'rw', isa => 'ArrayRef', default => sub {[]}, ); has 'app' => ( is => 'ro', isa => 'App::Alice', weak_ref => 1, required => 1, ); sub BUILD { my $self = shift; $self->reload_handlers; } sub reload_handlers { my $self = shift; my $commands_file = $self->app->config->assetdir . "/commands.pl"; if (-e $commands_file) { my $commands = do $commands_file; if ($commands and ref $commands eq "ARRAY") { $self->handlers($commands) if $commands; } else { warn "$!\n"; } } } sub handle { my ($self, $command, $window) = @_; for my $handler (@{$self->handlers}) { my $re = $handler->{re}; if ($command =~ /$re/) { my @args = grep {defined $_} ($5, $4, $3, $2, $1); # up to 5 captures if ($handler->{in_channel} and !$window->is_channel) { $self->reply($window, "$command can only be used in a channel"); } else { $handler->{code}->($self, $window, @args); } return; } } } sub show { my ($self, $window, $message) = @_; $self->broadcast($window->format_message($window->nick, $message)); } sub reply { my ($self, $window, $message) = @_; $self->broadcast($window->format_announcement($message)); } sub broadcast { my ($self, @messages) = @_; $self->app->broadcast(@messages); } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/Notifier/0000755000175000017500000000000011437215373016230 5ustar leedoleedoApp-Alice-0.19/lib/App/Alice/Notifier/LibNotify.pm0000644000175000017500000000074711366325415020475 0ustar leedoleedopackage App::Alice::Notifier::LibNotify; use Desktop::Notify; use Any::Moose; has 'client' => ( is => 'ro', isa => 'Desktop::Notify', default => sub { return Desktop::Notify->new; } ); sub display { my ($self, $message) = @_; my $notification = $self->client->create( summary => $message->{nick} . " in " . $message->{window}->{title}, body => $message->{body}, timeout => 3000); $notification->show; } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/Notifier/Growl.pm0000644000175000017500000000063511366325415017664 0ustar leedoleedopackage App::Alice::Notifier::Growl; use Any::Moose; sub BUILD { my $self = shift; require Mac::Growl; Mac::Growl->import(":all"); RegisterNotifications("Alice", ["message"], ["message"]); } sub display { my ($self, $message) = @_; PostNotification("Alice", "message", $message->{nick} . " in " . $message->{window}->{title}, $message->{body}, ); } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice/InfoWindow.pm0000644000175000017500000000413711436011377017074 0ustar leedoleedopackage App::Alice::InfoWindow; use Any::Moose; use Encode; use IRC::Formatting::HTML qw/irc_to_html/; use Text::MicroTemplate qw/encoded_string/; extends 'App::Alice::Window'; has '+is_channel' => (lazy => 0, default => 0); has '+title' => (required => 0, default => 'info'); has '+session' => (default => ""); has 'topic' => (is => 'ro', isa => 'HashRef', default => sub {{string => ''}}); has '+type' => (lazy => 0, default => 'info'); has '+_irc' => (required => 0, isa => 'Any'); sub irc { my $self = shift; return ($self->app->connected_ircs)[0] if $self->app->connected_ircs == 1; $self->app->broadcast( $self->format_announcement("No server specified! /command -server args")); return undef; } sub all_nicks { return []; } sub format_message { my ($self, $from, $body, %options) = @_; my $html = irc_to_html($body); my $message = { type => "message", event => "say", nick => $from, window => $self->serialized, html => encoded_string($html), self => $options{self} ? 1 : 0, hightlight => $options{highlight} ? 1 : 0, msgid => $self->app->next_msgid, timestamp => time, monospaced => $options{mono} ? 1 : 0, consecutive => $from eq $self->buffer->previous_nick ? 1 : 0, }; $message->{html} = $self->app->render("message", $message); $self->buffer->add($message); return $message; } sub copy_message { my ($self, $msg) = @_; my $copy = { type => "message", event => "say", nick => $msg->{nick}, window => $self->serialized, html => $msg->{html}, self => $msg->{self}, highlight => $msg->{highlight}, msgid => $self->app->next_msgid, timestamp => $msg->{timestamp}, monospaced => $msg->{monospaced}, consecutive => $msg->{nick} eq $self->buffer->previous_nick ? 1 : 0, }; if ($msg->{consecutive} and !$copy->{consecutive}) { $copy->{html} =~ s/(
  • {consecutive} and $copy->{consecutive}) { $copy->{html} =~ s/(
  • ( is => 'rw', isa => 'Str', default => "show", ); has avatars => ( is => 'rw', isa => 'Str', default => "show", ); has style => ( is => 'rw', isa => 'Str', default => 'default', ); has timeformat => ( is => 'rw', isa => 'Str', default => '24', ); has alerts => ( is => 'rw', isa => 'Str', default => 'show', ); has quitmsg => ( is => 'rw', isa => 'Str', default => 'alice.', ); has debug => ( is => 'rw', isa => 'Bool', default => 0, ); has port => ( is => 'rw', isa => 'Str', default => "8080", ); has address => ( is => 'rw', isa => 'Str', default => '127.0.0.1', ); has auth => ( is => 'rw', isa => 'HashRef[Str]', default => sub {{}}, ); has highlights => ( is => 'rw', isa => 'ArrayRef[Str]', default => sub {[]}, ); has servers => ( is => 'rw', isa => 'HashRef[HashRef]', default => sub {{}}, ); has order => ( is => 'rw', isa => 'ArrayRef[Str]', default => sub {[]}, ); has monospace_nicks => ( is => 'rw', isa => 'ArrayRef[Str]', default => sub {['Shaniqua']}, ); has path => ( is => 'ro', isa => 'Str', default => "$ENV{HOME}/.alice", ); has file => ( is => 'ro', isa => 'Str', default => "config", ); has fullpath => ( is => 'ro', isa => 'Str', lazy => 1, default => sub {$_[0]->path ."/". $_[0]->file}, ); has commandline => ( is => 'ro', isa => 'HashRef', default => sub {{}}, ); has ignore => ( is => 'rw', isa => 'ArrayRef', default => sub {[]}, ); has message_store => ( is => 'rw', isa => 'Str', default => 'Memory' ); has static_prefix => ( is => 'rw', isa => 'Str', default => '/static/', ); sub ignores {@{$_[0]->ignore}} sub add_ignore {push @{shift->ignore}, @_} sub BUILD { my $self = shift; $self->load; mkdir $self->path unless -d $self->path; } sub load { my $self = shift; my $config = {}; if (-e $self->fullpath) { $config = require $self->fullpath; } else { say STDERR "No config found, writing a few config to ".$self->fullpath; $self->write; } my ($port, $debug, $address) = @_; GetOptions("port=i" => \$port, "debug" => \$debug, "address=s" => \$address); $self->commandline->{port} = $port if $port and $port =~ /\d+/; $self->commandline->{debug} = 1 if $debug; $self->commandline->{address} = $address if $address; $self->merge($config); } sub http_port { my $self = shift; if ($self->commandline->{port}) { return $self->commandline->{port}; } return $self->port; } sub http_address { my $self = shift; if ($self->commandline->{address}) { return $self->commandline->{address}; } if ($self->address eq "localhost") { $self->address("127.0.0.1"); } return $self->address; } sub show_debug { my $self = shift; if ($self->commandline->{debug}) { return 1; } return $self->debug; } sub merge { my ($self, $config) = @_; for my $key (keys %$config) { if (exists $config->{$key} and my $attr = $self->meta->get_attribute($key)) { $self->$key($config->{$key}) if $attr->has_write_method; } else { say STDERR "$key is not a valid config option"; } } } sub write { my ($self, $data) = @_; my $config = $data || $self->serialized; mkdir $self->path if !-d $self->path; open my $fh, ">", $self->fullpath; { local $Data::Dumper::Terse = 1; local $Data::Dumper::Indent = 1; print $fh Dumper($config) or warn "Can not write config file: $!\n"; } } sub serialized { my $self = shift; return { map { my $name = $_->name; $name => $self->$name; } grep {$_->has_write_method} $self->meta->get_all_attributes }; } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/lib/App/Alice.pod0000644000175000017500000001413211437015710015147 0ustar leedoleedo=pod =head1 NAME App::Alice - an Altogether Lovely Internet Chatting Experience =head1 SYNPOSIS arthur:~ leedo$ alice Location: http://localhost:8080/ =head1 DESCRIPTION Alice is an IRC client that is viewed in the web browser. Alice runs in the background maintaining connections and collecting messages. When a browser connects, it will display the 100 most recent messages for each channel, and update with any new messages as they arrive. Alice also logs messages to an SQLite database. These logs are searchable through the web interface. =head1 USAGE Installation will add a new `alice` command to start the alice server. When the command is run it will start the daemon and print the URL to load in your browser. =head2 COMMANDLINE OPTIONS =over 4 =item -d --debug Print out additional debug information. Useful for development or finding out if something is wrong. =item -p --port This will change the port that the HTTP server listens on. The default port is 8080. =item -a --address This will change the IP address that the HTTP server listens on. The default address is 127.0.0.1. That means alice only accepts local connections by default. If you want to connect to alice remotely you should change it to the IP you want to listen on, or 0.0.0.0 to listen on all addresses. =back =head1 CONFIGURATION Most of alice can be configured through the web interface. There are two windows that can be used to alter the configuration, Connections and Preferences. To bring up either of these windows click the gear icon in the bottom right hand corner of the page. This B bring up the new window. Some browsers (specifically Chrome) will block this popup by default. If it doesn't appear make sure that you allow popups! =head2 CONNECTION WINDOW The connection window is used to add or remove servers. It should be familiar if you have ever used an IRC client (and I assume you have.) The only difference of note is the "Avatar" field. In reality, this field just sets the B. Alice abuses this field to get avatars for users. If a user has an image URL or an email address as their realname, alice will display the image next too their messages. This feature can be disabled in the Preferences window. =head2 PREFERENCES WINDOW The Preferences window can be used to set configuration options that are not connection specific. You can toggle the use of avatars, timestamps, and notifications. You can also edit a list of highlightable terms. =head2 HTTP AUTHENTICATION Some configuration options do not have a UI yet. The most notable of these options is HTTP authentication. If you would like to use HTTP authentication, you will have to edit your configuration file by hand. You can find this file at ~/.alice/config. The config is simply a perl hash. So, if you are familiar with perl it should not be too intimidating. If you do not know perl, sorry! :) You will need to add "user" and "pass" values to the "auth" hash. The resulting section of configuration might look like this: 'auth' => { 'user' => 'lee', 'pass' => 'mypassword', }, =head1 COMMANDS =over 4 =item /j[oin] [-network] $channel Takes a channel name as an argument, and an optional network flag. If no network flag is provided, it will use the network of the current tab. =item /close Closes the current tab. If used in a channel it will also part the channel. B and B are aliases for /close =item /clear This will clear the current tab's messages from your browser. It will also clear the tab's message buffer so when you refresh your browser the messages won't re-appear. =item /msg [-network] [] Takes a nick as an argument and an optional network flag. If no network flag is provided, it will use the network of the current tab. A third argument may be used for the message text. If no message text is provided, a blank tab will be opened. B is an alias for /msg =item /whois [-force] Takes a nick as an argument and an optional force flag. This will print some information about the supplied user. If the force flag is provided, the information will be refreshed from the server. =item /quote Sends a string as a raw message to the server. B is an alias for /quote =item /t[opic] [] Takes an optional topic string. This will display the topic for the current tab. If a string is supplied, it will attempt to update the channel's topic. Only works in a channel. =item /n[ames] [-avatars] This will print a table of all of the nicks in the current tab. An optional avatars flag can be provided to include avatars. =item /me Sends a string as an action to the channel. e.g. * lee hits clint with a large trout =item /w[indow] Focus the provided tab number. Also accepts "next" or "prev". The space after the w can be ommited (e.g. /w4 to focus window 4.) =item /connect Connect to a network. The network must be the name of a server from the Connections window. If you are already connected to the network it will do nothing. =item /disconnect Disconnect from a network. The network must be the name of a server from the Connections window. This command will also stop any reconnect timers for that network. =item /ignore Ignore any nick that matches the one provided. B =item /ignores List all active ignored nicks. =item /unignore Unignore a nick. =back =head1 NOTIFICATIONS If you get a message with your nick in the body while no browsers are connected, a notification will be sent using either Growl (if running on OS X) or libnotify (on Linux.) You can add additional patterns to highlight in the Preferences window. If you are using Fluid.app (a SSB for OS X) or Chrome you can also get notifications when the window is unfocused. =head1 MOBILE INTERFACE Alice has an iPhone style sheet, but it may work in other mobile browsers as well. Any help or bug reports would be much appreciated. =head1 COPYRIGHT Copyright 2010 by Lee Aylward Eleedo@cpan.orgE This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut App-Alice-0.19/lib/App/Alice.pm0000644000175000017500000002735111437215357015021 0ustar leedoleedopackage App::Alice; use Text::MicroTemplate::File; use App::Alice::Window; use App::Alice::InfoWindow; use App::Alice::HTTPD; use App::Alice::IRC; use App::Alice::Signal; use App::Alice::Config; use App::Alice::Logger; use App::Alice::History; use Any::Moose; use File::Copy; use Digest::MD5 qw/md5_hex/; use List::Util qw/first/; use Encode; our $VERSION = '0.19'; has condvar => ( is => 'rw', isa => 'AnyEvent::CondVar' ); has config => ( is => 'ro', isa => 'App::Alice::Config', ); has msgid => ( is => 'rw', isa => 'Int', default => 1, ); sub next_msgid {$_[0]->msgid($_[0]->msgid + 1)} has _ircs => ( is => 'rw', isa => 'ArrayRef', default => sub {[]}, ); sub ircs {@{$_[0]->_ircs}} sub add_irc {push @{$_[0]->_ircs}, $_[1]} sub has_irc {$_[0]->get_irc($_[1])} sub get_irc {first {$_->alias eq $_[1]} $_[0]->ircs} sub remove_irc {$_[0]->_ircs([ grep { $_->alias ne $_[1] } $_[0]->ircs])} sub irc_aliases {map {$_->alias} $_[0]->ircs} sub connected_ircs {grep {$_->is_connected} $_[0]->ircs} has standalone => ( is => 'ro', isa => 'Bool', default => 1, ); has httpd => ( is => 'rw', isa => 'App::Alice::HTTPD', lazy => 1, default => sub { App::Alice::HTTPD->new(app => shift); }, ); has commands => ( is => 'ro', isa => 'App::Alice::Commands', lazy => 1, default => sub { App::Alice::Commands->new(app => shift); } ); has notifier => ( is => 'ro', lazy => 1, default => sub { my $self = shift; my $notifier; eval { if ($^O eq 'darwin') { # 5.10 doesn't seem to put Extras in @INC # need this for Foundation.pm if ($] >= 5.01 and -e "/System/Library/Perl/Extras/5.10.0") { require lib; lib->import("/System/Library/Perl/Extras/5.10.0"); } require App::Alice::Notifier::Growl; $notifier = App::Alice::Notifier::Growl->new; } elsif ($^O eq 'linux') { require App::Alice::Notifier::LibNotify; $notifier = App::Alice::Notifier::LibNotify->new; } }; $self->log(info => "Notifications disabled") unless $notifier; return $notifier; } ); has history => ( is => 'rw', lazy => 1, default => sub { my $self = shift; my $config = $self->config->path."/log.db"; if (-e $config) { if ((stat($config))[9] < 1272757679) { print STDERR "Log schema is out of date, updating\n"; copy($self->config->assetdir."/log.db", $config); } } else { copy($self->config->assetdir."/log.db", $config); } App::Alice::History->new( dbfile => $self->config->path ."/log.db" ); }, ); sub store { my $self = shift; my %fields = @_; $fields{user} = $self->user; $fields{time} = time; $self->history->store(%fields); } has logger => ( is => 'ro', default => sub {App::Alice::Logger->new}, ); sub log {$_[0]->logger->log($_[1] => $_[2]) if $_[0]->config->show_debug} has _windows => ( is => 'rw', isa => 'ArrayRef', default => sub {[]}, ); sub windows {@{$_[0]->_windows}} sub add_window {push @{$_[0]->_windows}, $_[1]} sub has_window {$_[0]->get_window($_[1])} sub get_window {first {$_->id eq $_[1]} $_[0]->windows} sub remove_window {$_[0]->_windows([grep {$_->id ne $_[1]} $_[0]->windows])} sub window_ids {map {$_->id} $_[0]->windows} has 'template' => ( is => 'ro', isa => 'Text::MicroTemplate::File', lazy => 1, default => sub { my $self = shift; Text::MicroTemplate::File->new( include_path => $self->config->assetdir . '/templates', cache => 1, ); }, ); has 'info_window' => ( is => 'ro', isa => 'App::Alice::InfoWindow', lazy => 1, default => sub { my $self = shift; my $info = App::Alice::InfoWindow->new( assetdir => $self->config->assetdir, app => $self, ); $self->add_window($info); return $info; } ); has 'shutting_down' => ( is => 'rw', default => 0, isa => 'Bool', ); has 'user' => ( is => 'ro', default => $ENV{USER} ); sub BUILDARGS { my ($class, %options) = @_; my $self = {standalone => 1}; for (qw/standalone history notifier template user/) { if (exists $options{$_}) { $self->{$_} = $options{$_}; delete $options{$_}; } } $self->{config} = App::Alice::Config->new(%options); # some options get overwritten by the config # so merge options again $self->{config}->merge(\%options); return $self; } sub run { my $self = shift; # initialize lazy stuff $self->commands; $self->history; $self->info_window; $self->template; $self->httpd; $self->notifier; print STDERR "Location: http://".$self->config->http_address.":".$self->config->http_port."/\n" if $self->standalone; $self->add_irc_server($_, $self->config->servers->{$_}) for keys %{$self->config->servers}; if ($self->standalone) { $self->condvar(AnyEvent->condvar); my @sigs; for my $sig (qw/INT QUIT/) { my $w = AnyEvent->signal( signal => $sig, cb => sub {App::Alice::Signal->new(app => $self, type => $sig)} ); push @sigs, $w; } $self->condvar->recv; } } sub init_shutdown { my ($self, $cb, $msg) = @_; $self->{on_shutdown} = $cb; $self->shutting_down(1); $self->history(undef); $self->alert("Alice server is shutting down"); if ($self->connected_ircs) { print STDERR "\nDisconnecting, please wait\n" if $self->standalone; $_->init_shutdown($msg) for $self->connected_ircs; } else { print "\n"; $self->shutdown; return; } $self->{shutdown_timer} = AnyEvent->timer( after => 3, cb => sub{$self->shutdown} ); } sub shutdown { my $self = shift; $self->_ircs([]); $self->httpd->shutdown; $_->buffer->clear for $self->windows; delete $self->{shutdown_timer} if $self->{shutdown_timer}; $self->{on_shutdown}->() if $self->{on_shutdown}; $self->condvar->send if $self->condvar; } sub handle_command { my ($self, $command, $window) = @_; $self->commands->handle($command, $window); } sub reload_commands { my $self = shift; $self->commands->reload_handlers; } sub merge_config { my ($self, $new_config) = @_; for my $newserver (values %$new_config) { if (! exists $self->config->servers->{$newserver->{name}}) { $self->add_irc_server($newserver->{name}, $newserver); } for my $key (keys %$newserver) { $self->config->servers->{$newserver->{name}}{$key} = $newserver->{$key}; } } } sub tab_order { my ($self, $window_ids) = @_; my $order = []; for my $count (0 .. scalar @$window_ids - 1) { if (my $window = $self->get_window($window_ids->[$count])) { next unless $window->is_channel and $self->config->servers->{$window->irc->alias}; push @$order, $window->title; } } $self->config->order($order); $self->config->write; } sub with_messages { my ($self, $cb) = @_; $_->buffer->with_messages($cb) for $self->windows; } sub find_window { my ($self, $title, $connection) = @_; return $self->info_window if $title eq "info"; my $id = $self->_build_window_id($title, $connection->alias); if (my $window = $self->get_window($id)) { return $window; } } sub alert { my ($self, $message) = @_; return unless $message; $self->broadcast({ type => "action", event => "alert", body => $message, }); } sub create_window { my ($self, $title, $connection) = @_; my $id = $self->_build_window_id($title, $connection->alias); my $window = App::Alice::Window->new( title => $title, irc => $connection, assetdir => $self->config->assetdir, app => $self, ); $self->add_window($window); return $window; } sub _build_window_id { my ($self, $title, $session) = @_; md5_hex(encode_utf8(lc $self->user."-$title-$session")); } sub find_or_create_window { my ($self, $title, $connection) = @_; return $self->info_window if $title eq "info"; if (my $window = $self->find_window($title, $connection)) { return $window; } else { $self->create_window($title, $connection); } } sub sorted_windows { my $self = shift; my %o; if ($self->config->order) { %o = map {$self->config->order->[$_] => $_ + 2} 0 .. @{$self->config->order} - 1; } $o{info} = 1; sort { ($o{$a->title} || $a->sort_name) cmp ($o{$b->title} || $b->sort_name) } $self->windows; } sub close_window { my ($self, $window) = @_; $self->broadcast($window->close_action); $self->log(debug => "sending a request to close a tab: " . $window->title) if $self->httpd->stream_count; $self->remove_window($window->id) if $window->type ne "info"; } sub add_irc_server { my ($self, $name, $config) = @_; $self->config->servers->{$name} = $config; my $irc = App::Alice::IRC->new( app => $self, alias => $name ); $self->add_irc($irc); } sub reload_config { my ($self, $new_config) = @_; my %prev = map {$_ => $self->config->servers->{$_}{ircname} || ""} keys %{ $self->config->servers }; if ($new_config) { $self->config->merge($new_config); $self->config->write; } for my $network (keys %{$self->config->servers}) { my $config = $self->config->servers->{$network}; if (!$self->has_irc($network)) { $self->add_irc_server($network, $config); } else { my $irc = $self->get_irc($network); $config->{ircname} ||= ""; if ($config->{ircname} ne $prev{$network}) { $irc->update_realname($config->{ircname}); } $irc->config($config); } } for my $irc ($self->ircs) { if (!$self->config->servers->{$irc->alias}) { $self->remove_window($_->id) for $irc->windows; $irc->remove; } } } sub format_info { my ($self, $session, $body, %options) = @_; $self->info_window->format_message($session, $body, %options); } sub broadcast { my ($self, @messages) = @_; # add any highlighted messages to the log window push @messages, map {$self->info_window->copy_message($_)} grep {$_->{highlight}} @messages; $self->httpd->broadcast(@messages); if ($self->config->alerts and $self->notifier and ! $self->httpd->stream_count) { for my $message (@messages) { next if !$message->{window} or $message->{window}{type} eq "info"; $self->notifier->display($message) if $message->{highlight}; } } } sub render { my ($self, $template, @data) = @_; return $self->template->render_file("$template.html", $self, @data)->as_string; } sub is_highlight { my ($self, $own_nick, $body) = @_; for ((@{$self->config->highlights}, $own_nick)) { my $highlight = quotemeta($_); return 1 if $body =~ /\b$highlight\b/i; } return 0; } sub is_monospace_nick { my ($self, $nick) = @_; for (@{$self->config->monospace_nicks}) { return 1 if $_ eq $nick; } return 0; } sub is_ignore { my ($self, $nick) = @_; for ($self->config->ignores) { return 1 if $nick eq $_; } return 0; } sub add_ignore { my ($self, $nick) = @_; $self->config->add_ignore($nick); $self->config->write; } sub remove_ignore { my ($self, $nick) = @_; $self->config->ignore([ grep {$nick ne $_} $self->config->ignores ]); $self->config->write; } sub ignores { my $self = shift; return $self->config->ignores; } sub auth_enabled { my $self = shift; return ($self->config->auth and ref $self->config->auth eq 'HASH' and $self->config->auth->{user} and $self->config->auth->{pass}); } sub authenticate { my ($self, $user, $pass) = @_; $user ||= ""; $pass ||= ""; if ($self->auth_enabled) { return ($self->config->auth->{user} eq $user and $self->config->auth->{pass} eq $pass); } return 1; } sub static_url { my ($self, $file) = @_; return $self->config->static_prefix . $file; } __PACKAGE__->meta->make_immutable; 1; App-Alice-0.19/Changes0000644000175000017500000000651411437215213013440 0ustar leedoleedo0.19 Tue 31 Aug 2010 - Added a dark theme - Made preferences and connections windows into popup divs instead of real windows - Redis message store improvements - Improved debug output - Workarounds for weird android browser quirks - Much better mobile style - Improved reconnect behavior - Nick tab completion in private messages - Use browser's timezone for timestamps - Faster page loads 0.18 Tue 10 Aug 2010 - Shortcuts work on Linux and Windows - Alt-[0-9] shortcuts added to jump to tab - javascript bug fixes - Added link to grant Chrome permission for notifications - Fixed tab order saving 0.17 Fri 30 Jul 2010 - Improved URL regex (thanks gruber) - Added color chooser to wysiwyg - Smooth out wysiwyg interface - Bump IRC::Formatting::HTML requirement for improved invert/italic support - Better iPad display - Some changes to improve memory usage by a small amount - URL hash tags for each tab include network and tab name, instead of md5 of them 0.16 Fri 18 Jun 2010 - Added basic wysiwyg input field - Reload commands without restarting - Added /names -avatars to view a grid of nicks and avatars in the channel - Added support for REALNAME command (only works on hector irc server) 0.15 Thu 03 Jun 2010 - Added a help dialog - Added a preferences window that allows: - disabling inline images - disabling avatars - disabling alerts - custom highlight list - Put current tab in the URL hash - refocuses current tab on reload - Style highlighted lines w/ a red stripe - Various CSS and JS fixes - Use random port in tests - Added /window command - Better iPad/iPod display 0.14 Sun 18 Apr 2010 - Fixed broken tests 0.13 Sat 10 Apr 2010 - Use cookie based auth - Don't break on perl 5.8 - Added Redis message store - Moved most display logic to CSS 0.12 Thu 25 Mar 2010 - Switched to Twiggy from AE:HTTPD - Many HTML/CSS tweaks - Sends buffered messages with initial page load (loads much faster) - Improved iPhone stylesheet 0.11 Sat 13 Mar 2010 - Improved unicode support - Faster shutdown process - Make / an alias for /view - Show context for log search result on click 0.10 Mon 22 Feb 2010 - Added NullLogger to fix test failures 0.09 Sun 21 Feb 2010 - Added back log.db template file 0.06 Tue 06 Dec 2009 - Fixed bug with initial tab focus 0.05 Tue 06 Dec 2009 - Removed Digest::CRC dependency - Added /ignore /unignore and /ignores - UI additions - Switched to Any::Moose 0.04 Tue 24 Nov 2009 - Removed MooseX::ClassAttribute dependency - Added reconnect retry counter 0.03 Sat 21 Nov 2009 - Fixed a number of issues with reconnecting - Fixed issue adding new server from config window 0.02 Fri 20 Nov 2009 - Switched to AnyEvent and Text::MicroTemplate - Removed other dependencies 0.01 Wed 19 Aug 2009 - Initial release App-Alice-0.19/t/0000755000175000017500000000000011437215373012411 5ustar leedoleedoApp-Alice-0.19/t/pod.t0000644000175000017500000000021711412170767013360 0ustar leedoleedouse Test::More; eval "use Test::Pod 1.14"; if ( $@ ) { plan skip_all => "Test::Pod 1.14 required for testing POD" } all_pod_files_ok(); App-Alice-0.19/t/alice/0000755000175000017500000000000011437215373013466 5ustar leedoleedoApp-Alice-0.19/t/alice/test_config0000644000175000017500000000103411437215367015716 0ustar leedoleedo{ 'quitmsg' => 'alice.', 'static_prefix' => '/static/', 'ignore' => [], 'highlights' => [], 'auth' => {}, 'message_store' => 'Memory', 'order' => [], 'debug' => 0, 'monospace_nicks' => [ 'Shaniqua' ], 'avatars' => 'show', 'address' => '127.0.0.1', 'alerts' => 'show', 'images' => 'show', 'timeformat' => '24', 'style' => 'default', 'port' => 10398, 'servers' => { 'test' => { 'nick' => 'tester', 'autoconnect' => 0, 'port' => 6667, 'host' => 'not.real.server' } } } App-Alice-0.19/t/02-app.t0000644000175000017500000000323411421100170013554 0ustar leedoleedouse Test::More; use App::Alice; use App::Alice::Test::NullHistory; use Test::TCP; my $history = App::Alice::Test::NullHistory->new; my $app = App::Alice->new( history => $history, standalone => 0, path => 't/alice', file => "test_config", port => empty_port(), ); $app->add_irc_server("test", { nick => "tester", host => "not.real.server", port => 6667, autoconnect => 0, }); # connections ok $app->has_irc("test"), "add connection"; my $irc = $app->get_irc("test"); is_deeply [$app->ircs], [$irc], "connection list"; # windows my $info = $app->info_window; ok $info, "info window"; my $window = $app->create_window("test-window", $irc); ok $window, "create window"; my $window_id = $app->_build_window_id("test-window", "test"); ok $app->has_window($window_id), "window exists"; ok $app->find_window("test-window", $irc), "find window by name"; ok ref $app->get_window($window_id) eq "App::Alice::Window", "get window"; is_deeply [$app->sorted_windows], [$info, $window], "window list"; is_deeply $app->find_or_create_window("test-window", $irc), $window, "find or create existing window"; my $window2 = $app->find_or_create_window("test-window2", $irc); ok $app->find_window("test-window2", $irc), "find or create non-existent window"; $app->remove_window($app->_build_window_id("test-window2", "test")); $app->close_window($window); ok !$app->has_window($window_id), "close window"; # ignores $app->add_ignore("jerk"); ok $app->is_ignore("jerk"), "add ignore"; is_deeply [$app->ignores], ["jerk"], "ignore list"; $app->remove_ignore("jerk"); ok !$app->is_ignore("jerk"), "remove ignore"; is_deeply [$app->ignores], [], "ignore list post remove"; done_testing(); App-Alice-0.19/t/01-use.t0000644000175000017500000000006111366325415013605 0ustar leedoleedouse Test::More tests => 1; use_ok 'App::Alice'; App-Alice-0.19/t/03-window.t0000644000175000017500000000173211403451216014317 0ustar leedoleedouse Test::More; use App::Alice; use App::Alice::Test::NullHistory; use Test::TCP; my $history = App::Alice::Test::NullHistory->new; my $app = App::Alice->new( history => $history, standalone => 0, path => 't/alice', file => "test_config", port => empty_port(), ); $app->add_irc_server("test", { nick => "tester", host => "not.real.server", port => 6667, autoconnect => 0, }); my $irc = $app->get_irc("test"); my $window = $app->create_window("test-window", $irc); ok $window->type eq "privmsg", "correct window type for privmsg"; ok !$window->is_channel, "is_channel false for privmsg"; $app->remove_window($window->id); $window = $app->create_window("#test-window", $irc); ok $window->type eq "channel", "correct window type for channel"; ok $window->is_channel, "is_channel for channel"; is $window->title, "#test-window", "window title"; is $window->nick, "tester", "nick"; is $window->topic->{string}, "no topic set", "default window topic"; done_testing(); App-Alice-0.19/t/04-irc.t0000644000175000017500000000462111403451216013566 0ustar leedoleedouse Test::More; use App::Alice; use App::Alice::Test::MockIRC; use App::Alice::Test::NullHistory; use Test::TCP; my $history = App::Alice::Test::NullHistory->new; my $app = App::Alice->new( history => $history, standalone => 0, path => 't/alice', file => "test_config", port => empty_port(), ); my $cl = App::Alice::Test::MockIRC->new(nick => "tester"); $app->config->servers->{"test"} = { host => "not.real.server", port => 6667, autoconnect => 1, channels => ["#test"], on_connect => ["JOIN #test2"], }; my $irc = App::Alice::IRC->new( alias => "test", app => $app, cl => $cl, ); $app->add_irc("test", $irc); # joining channels ok $irc->is_connected, "connect"; ok my $window = $app->find_window("#test", $irc), "auto-join channel"; ok $app->find_window("#test2", $irc), "on_connect join command"; # nicks is $irc->nick, "tester", "nick set"; ok $irc->includes_nick("test"), "existing nick in channel"; ok exists $irc->get_nick_info("test")->{channels}{'#test'}, "existing nick info set"; $cl->send_cl(":nick!user\@host JOIN #test"); ok $irc->includes_nick("nick"), "nick added after join"; ok exists $irc->get_nick_info("nick")->{channels}{'#test'}, "new nick info set"; $cl->send_cl(":nick!user\@host NICK nick2"); ok $irc->includes_nick("nick2"), "nick change"; ok !$irc->includes_nick("nick"), "old nick removed after nick change"; $cl->send_cl(":nick!user\@host PART #test"); ok !$irc->includes_nick("nick"), "nick gone after part"; # topic is $window->topic->{string}, "no topic set", "default initial topic"; $cl->send_srv(TOPIC => "#test", "updated topic"); is $window->topic->{string}, "updated topic", "self topic change string"; is $window->topic->{author}, "tester", "self topic change author"; $cl->send_cl(":nick!user\@host TOPIC #test :another topic update"); is $window->topic->{string}, "another topic update", "external topic change string"; is $window->topic->{author}, "nick", "external topic change author"; # part channel $cl->send_srv(PART => "#test"); ok !$app->find_window("#test", $irc), "part removes window"; # messages $cl->send_cl(":nick!user\@host PRIVMSG tester :hi"); ok $app->find_window("nick", $irc), "private message"; $cl->send_cl(":nick!user\@host PRIVMSG #test3 :hi"); ok !$app->find_window("#test3", $irc), "msg to unjoined channel doesn't create window"; # disconnect $cl->disconnect; ok !$irc->is_connected, "disconnect"; undef $app; undef $cl; done_testing(); App-Alice-0.19/bin/0000755000175000017500000000000011437215373012716 5ustar leedoleedoApp-Alice-0.19/bin/alice0000755000175000017500000000101211412170471013703 0ustar leedoleedo#!/usr/bin/perl use strict; use warnings; use FindBin; BEGIN { # running from source dir if (-e "$FindBin::Bin/../lib/App/Alice") { require lib; lib->import("$FindBin::Bin/../lib"); # extlib present if (-e "$FindBin::Bin/../extlib") { lib->import("$FindBin::Bin/../extlib/lib/perl5"); require local::lib; local::lib->import("$FindBin::Bin/../extlib"); } } require App::Alice; } $0 = "aliced\0"; binmode(STDERR, ":utf8"); binmode(STDOUT, ":utf8"); App::Alice->new->run; App-Alice-0.19/inc/0000755000175000017500000000000011437215373012717 5ustar leedoleedoApp-Alice-0.19/inc/Module/0000755000175000017500000000000011437215373014144 5ustar leedoleedoApp-Alice-0.19/inc/Module/Install.pm0000644000175000017500000003013511437215362016110 0ustar leedoleedo#line 1 package Module::Install; # For any maintainers: # The load order for Module::Install is a bit magic. # It goes something like this... # # IF ( host has Module::Install installed, creating author mode ) { # 1. Makefile.PL calls "use inc::Module::Install" # 2. $INC{inc/Module/Install.pm} set to installed version of inc::Module::Install # 3. The installed version of inc::Module::Install loads # 4. inc::Module::Install calls "require Module::Install" # 5. The ./inc/ version of Module::Install loads # } ELSE { # 1. Makefile.PL calls "use inc::Module::Install" # 2. $INC{inc/Module/Install.pm} set to ./inc/ version of Module::Install # 3. The ./inc/ version of Module::Install loads # } use 5.005; use strict 'vars'; use Cwd (); use File::Find (); use File::Path (); use vars qw{$VERSION $MAIN}; BEGIN { # All Module::Install core packages now require synchronised versions. # This will be used to ensure we don't accidentally load old or # different versions of modules. # This is not enforced yet, but will be some time in the next few # releases once we can make sure it won't clash with custom # Module::Install extensions. $VERSION = '1.00'; # Storage for the pseudo-singleton $MAIN = undef; *inc::Module::Install::VERSION = *VERSION; @inc::Module::Install::ISA = __PACKAGE__; } sub import { my $class = shift; my $self = $class->new(@_); my $who = $self->_caller; #------------------------------------------------------------- # all of the following checks should be included in import(), # to allow "eval 'require Module::Install; 1' to test # installation of Module::Install. (RT #51267) #------------------------------------------------------------- # Whether or not inc::Module::Install is actually loaded, the # $INC{inc/Module/Install.pm} is what will still get set as long as # the caller loaded module this in the documented manner. # If not set, the caller may NOT have loaded the bundled version, and thus # they may not have a MI version that works with the Makefile.PL. This would # result in false errors or unexpected behaviour. And we don't want that. my $file = join( '/', 'inc', split /::/, __PACKAGE__ ) . '.pm'; unless ( $INC{$file} ) { die <<"END_DIE" } Please invoke ${\__PACKAGE__} with: use inc::${\__PACKAGE__}; not: use ${\__PACKAGE__}; END_DIE # This reportedly fixes a rare Win32 UTC file time issue, but # as this is a non-cross-platform XS module not in the core, # we shouldn't really depend on it. See RT #24194 for detail. # (Also, this module only supports Perl 5.6 and above). eval "use Win32::UTCFileTime" if $^O eq 'MSWin32' && $] >= 5.006; # If the script that is loading Module::Install is from the future, # then make will detect this and cause it to re-run over and over # again. This is bad. Rather than taking action to touch it (which # is unreliable on some platforms and requires write permissions) # for now we should catch this and refuse to run. if ( -f $0 ) { my $s = (stat($0))[9]; # If the modification time is only slightly in the future, # sleep briefly to remove the problem. my $a = $s - time; if ( $a > 0 and $a < 5 ) { sleep 5 } # Too far in the future, throw an error. my $t = time; if ( $s > $t ) { die <<"END_DIE" } Your installer $0 has a modification time in the future ($s > $t). This is known to create infinite loops in make. Please correct this, then run $0 again. END_DIE } # Build.PL was formerly supported, but no longer is due to excessive # difficulty in implementing every single feature twice. if ( $0 =~ /Build.PL$/i ) { die <<"END_DIE" } Module::Install no longer supports Build.PL. It was impossible to maintain duel backends, and has been deprecated. Please remove all Build.PL files and only use the Makefile.PL installer. END_DIE #------------------------------------------------------------- # To save some more typing in Module::Install installers, every... # use inc::Module::Install # ...also acts as an implicit use strict. $^H |= strict::bits(qw(refs subs vars)); #------------------------------------------------------------- unless ( -f $self->{file} ) { foreach my $key (keys %INC) { delete $INC{$key} if $key =~ /Module\/Install/; } local $^W; require "$self->{path}/$self->{dispatch}.pm"; File::Path::mkpath("$self->{prefix}/$self->{author}"); $self->{admin} = "$self->{name}::$self->{dispatch}"->new( _top => $self ); $self->{admin}->init; @_ = ($class, _self => $self); goto &{"$self->{name}::import"}; } local $^W; *{"${who}::AUTOLOAD"} = $self->autoload; $self->preload; # Unregister loader and worker packages so subdirs can use them again delete $INC{'inc/Module/Install.pm'}; delete $INC{'Module/Install.pm'}; # Save to the singleton $MAIN = $self; return 1; } sub autoload { my $self = shift; my $who = $self->_caller; my $cwd = Cwd::cwd(); my $sym = "${who}::AUTOLOAD"; $sym->{$cwd} = sub { my $pwd = Cwd::cwd(); if ( my $code = $sym->{$pwd} ) { # Delegate back to parent dirs goto &$code unless $cwd eq $pwd; } unless ($$sym =~ s/([^:]+)$//) { # XXX: it looks like we can't retrieve the missing function # via $$sym (usually $main::AUTOLOAD) in this case. # I'm still wondering if we should slurp Makefile.PL to # get some context or not ... my ($package, $file, $line) = caller; die <<"EOT"; Unknown function is found at $file line $line. Execution of $file aborted due to runtime errors. If you're a contributor to a project, you may need to install some Module::Install extensions from CPAN (or other repository). If you're a user of a module, please contact the author. EOT } my $method = $1; if ( uc($method) eq $method ) { # Do nothing return; } elsif ( $method =~ /^_/ and $self->can($method) ) { # Dispatch to the root M:I class return $self->$method(@_); } # Dispatch to the appropriate plugin unshift @_, ( $self, $1 ); goto &{$self->can('call')}; }; } sub preload { my $self = shift; unless ( $self->{extensions} ) { $self->load_extensions( "$self->{prefix}/$self->{path}", $self ); } my @exts = @{$self->{extensions}}; unless ( @exts ) { @exts = $self->{admin}->load_all_extensions; } my %seen; foreach my $obj ( @exts ) { while (my ($method, $glob) = each %{ref($obj) . '::'}) { next unless $obj->can($method); next if $method =~ /^_/; next if $method eq uc($method); $seen{$method}++; } } my $who = $self->_caller; foreach my $name ( sort keys %seen ) { local $^W; *{"${who}::$name"} = sub { ${"${who}::AUTOLOAD"} = "${who}::$name"; goto &{"${who}::AUTOLOAD"}; }; } } sub new { my ($class, %args) = @_; delete $INC{'FindBin.pm'}; { # to suppress the redefine warning local $SIG{__WARN__} = sub {}; require FindBin; } # ignore the prefix on extension modules built from top level. my $base_path = Cwd::abs_path($FindBin::Bin); unless ( Cwd::abs_path(Cwd::cwd()) eq $base_path ) { delete $args{prefix}; } return $args{_self} if $args{_self}; $args{dispatch} ||= 'Admin'; $args{prefix} ||= 'inc'; $args{author} ||= ($^O eq 'VMS' ? '_author' : '.author'); $args{bundle} ||= 'inc/BUNDLES'; $args{base} ||= $base_path; $class =~ s/^\Q$args{prefix}\E:://; $args{name} ||= $class; $args{version} ||= $class->VERSION; unless ( $args{path} ) { $args{path} = $args{name}; $args{path} =~ s!::!/!g; } $args{file} ||= "$args{base}/$args{prefix}/$args{path}.pm"; $args{wrote} = 0; bless( \%args, $class ); } sub call { my ($self, $method) = @_; my $obj = $self->load($method) or return; splice(@_, 0, 2, $obj); goto &{$obj->can($method)}; } sub load { my ($self, $method) = @_; $self->load_extensions( "$self->{prefix}/$self->{path}", $self ) unless $self->{extensions}; foreach my $obj (@{$self->{extensions}}) { return $obj if $obj->can($method); } my $admin = $self->{admin} or die <<"END_DIE"; The '$method' method does not exist in the '$self->{prefix}' path! Please remove the '$self->{prefix}' directory and run $0 again to load it. END_DIE my $obj = $admin->load($method, 1); push @{$self->{extensions}}, $obj; $obj; } sub load_extensions { my ($self, $path, $top) = @_; my $should_reload = 0; unless ( grep { ! ref $_ and lc $_ eq lc $self->{prefix} } @INC ) { unshift @INC, $self->{prefix}; $should_reload = 1; } foreach my $rv ( $self->find_extensions($path) ) { my ($file, $pkg) = @{$rv}; next if $self->{pathnames}{$pkg}; local $@; my $new = eval { local $^W; require $file; $pkg->can('new') }; unless ( $new ) { warn $@ if $@; next; } $self->{pathnames}{$pkg} = $should_reload ? delete $INC{$file} : $INC{$file}; push @{$self->{extensions}}, &{$new}($pkg, _top => $top ); } $self->{extensions} ||= []; } sub find_extensions { my ($self, $path) = @_; my @found; File::Find::find( sub { my $file = $File::Find::name; return unless $file =~ m!^\Q$path\E/(.+)\.pm\Z!is; my $subpath = $1; return if lc($subpath) eq lc($self->{dispatch}); $file = "$self->{path}/$subpath.pm"; my $pkg = "$self->{name}::$subpath"; $pkg =~ s!/!::!g; # If we have a mixed-case package name, assume case has been preserved # correctly. Otherwise, root through the file to locate the case-preserved # version of the package name. if ( $subpath eq lc($subpath) || $subpath eq uc($subpath) ) { my $content = Module::Install::_read($subpath . '.pm'); my $in_pod = 0; foreach ( split //, $content ) { $in_pod = 1 if /^=\w/; $in_pod = 0 if /^=cut/; next if ($in_pod || /^=cut/); # skip pod text next if /^\s*#/; # and comments if ( m/^\s*package\s+($pkg)\s*;/i ) { $pkg = $1; last; } } } push @found, [ $file, $pkg ]; }, $path ) if -d $path; @found; } ##################################################################### # Common Utility Functions sub _caller { my $depth = 0; my $call = caller($depth); while ( $call eq __PACKAGE__ ) { $depth++; $call = caller($depth); } return $call; } # Done in evals to avoid confusing Perl::MinimumVersion eval( $] >= 5.006 ? <<'END_NEW' : <<'END_OLD' ); die $@ if $@; sub _read { local *FH; open( FH, '<', $_[0] ) or die "open($_[0]): $!"; my $string = do { local $/; }; close FH or die "close($_[0]): $!"; return $string; } END_NEW sub _read { local *FH; open( FH, "< $_[0]" ) or die "open($_[0]): $!"; my $string = do { local $/; }; close FH or die "close($_[0]): $!"; return $string; } END_OLD sub _readperl { my $string = Module::Install::_read($_[0]); $string =~ s/(?:\015{1,2}\012|\015|\012)/\n/sg; $string =~ s/(\n)\n*__(?:DATA|END)__\b.*\z/$1/s; $string =~ s/\n\n=\w+.+?\n\n=cut\b.+?\n+/\n\n/sg; return $string; } sub _readpod { my $string = Module::Install::_read($_[0]); $string =~ s/(?:\015{1,2}\012|\015|\012)/\n/sg; return $string if $_[0] =~ /\.pod\z/; $string =~ s/(^|\n=cut\b.+?\n+)[^=\s].+?\n(\n=\w+|\z)/$1$2/sg; $string =~ s/\n*=pod\b[^\n]*\n+/\n\n/sg; $string =~ s/\n*=cut\b[^\n]*\n+/\n\n/sg; $string =~ s/^\n+//s; return $string; } # Done in evals to avoid confusing Perl::MinimumVersion eval( $] >= 5.006 ? <<'END_NEW' : <<'END_OLD' ); die $@ if $@; sub _write { local *FH; open( FH, '>', $_[0] ) or die "open($_[0]): $!"; foreach ( 1 .. $#_ ) { print FH $_[$_] or die "print($_[0]): $!"; } close FH or die "close($_[0]): $!"; } END_NEW sub _write { local *FH; open( FH, "> $_[0]" ) or die "open($_[0]): $!"; foreach ( 1 .. $#_ ) { print FH $_[$_] or die "print($_[0]): $!"; } close FH or die "close($_[0]): $!"; } END_OLD # _version is for processing module versions (eg, 1.03_05) not # Perl versions (eg, 5.8.1). sub _version ($) { my $s = shift || 0; my $d =()= $s =~ /(\.)/g; if ( $d >= 2 ) { # Normalise multipart versions $s =~ s/(\.)(\d{1,3})/sprintf("$1%03d",$2)/eg; } $s =~ s/^(\d+)\.?//; my $l = $1 || 0; my @v = map { $_ . '0' x (3 - length $_) } $s =~ /(\d{1,3})\D?/g; $l = $l . '.' . join '', @v if @v; return $l + 0; } sub _cmp ($$) { _version($_[0]) <=> _version($_[1]); } # Cloned from Params::Util::_CLASS sub _CLASS ($) { ( defined $_[0] and ! ref $_[0] and $_[0] =~ m/^[^\W\d]\w*(?:::\w+)*\z/s ) ? $_[0] : undef; } 1; # Copyright 2008 - 2010 Adam Kennedy. App-Alice-0.19/inc/Module/Install/0000755000175000017500000000000011437215373015552 5ustar leedoleedoApp-Alice-0.19/inc/Module/Install/WriteAll.pm0000644000175000017500000000237611437215363017642 0ustar leedoleedo#line 1 package Module::Install::WriteAll; use strict; use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { $VERSION = '1.00'; @ISA = qw{Module::Install::Base}; $ISCORE = 1; } sub WriteAll { my $self = shift; my %args = ( meta => 1, sign => 0, inline => 0, check_nmake => 1, @_, ); $self->sign(1) if $args{sign}; $self->admin->WriteAll(%args) if $self->is_admin; $self->check_nmake if $args{check_nmake}; unless ( $self->makemaker_args->{PL_FILES} ) { # XXX: This still may be a bit over-defensive... unless ($self->makemaker(6.25)) { $self->makemaker_args( PL_FILES => {} ) if -f 'Build.PL'; } } # Until ExtUtils::MakeMaker support MYMETA.yml, make sure # we clean it up properly ourself. $self->realclean_files('MYMETA.yml'); if ( $args{inline} ) { $self->Inline->write; } else { $self->Makefile->write; } # The Makefile write process adds a couple of dependencies, # so write the META.yml files after the Makefile. if ( $args{meta} ) { $self->Meta->write; } # Experimental support for MYMETA if ( $ENV{X_MYMETA} ) { if ( $ENV{X_MYMETA} eq 'JSON' ) { $self->Meta->write_mymeta_json; } else { $self->Meta->write_mymeta_yaml; } } return 1; } 1; App-Alice-0.19/inc/Module/Install/Can.pm0000644000175000017500000000333311437215363016612 0ustar leedoleedo#line 1 package Module::Install::Can; use strict; use Config (); use File::Spec (); use ExtUtils::MakeMaker (); use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { $VERSION = '1.00'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } # check if we can load some module ### Upgrade this to not have to load the module if possible sub can_use { my ($self, $mod, $ver) = @_; $mod =~ s{::|\\}{/}g; $mod .= '.pm' unless $mod =~ /\.pm$/i; my $pkg = $mod; $pkg =~ s{/}{::}g; $pkg =~ s{\.pm$}{}i; local $@; eval { require $mod; $pkg->VERSION($ver || 0); 1 }; } # check if we can run some command sub can_run { my ($self, $cmd) = @_; my $_cmd = $cmd; return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd)); for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') { next if $dir eq ''; my $abs = File::Spec->catfile($dir, $_[1]); return $abs if (-x $abs or $abs = MM->maybe_command($abs)); } return; } # can we locate a (the) C compiler sub can_cc { my $self = shift; my @chunks = split(/ /, $Config::Config{cc}) or return; # $Config{cc} may contain args; try to find out the program part while (@chunks) { return $self->can_run("@chunks") || (pop(@chunks), next); } return; } # Fix Cygwin bug on maybe_command(); if ( $^O eq 'cygwin' ) { require ExtUtils::MM_Cygwin; require ExtUtils::MM_Win32; if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) { *ExtUtils::MM_Cygwin::maybe_command = sub { my ($self, $file) = @_; if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) { ExtUtils::MM_Win32->maybe_command($file); } else { ExtUtils::MM_Unix->maybe_command($file); } } } } 1; __END__ #line 156 App-Alice-0.19/inc/Module/Install/Scripts.pm0000644000175000017500000000101111437215363017527 0ustar leedoleedo#line 1 package Module::Install::Scripts; use strict 'vars'; use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { $VERSION = '1.00'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } sub install_script { my $self = shift; my $args = $self->makemaker_args; my $exe = $args->{EXE_FILES} ||= []; foreach ( @_ ) { if ( -f $_ ) { push @$exe, $_; } elsif ( -d 'script' and -f "script/$_" ) { push @$exe, "script/$_"; } else { die("Cannot find script '$_'"); } } } 1; App-Alice-0.19/inc/Module/Install/Fetch.pm0000644000175000017500000000462711437215363017151 0ustar leedoleedo#line 1 package Module::Install::Fetch; use strict; use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { $VERSION = '1.00'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } sub get_file { my ($self, %args) = @_; my ($scheme, $host, $path, $file) = $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return; if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) { $args{url} = $args{ftp_url} or (warn("LWP support unavailable!\n"), return); ($scheme, $host, $path, $file) = $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return; } $|++; print "Fetching '$file' from $host... "; unless (eval { require Socket; Socket::inet_aton($host) }) { warn "'$host' resolve failed!\n"; return; } return unless $scheme eq 'ftp' or $scheme eq 'http'; require Cwd; my $dir = Cwd::getcwd(); chdir $args{local_dir} or return if exists $args{local_dir}; if (eval { require LWP::Simple; 1 }) { LWP::Simple::mirror($args{url}, $file); } elsif (eval { require Net::FTP; 1 }) { eval { # use Net::FTP to get past firewall my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600); $ftp->login("anonymous", 'anonymous@example.com'); $ftp->cwd($path); $ftp->binary; $ftp->get($file) or (warn("$!\n"), return); $ftp->quit; } } elsif (my $ftp = $self->can_run('ftp')) { eval { # no Net::FTP, fallback to ftp.exe require FileHandle; my $fh = FileHandle->new; local $SIG{CHLD} = 'IGNORE'; unless ($fh->open("|$ftp -n")) { warn "Couldn't open ftp: $!\n"; chdir $dir; return; } my @dialog = split(/\n/, <<"END_FTP"); open $host user anonymous anonymous\@example.com cd $path binary get $file $file quit END_FTP foreach (@dialog) { $fh->print("$_\n") } $fh->close; } } else { warn "No working 'ftp' program available!\n"; chdir $dir; return; } unless (-f $file) { warn "Fetching failed: $@\n"; chdir $dir; return; } return if exists $args{size} and -s $file != $args{size}; system($args{run}) if exists $args{run}; unlink($file) if $args{remove}; print(((!exists $args{check_for} or -e $args{check_for}) ? "done!" : "failed! ($!)"), "\n"); chdir $dir; return !$?; } 1; App-Alice-0.19/inc/Module/Install/Base.pm0000644000175000017500000000214711437215363016765 0ustar leedoleedo#line 1 package Module::Install::Base; use strict 'vars'; use vars qw{$VERSION}; BEGIN { $VERSION = '1.00'; } # Suspend handler for "redefined" warnings BEGIN { my $w = $SIG{__WARN__}; $SIG{__WARN__} = sub { $w }; } #line 42 sub new { my $class = shift; unless ( defined &{"${class}::call"} ) { *{"${class}::call"} = sub { shift->_top->call(@_) }; } unless ( defined &{"${class}::load"} ) { *{"${class}::load"} = sub { shift->_top->load(@_) }; } bless { @_ }, $class; } #line 61 sub AUTOLOAD { local $@; my $func = eval { shift->_top->autoload } or return; goto &$func; } #line 75 sub _top { $_[0]->{_top}; } #line 90 sub admin { $_[0]->_top->{admin} or Module::Install::Base::FakeAdmin->new; } #line 106 sub is_admin { ! $_[0]->admin->isa('Module::Install::Base::FakeAdmin'); } sub DESTROY {} package Module::Install::Base::FakeAdmin; use vars qw{$VERSION}; BEGIN { $VERSION = $Module::Install::Base::VERSION; } my $fake; sub new { $fake ||= bless(\@_, $_[0]); } sub AUTOLOAD {} sub DESTROY {} # Restore warning handler BEGIN { $SIG{__WARN__} = $SIG{__WARN__}->(); } 1; #line 159 App-Alice-0.19/inc/Module/Install/Win32.pm0000644000175000017500000000340311437215363017011 0ustar leedoleedo#line 1 package Module::Install::Win32; use strict; use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { $VERSION = '1.00'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } # determine if the user needs nmake, and download it if needed sub check_nmake { my $self = shift; $self->load('can_run'); $self->load('get_file'); require Config; return unless ( $^O eq 'MSWin32' and $Config::Config{make} and $Config::Config{make} =~ /^nmake\b/i and ! $self->can_run('nmake') ); print "The required 'nmake' executable not found, fetching it...\n"; require File::Basename; my $rv = $self->get_file( url => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe', ftp_url => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe', local_dir => File::Basename::dirname($^X), size => 51928, run => 'Nmake15.exe /o > nul', check_for => 'Nmake.exe', remove => 1, ); die <<'END_MESSAGE' unless $rv; ------------------------------------------------------------------------------- Since you are using Microsoft Windows, you will need the 'nmake' utility before installation. It's available at: http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe or ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe Please download the file manually, save it to a directory in %PATH% (e.g. C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to that directory, and run "Nmake15.exe" from there; that will create the 'nmake.exe' file needed by this module. You may then resume the installation process described in README. ------------------------------------------------------------------------------- END_MESSAGE } 1; App-Alice-0.19/inc/Module/Install/Share.pm0000644000175000017500000000463311437215363017157 0ustar leedoleedo#line 1 package Module::Install::Share; use strict; use Module::Install::Base (); use File::Find (); use ExtUtils::Manifest (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { $VERSION = '1.00'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } sub install_share { my $self = shift; my $dir = @_ ? pop : 'share'; my $type = @_ ? shift : 'dist'; unless ( defined $type and $type eq 'module' or $type eq 'dist' ) { die "Illegal or invalid share dir type '$type'"; } unless ( defined $dir and -d $dir ) { require Carp; Carp::croak("Illegal or missing directory install_share param"); } # Split by type my $S = ($^O eq 'MSWin32') ? "\\" : "\/"; my $root; if ( $type eq 'dist' ) { die "Too many parameters to install_share" if @_; # Set up the install $root = "\$(INST_LIB)${S}auto${S}share${S}dist${S}\$(DISTNAME)"; } else { my $module = Module::Install::_CLASS($_[0]); unless ( defined $module ) { die "Missing or invalid module name '$_[0]'"; } $module =~ s/::/-/g; $root = "\$(INST_LIB)${S}auto${S}share${S}module${S}$module"; } my $manifest = -r 'MANIFEST' ? ExtUtils::Manifest::maniread() : undef; my $skip_checker = $ExtUtils::Manifest::VERSION >= 1.54 ? ExtUtils::Manifest::maniskip() : ExtUtils::Manifest::_maniskip(); my $postamble = ''; my $perm_dir = eval($ExtUtils::MakeMaker::VERSION) >= 6.52 ? '$(PERM_DIR)' : 755; File::Find::find({ no_chdir => 1, wanted => sub { my $path = File::Spec->abs2rel($_, $dir); if (-d $_) { return if $skip_checker->($File::Find::name); $postamble .=<<"END"; \t\$(NOECHO) \$(MKPATH) "$root${S}$path" \t\$(NOECHO) \$(CHMOD) $perm_dir "$root${S}$path" END } else { return if ref $manifest && !exists $manifest->{$File::Find::name}; return if $skip_checker->($File::Find::name); $postamble .=<<"END"; \t\$(NOECHO) \$(CP) "$dir${S}$path" "$root${S}$path" END } }, }, $dir); # Set up the install $self->postamble(<<"END_MAKEFILE"); config :: $postamble END_MAKEFILE # The above appears to behave incorrectly when used with old versions # of ExtUtils::Install (known-bad on RHEL 3, with 5.8.0) # So when we need to install a share directory, make sure we add a # dependency on a moderately new version of ExtUtils::MakeMaker. $self->build_requires( 'ExtUtils::MakeMaker' => '6.11' ); # 99% of the time we don't want to index a shared dir $self->no_index( directory => $dir ); } 1; __END__ #line 154 App-Alice-0.19/inc/Module/Install/Makefile.pm0000644000175000017500000002703211437215363017630 0ustar leedoleedo#line 1 package Module::Install::Makefile; use strict 'vars'; use ExtUtils::MakeMaker (); use Module::Install::Base (); use Fcntl qw/:flock :seek/; use vars qw{$VERSION @ISA $ISCORE}; BEGIN { $VERSION = '1.00'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } sub Makefile { $_[0] } my %seen = (); sub prompt { shift; # Infinite loop protection my @c = caller(); if ( ++$seen{"$c[1]|$c[2]|$_[0]"} > 3 ) { die "Caught an potential prompt infinite loop ($c[1]|$c[2]|$_[0])"; } # In automated testing or non-interactive session, always use defaults if ( ($ENV{AUTOMATED_TESTING} or -! -t STDIN) and ! $ENV{PERL_MM_USE_DEFAULT} ) { local $ENV{PERL_MM_USE_DEFAULT} = 1; goto &ExtUtils::MakeMaker::prompt; } else { goto &ExtUtils::MakeMaker::prompt; } } # Store a cleaned up version of the MakeMaker version, # since we need to behave differently in a variety of # ways based on the MM version. my $makemaker = eval $ExtUtils::MakeMaker::VERSION; # If we are passed a param, do a "newer than" comparison. # Otherwise, just return the MakeMaker version. sub makemaker { ( @_ < 2 or $makemaker >= eval($_[1]) ) ? $makemaker : 0 } # Ripped from ExtUtils::MakeMaker 6.56, and slightly modified # as we only need to know here whether the attribute is an array # or a hash or something else (which may or may not be appendable). my %makemaker_argtype = ( C => 'ARRAY', CONFIG => 'ARRAY', # CONFIGURE => 'CODE', # ignore DIR => 'ARRAY', DL_FUNCS => 'HASH', DL_VARS => 'ARRAY', EXCLUDE_EXT => 'ARRAY', EXE_FILES => 'ARRAY', FUNCLIST => 'ARRAY', H => 'ARRAY', IMPORTS => 'HASH', INCLUDE_EXT => 'ARRAY', LIBS => 'ARRAY', # ignore '' MAN1PODS => 'HASH', MAN3PODS => 'HASH', META_ADD => 'HASH', META_MERGE => 'HASH', PL_FILES => 'HASH', PM => 'HASH', PMLIBDIRS => 'ARRAY', PMLIBPARENTDIRS => 'ARRAY', PREREQ_PM => 'HASH', CONFIGURE_REQUIRES => 'HASH', SKIP => 'ARRAY', TYPEMAPS => 'ARRAY', XS => 'HASH', # VERSION => ['version',''], # ignore # _KEEP_AFTER_FLUSH => '', clean => 'HASH', depend => 'HASH', dist => 'HASH', dynamic_lib=> 'HASH', linkext => 'HASH', macro => 'HASH', postamble => 'HASH', realclean => 'HASH', test => 'HASH', tool_autosplit => 'HASH', # special cases where you can use makemaker_append CCFLAGS => 'APPENDABLE', DEFINE => 'APPENDABLE', INC => 'APPENDABLE', LDDLFLAGS => 'APPENDABLE', LDFROM => 'APPENDABLE', ); sub makemaker_args { my ($self, %new_args) = @_; my $args = ( $self->{makemaker_args} ||= {} ); foreach my $key (keys %new_args) { if ($makemaker_argtype{$key}) { if ($makemaker_argtype{$key} eq 'ARRAY') { $args->{$key} = [] unless defined $args->{$key}; unless (ref $args->{$key} eq 'ARRAY') { $args->{$key} = [$args->{$key}] } push @{$args->{$key}}, ref $new_args{$key} eq 'ARRAY' ? @{$new_args{$key}} : $new_args{$key}; } elsif ($makemaker_argtype{$key} eq 'HASH') { $args->{$key} = {} unless defined $args->{$key}; foreach my $skey (keys %{ $new_args{$key} }) { $args->{$key}{$skey} = $new_args{$key}{$skey}; } } elsif ($makemaker_argtype{$key} eq 'APPENDABLE') { $self->makemaker_append($key => $new_args{$key}); } } else { if (defined $args->{$key}) { warn qq{MakeMaker attribute "$key" is overriden; use "makemaker_append" to append values\n}; } $args->{$key} = $new_args{$key}; } } return $args; } # For mm args that take multiple space-seperated args, # append an argument to the current list. sub makemaker_append { my $self = shift; my $name = shift; my $args = $self->makemaker_args; $args->{$name} = defined $args->{$name} ? join( ' ', $args->{$name}, @_ ) : join( ' ', @_ ); } sub build_subdirs { my $self = shift; my $subdirs = $self->makemaker_args->{DIR} ||= []; for my $subdir (@_) { push @$subdirs, $subdir; } } sub clean_files { my $self = shift; my $clean = $self->makemaker_args->{clean} ||= {}; %$clean = ( %$clean, FILES => join ' ', grep { length $_ } ($clean->{FILES} || (), @_), ); } sub realclean_files { my $self = shift; my $realclean = $self->makemaker_args->{realclean} ||= {}; %$realclean = ( %$realclean, FILES => join ' ', grep { length $_ } ($realclean->{FILES} || (), @_), ); } sub libs { my $self = shift; my $libs = ref $_[0] ? shift : [ shift ]; $self->makemaker_args( LIBS => $libs ); } sub inc { my $self = shift; $self->makemaker_args( INC => shift ); } sub _wanted_t { } sub tests_recursive { my $self = shift; my $dir = shift || 't'; unless ( -d $dir ) { die "tests_recursive dir '$dir' does not exist"; } my %tests = map { $_ => 1 } split / /, ($self->tests || ''); require File::Find; File::Find::find( sub { /\.t$/ and -f $_ and $tests{"$File::Find::dir/*.t"} = 1 }, $dir ); $self->tests( join ' ', sort keys %tests ); } sub write { my $self = shift; die "&Makefile->write() takes no arguments\n" if @_; # Check the current Perl version my $perl_version = $self->perl_version; if ( $perl_version ) { eval "use $perl_version; 1" or die "ERROR: perl: Version $] is installed, " . "but we need version >= $perl_version"; } # Make sure we have a new enough MakeMaker require ExtUtils::MakeMaker; if ( $perl_version and $self->_cmp($perl_version, '5.006') >= 0 ) { # MakeMaker can complain about module versions that include # an underscore, even though its own version may contain one! # Hence the funny regexp to get rid of it. See RT #35800 # for details. my $v = $ExtUtils::MakeMaker::VERSION =~ /^(\d+\.\d+)/; $self->build_requires( 'ExtUtils::MakeMaker' => $v ); $self->configure_requires( 'ExtUtils::MakeMaker' => $v ); } else { # Allow legacy-compatibility with 5.005 by depending on the # most recent EU:MM that supported 5.005. $self->build_requires( 'ExtUtils::MakeMaker' => 6.42 ); $self->configure_requires( 'ExtUtils::MakeMaker' => 6.42 ); } # Generate the MakeMaker params my $args = $self->makemaker_args; $args->{DISTNAME} = $self->name; $args->{NAME} = $self->module_name || $self->name; $args->{NAME} =~ s/-/::/g; $args->{VERSION} = $self->version or die <<'EOT'; ERROR: Can't determine distribution version. Please specify it explicitly via 'version' in Makefile.PL, or set a valid $VERSION in a module, and provide its file path via 'version_from' (or 'all_from' if you prefer) in Makefile.PL. EOT $DB::single = 1; if ( $self->tests ) { my @tests = split ' ', $self->tests; my %seen; $args->{test} = { TESTS => (join ' ', grep {!$seen{$_}++} @tests), }; } elsif ( $Module::Install::ExtraTests::use_extratests ) { # Module::Install::ExtraTests doesn't set $self->tests and does its own tests via harness. # So, just ignore our xt tests here. } elsif ( -d 'xt' and ($Module::Install::AUTHOR or $ENV{RELEASE_TESTING}) ) { $args->{test} = { TESTS => join( ' ', map { "$_/*.t" } grep { -d $_ } qw{ t xt } ), }; } if ( $] >= 5.005 ) { $args->{ABSTRACT} = $self->abstract; $args->{AUTHOR} = join ', ', @{$self->author || []}; } if ( $self->makemaker(6.10) ) { $args->{NO_META} = 1; #$args->{NO_MYMETA} = 1; } if ( $self->makemaker(6.17) and $self->sign ) { $args->{SIGN} = 1; } unless ( $self->is_admin ) { delete $args->{SIGN}; } if ( $self->makemaker(6.31) and $self->license ) { $args->{LICENSE} = $self->license; } my $prereq = ($args->{PREREQ_PM} ||= {}); %$prereq = ( %$prereq, map { @$_ } # flatten [module => version] map { @$_ } grep $_, ($self->requires) ); # Remove any reference to perl, PREREQ_PM doesn't support it delete $args->{PREREQ_PM}->{perl}; # Merge both kinds of requires into BUILD_REQUIRES my $build_prereq = ($args->{BUILD_REQUIRES} ||= {}); %$build_prereq = ( %$build_prereq, map { @$_ } # flatten [module => version] map { @$_ } grep $_, ($self->configure_requires, $self->build_requires) ); # Remove any reference to perl, BUILD_REQUIRES doesn't support it delete $args->{BUILD_REQUIRES}->{perl}; # Delete bundled dists from prereq_pm, add it to Makefile DIR my $subdirs = ($args->{DIR} || []); if ($self->bundles) { my %processed; foreach my $bundle (@{ $self->bundles }) { my ($mod_name, $dist_dir) = @$bundle; delete $prereq->{$mod_name}; $dist_dir = File::Basename::basename($dist_dir); # dir for building this module if (not exists $processed{$dist_dir}) { if (-d $dist_dir) { # List as sub-directory to be processed by make push @$subdirs, $dist_dir; } # Else do nothing: the module is already present on the system $processed{$dist_dir} = undef; } } } unless ( $self->makemaker('6.55_03') ) { %$prereq = (%$prereq,%$build_prereq); delete $args->{BUILD_REQUIRES}; } if ( my $perl_version = $self->perl_version ) { eval "use $perl_version; 1" or die "ERROR: perl: Version $] is installed, " . "but we need version >= $perl_version"; if ( $self->makemaker(6.48) ) { $args->{MIN_PERL_VERSION} = $perl_version; } } if ($self->installdirs) { warn qq{old INSTALLDIRS (probably set by makemaker_args) is overriden by installdirs\n} if $args->{INSTALLDIRS}; $args->{INSTALLDIRS} = $self->installdirs; } my %args = map { ( $_ => $args->{$_} ) } grep {defined($args->{$_} ) } keys %$args; my $user_preop = delete $args{dist}->{PREOP}; if ( my $preop = $self->admin->preop($user_preop) ) { foreach my $key ( keys %$preop ) { $args{dist}->{$key} = $preop->{$key}; } } my $mm = ExtUtils::MakeMaker::WriteMakefile(%args); $self->fix_up_makefile($mm->{FIRST_MAKEFILE} || 'Makefile'); } sub fix_up_makefile { my $self = shift; my $makefile_name = shift; my $top_class = ref($self->_top) || ''; my $top_version = $self->_top->VERSION || ''; my $preamble = $self->preamble ? "# Preamble by $top_class $top_version\n" . $self->preamble : ''; my $postamble = "# Postamble by $top_class $top_version\n" . ($self->postamble || ''); local *MAKEFILE; open MAKEFILE, "+< $makefile_name" or die "fix_up_makefile: Couldn't open $makefile_name: $!"; eval { flock MAKEFILE, LOCK_EX }; my $makefile = do { local $/; }; $makefile =~ s/\b(test_harness\(\$\(TEST_VERBOSE\), )/$1'inc', /; $makefile =~ s/( -I\$\(INST_ARCHLIB\))/ -Iinc$1/g; $makefile =~ s/( "-I\$\(INST_LIB\)")/ "-Iinc"$1/g; $makefile =~ s/^(FULLPERL = .*)/$1 "-Iinc"/m; $makefile =~ s/^(PERL = .*)/$1 "-Iinc"/m; # Module::Install will never be used to build the Core Perl # Sometimes PERL_LIB and PERL_ARCHLIB get written anyway, which breaks # PREFIX/PERL5LIB, and thus, install_share. Blank them if they exist $makefile =~ s/^PERL_LIB = .+/PERL_LIB =/m; #$makefile =~ s/^PERL_ARCHLIB = .+/PERL_ARCHLIB =/m; # Perl 5.005 mentions PERL_LIB explicitly, so we have to remove that as well. $makefile =~ s/(\"?)-I\$\(PERL_LIB\)\1//g; # XXX - This is currently unused; not sure if it breaks other MM-users # $makefile =~ s/^pm_to_blib\s+:\s+/pm_to_blib :: /mg; seek MAKEFILE, 0, SEEK_SET; truncate MAKEFILE, 0; print MAKEFILE "$preamble$makefile$postamble" or die $!; close MAKEFILE or die $!; 1; } sub preamble { my ($self, $text) = @_; $self->{preamble} = $text . $self->{preamble} if defined $text; $self->{preamble}; } sub postamble { my ($self, $text) = @_; $self->{postamble} ||= $self->admin->postamble; $self->{postamble} .= $text if defined $text; $self->{postamble} } 1; __END__ #line 541 App-Alice-0.19/inc/Module/Install/Metadata.pm0000644000175000017500000004302011437215363017626 0ustar leedoleedo#line 1 package Module::Install::Metadata; use strict 'vars'; use Module::Install::Base (); use vars qw{$VERSION @ISA $ISCORE}; BEGIN { $VERSION = '1.00'; @ISA = 'Module::Install::Base'; $ISCORE = 1; } my @boolean_keys = qw{ sign }; my @scalar_keys = qw{ name module_name abstract version distribution_type tests installdirs }; my @tuple_keys = qw{ configure_requires build_requires requires recommends bundles resources }; my @resource_keys = qw{ homepage bugtracker repository }; my @array_keys = qw{ keywords author }; *authors = \&author; sub Meta { shift } sub Meta_BooleanKeys { @boolean_keys } sub Meta_ScalarKeys { @scalar_keys } sub Meta_TupleKeys { @tuple_keys } sub Meta_ResourceKeys { @resource_keys } sub Meta_ArrayKeys { @array_keys } foreach my $key ( @boolean_keys ) { *$key = sub { my $self = shift; if ( defined wantarray and not @_ ) { return $self->{values}->{$key}; } $self->{values}->{$key} = ( @_ ? $_[0] : 1 ); return $self; }; } foreach my $key ( @scalar_keys ) { *$key = sub { my $self = shift; return $self->{values}->{$key} if defined wantarray and !@_; $self->{values}->{$key} = shift; return $self; }; } foreach my $key ( @array_keys ) { *$key = sub { my $self = shift; return $self->{values}->{$key} if defined wantarray and !@_; $self->{values}->{$key} ||= []; push @{$self->{values}->{$key}}, @_; return $self; }; } foreach my $key ( @resource_keys ) { *$key = sub { my $self = shift; unless ( @_ ) { return () unless $self->{values}->{resources}; return map { $_->[1] } grep { $_->[0] eq $key } @{ $self->{values}->{resources} }; } return $self->{values}->{resources}->{$key} unless @_; my $uri = shift or die( "Did not provide a value to $key()" ); $self->resources( $key => $uri ); return 1; }; } foreach my $key ( grep { $_ ne "resources" } @tuple_keys) { *$key = sub { my $self = shift; return $self->{values}->{$key} unless @_; my @added; while ( @_ ) { my $module = shift or last; my $version = shift || 0; push @added, [ $module, $version ]; } push @{ $self->{values}->{$key} }, @added; return map {@$_} @added; }; } # Resource handling my %lc_resource = map { $_ => 1 } qw{ homepage license bugtracker repository }; sub resources { my $self = shift; while ( @_ ) { my $name = shift or last; my $value = shift or next; if ( $name eq lc $name and ! $lc_resource{$name} ) { die("Unsupported reserved lowercase resource '$name'"); } $self->{values}->{resources} ||= []; push @{ $self->{values}->{resources} }, [ $name, $value ]; } $self->{values}->{resources}; } # Aliases for build_requires that will have alternative # meanings in some future version of META.yml. sub test_requires { shift->build_requires(@_) } sub install_requires { shift->build_requires(@_) } # Aliases for installdirs options sub install_as_core { $_[0]->installdirs('perl') } sub install_as_cpan { $_[0]->installdirs('site') } sub install_as_site { $_[0]->installdirs('site') } sub install_as_vendor { $_[0]->installdirs('vendor') } sub dynamic_config { my $self = shift; unless ( @_ ) { warn "You MUST provide an explicit true/false value to dynamic_config\n"; return $self; } $self->{values}->{dynamic_config} = $_[0] ? 1 : 0; return 1; } sub perl_version { my $self = shift; return $self->{values}->{perl_version} unless @_; my $version = shift or die( "Did not provide a value to perl_version()" ); # Normalize the version $version = $self->_perl_version($version); # We don't support the reall old versions unless ( $version >= 5.005 ) { die "Module::Install only supports 5.005 or newer (use ExtUtils::MakeMaker)\n"; } $self->{values}->{perl_version} = $version; } sub all_from { my ( $self, $file ) = @_; unless ( defined($file) ) { my $name = $self->name or die( "all_from called with no args without setting name() first" ); $file = join('/', 'lib', split(/-/, $name)) . '.pm'; $file =~ s{.*/}{} unless -e $file; unless ( -e $file ) { die("all_from cannot find $file from $name"); } } unless ( -f $file ) { die("The path '$file' does not exist, or is not a file"); } $self->{values}{all_from} = $file; # Some methods pull from POD instead of code. # If there is a matching .pod, use that instead my $pod = $file; $pod =~ s/\.pm$/.pod/i; $pod = $file unless -e $pod; # Pull the different values $self->name_from($file) unless $self->name; $self->version_from($file) unless $self->version; $self->perl_version_from($file) unless $self->perl_version; $self->author_from($pod) unless @{$self->author || []}; $self->license_from($pod) unless $self->license; $self->abstract_from($pod) unless $self->abstract; return 1; } sub provides { my $self = shift; my $provides = ( $self->{values}->{provides} ||= {} ); %$provides = (%$provides, @_) if @_; return $provides; } sub auto_provides { my $self = shift; return $self unless $self->is_admin; unless (-e 'MANIFEST') { warn "Cannot deduce auto_provides without a MANIFEST, skipping\n"; return $self; } # Avoid spurious warnings as we are not checking manifest here. local $SIG{__WARN__} = sub {1}; require ExtUtils::Manifest; local *ExtUtils::Manifest::manicheck = sub { return }; require Module::Build; my $build = Module::Build->new( dist_name => $self->name, dist_version => $self->version, license => $self->license, ); $self->provides( %{ $build->find_dist_packages || {} } ); } sub feature { my $self = shift; my $name = shift; my $features = ( $self->{values}->{features} ||= [] ); my $mods; if ( @_ == 1 and ref( $_[0] ) ) { # The user used ->feature like ->features by passing in the second # argument as a reference. Accomodate for that. $mods = $_[0]; } else { $mods = \@_; } my $count = 0; push @$features, ( $name => [ map { ref($_) ? ( ref($_) eq 'HASH' ) ? %$_ : @$_ : $_ } @$mods ] ); return @$features; } sub features { my $self = shift; while ( my ( $name, $mods ) = splice( @_, 0, 2 ) ) { $self->feature( $name, @$mods ); } return $self->{values}->{features} ? @{ $self->{values}->{features} } : (); } sub no_index { my $self = shift; my $type = shift; push @{ $self->{values}->{no_index}->{$type} }, @_ if $type; return $self->{values}->{no_index}; } sub read { my $self = shift; $self->include_deps( 'YAML::Tiny', 0 ); require YAML::Tiny; my $data = YAML::Tiny::LoadFile('META.yml'); # Call methods explicitly in case user has already set some values. while ( my ( $key, $value ) = each %$data ) { next unless $self->can($key); if ( ref $value eq 'HASH' ) { while ( my ( $module, $version ) = each %$value ) { $self->can($key)->($self, $module => $version ); } } else { $self->can($key)->($self, $value); } } return $self; } sub write { my $self = shift; return $self unless $self->is_admin; $self->admin->write_meta; return $self; } sub version_from { require ExtUtils::MM_Unix; my ( $self, $file ) = @_; $self->version( ExtUtils::MM_Unix->parse_version($file) ); # for version integrity check $self->makemaker_args( VERSION_FROM => $file ); } sub abstract_from { require ExtUtils::MM_Unix; my ( $self, $file ) = @_; $self->abstract( bless( { DISTNAME => $self->name }, 'ExtUtils::MM_Unix' )->parse_abstract($file) ); } # Add both distribution and module name sub name_from { my ($self, $file) = @_; if ( Module::Install::_read($file) =~ m/ ^ \s* package \s* ([\w:]+) \s* ; /ixms ) { my ($name, $module_name) = ($1, $1); $name =~ s{::}{-}g; $self->name($name); unless ( $self->module_name ) { $self->module_name($module_name); } } else { die("Cannot determine name from $file\n"); } } sub _extract_perl_version { if ( $_[0] =~ m/ ^\s* (?:use|require) \s* v? ([\d_\.]+) \s* ; /ixms ) { my $perl_version = $1; $perl_version =~ s{_}{}g; return $perl_version; } else { return; } } sub perl_version_from { my $self = shift; my $perl_version=_extract_perl_version(Module::Install::_read($_[0])); if ($perl_version) { $self->perl_version($perl_version); } else { warn "Cannot determine perl version info from $_[0]\n"; return; } } sub author_from { my $self = shift; my $content = Module::Install::_read($_[0]); if ($content =~ m/ =head \d \s+ (?:authors?)\b \s* ([^\n]*) | =head \d \s+ (?:licen[cs]e|licensing|copyright|legal)\b \s* .*? copyright .*? \d\d\d[\d.]+ \s* (?:\bby\b)? \s* ([^\n]*) /ixms) { my $author = $1 || $2; # XXX: ugly but should work anyway... if (eval "require Pod::Escapes; 1") { # Pod::Escapes has a mapping table. # It's in core of perl >= 5.9.3, and should be installed # as one of the Pod::Simple's prereqs, which is a prereq # of Pod::Text 3.x (see also below). $author =~ s{ E<( (\d+) | ([A-Za-z]+) )> } { defined $2 ? chr($2) : defined $Pod::Escapes::Name2character_number{$1} ? chr($Pod::Escapes::Name2character_number{$1}) : do { warn "Unknown escape: E<$1>"; "E<$1>"; }; }gex; } elsif (eval "require Pod::Text; 1" && $Pod::Text::VERSION < 3) { # Pod::Text < 3.0 has yet another mapping table, # though the table name of 2.x and 1.x are different. # (1.x is in core of Perl < 5.6, 2.x is in core of # Perl < 5.9.3) my $mapping = ($Pod::Text::VERSION < 2) ? \%Pod::Text::HTML_Escapes : \%Pod::Text::ESCAPES; $author =~ s{ E<( (\d+) | ([A-Za-z]+) )> } { defined $2 ? chr($2) : defined $mapping->{$1} ? $mapping->{$1} : do { warn "Unknown escape: E<$1>"; "E<$1>"; }; }gex; } else { $author =~ s{E}{<}g; $author =~ s{E}{>}g; } $self->author($author); } else { warn "Cannot determine author info from $_[0]\n"; } } #Stolen from M::B my %license_urls = ( perl => 'http://dev.perl.org/licenses/', apache => 'http://apache.org/licenses/LICENSE-2.0', apache_1_1 => 'http://apache.org/licenses/LICENSE-1.1', artistic => 'http://opensource.org/licenses/artistic-license.php', artistic_2 => 'http://opensource.org/licenses/artistic-license-2.0.php', lgpl => 'http://opensource.org/licenses/lgpl-license.php', lgpl2 => 'http://opensource.org/licenses/lgpl-2.1.php', lgpl3 => 'http://opensource.org/licenses/lgpl-3.0.html', bsd => 'http://opensource.org/licenses/bsd-license.php', gpl => 'http://opensource.org/licenses/gpl-license.php', gpl2 => 'http://opensource.org/licenses/gpl-2.0.php', gpl3 => 'http://opensource.org/licenses/gpl-3.0.html', mit => 'http://opensource.org/licenses/mit-license.php', mozilla => 'http://opensource.org/licenses/mozilla1.1.php', open_source => undef, unrestricted => undef, restrictive => undef, unknown => undef, ); sub license { my $self = shift; return $self->{values}->{license} unless @_; my $license = shift or die( 'Did not provide a value to license()' ); $license = __extract_license($license) || lc $license; $self->{values}->{license} = $license; # Automatically fill in license URLs if ( $license_urls{$license} ) { $self->resources( license => $license_urls{$license} ); } return 1; } sub _extract_license { my $pod = shift; my $matched; return __extract_license( ($matched) = $pod =~ m/ (=head \d \s+ L(?i:ICEN[CS]E|ICENSING)\b.*?) (=head \d.*|=cut.*|)\z /xms ) || __extract_license( ($matched) = $pod =~ m/ (=head \d \s+ (?:C(?i:OPYRIGHTS?)|L(?i:EGAL))\b.*?) (=head \d.*|=cut.*|)\z /xms ); } sub __extract_license { my $license_text = shift or return; my @phrases = ( '(?:under )?the same (?:terms|license) as (?:perl|the perl (?:\d )?programming language)' => 'perl', 1, '(?:under )?the terms of (?:perl|the perl programming language) itself' => 'perl', 1, 'Artistic and GPL' => 'perl', 1, 'GNU general public license' => 'gpl', 1, 'GNU public license' => 'gpl', 1, 'GNU lesser general public license' => 'lgpl', 1, 'GNU lesser public license' => 'lgpl', 1, 'GNU library general public license' => 'lgpl', 1, 'GNU library public license' => 'lgpl', 1, 'GNU Free Documentation license' => 'unrestricted', 1, 'GNU Affero General Public License' => 'open_source', 1, '(?:Free)?BSD license' => 'bsd', 1, 'Artistic license' => 'artistic', 1, 'Apache (?:Software )?license' => 'apache', 1, 'GPL' => 'gpl', 1, 'LGPL' => 'lgpl', 1, 'BSD' => 'bsd', 1, 'Artistic' => 'artistic', 1, 'MIT' => 'mit', 1, 'Mozilla Public License' => 'mozilla', 1, 'Q Public License' => 'open_source', 1, 'OpenSSL License' => 'unrestricted', 1, 'SSLeay License' => 'unrestricted', 1, 'zlib License' => 'open_source', 1, 'proprietary' => 'proprietary', 0, ); while ( my ($pattern, $license, $osi) = splice(@phrases, 0, 3) ) { $pattern =~ s#\s+#\\s+#gs; if ( $license_text =~ /\b$pattern\b/i ) { return $license; } } return ''; } sub license_from { my $self = shift; if (my $license=_extract_license(Module::Install::_read($_[0]))) { $self->license($license); } else { warn "Cannot determine license info from $_[0]\n"; return 'unknown'; } } sub _extract_bugtracker { my @links = $_[0] =~ m#L<( \Qhttp://rt.cpan.org/\E[^>]+| \Qhttp://github.com/\E[\w_]+/[\w_]+/issues| \Qhttp://code.google.com/p/\E[\w_\-]+/issues/list )>#gx; my %links; @links{@links}=(); @links=keys %links; return @links; } sub bugtracker_from { my $self = shift; my $content = Module::Install::_read($_[0]); my @links = _extract_bugtracker($content); unless ( @links ) { warn "Cannot determine bugtracker info from $_[0]\n"; return 0; } if ( @links > 1 ) { warn "Found more than one bugtracker link in $_[0]\n"; return 0; } # Set the bugtracker bugtracker( $links[0] ); return 1; } sub requires_from { my $self = shift; my $content = Module::Install::_readperl($_[0]); my @requires = $content =~ m/^use\s+([^\W\d]\w*(?:::\w+)*)\s+([\d\.]+)/mg; while ( @requires ) { my $module = shift @requires; my $version = shift @requires; $self->requires( $module => $version ); } } sub test_requires_from { my $self = shift; my $content = Module::Install::_readperl($_[0]); my @requires = $content =~ m/^use\s+([^\W\d]\w*(?:::\w+)*)\s+([\d\.]+)/mg; while ( @requires ) { my $module = shift @requires; my $version = shift @requires; $self->test_requires( $module => $version ); } } # Convert triple-part versions (eg, 5.6.1 or 5.8.9) to # numbers (eg, 5.006001 or 5.008009). # Also, convert double-part versions (eg, 5.8) sub _perl_version { my $v = $_[-1]; $v =~ s/^([1-9])\.([1-9]\d?\d?)$/sprintf("%d.%03d",$1,$2)/e; $v =~ s/^([1-9])\.([1-9]\d?\d?)\.(0|[1-9]\d?\d?)$/sprintf("%d.%03d%03d",$1,$2,$3 || 0)/e; $v =~ s/(\.\d\d\d)000$/$1/; $v =~ s/_.+$//; if ( ref($v) ) { # Numify $v = $v + 0; } return $v; } sub add_metadata { my $self = shift; my %hash = @_; for my $key (keys %hash) { warn "add_metadata: $key is not prefixed with 'x_'.\n" . "Use appopriate function to add non-private metadata.\n" unless $key =~ /^x_/; $self->{values}->{$key} = $hash{$key}; } } ###################################################################### # MYMETA Support sub WriteMyMeta { die "WriteMyMeta has been deprecated"; } sub write_mymeta_yaml { my $self = shift; # We need YAML::Tiny to write the MYMETA.yml file unless ( eval { require YAML::Tiny; 1; } ) { return 1; } # Generate the data my $meta = $self->_write_mymeta_data or return 1; # Save as the MYMETA.yml file print "Writing MYMETA.yml\n"; YAML::Tiny::DumpFile('MYMETA.yml', $meta); } sub write_mymeta_json { my $self = shift; # We need JSON to write the MYMETA.json file unless ( eval { require JSON; 1; } ) { return 1; } # Generate the data my $meta = $self->_write_mymeta_data or return 1; # Save as the MYMETA.yml file print "Writing MYMETA.json\n"; Module::Install::_write( 'MYMETA.json', JSON->new->pretty(1)->canonical->encode($meta), ); } sub _write_mymeta_data { my $self = shift; # If there's no existing META.yml there is nothing we can do return undef unless -f 'META.yml'; # We need Parse::CPAN::Meta to load the file unless ( eval { require Parse::CPAN::Meta; 1; } ) { return undef; } # Merge the perl version into the dependencies my $val = $self->Meta->{values}; my $perl = delete $val->{perl_version}; if ( $perl ) { $val->{requires} ||= []; my $requires = $val->{requires}; # Canonize to three-dot version after Perl 5.6 if ( $perl >= 5.006 ) { $perl =~ s{^(\d+)\.(\d\d\d)(\d*)}{join('.', $1, int($2||0), int($3||0))}e } unshift @$requires, [ perl => $perl ]; } # Load the advisory META.yml file my @yaml = Parse::CPAN::Meta::LoadFile('META.yml'); my $meta = $yaml[0]; # Overwrite the non-configure dependency hashs delete $meta->{requires}; delete $meta->{build_requires}; delete $meta->{recommends}; if ( exists $val->{requires} ) { $meta->{requires} = { map { @$_ } @{ $val->{requires} } }; } if ( exists $val->{build_requires} ) { $meta->{build_requires} = { map { @$_ } @{ $val->{build_requires} } }; } return $meta; } 1; App-Alice-0.19/META.yml0000644000175000017500000000156611437215363013426 0ustar leedoleedo--- abstract: 'an Altogether Lovely Internet Chatting Experience' author: - 'Lee Aylward ' build_requires: ExtUtils::MakeMaker: 6.42 Test::More: 0.86 Test::TCP: 0 configure_requires: ExtUtils::MakeMaker: 6.42 distribution_type: module generated_by: 'Module::Install version 1.00' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: App-Alice no_index: directory: - inc - share - t requires: Any::Moose: 0 AnyEvent: 5.2 AnyEvent::DBI: 0 AnyEvent::HTTP: 0 AnyEvent::IRC: 0.95 DBD::SQLite: 0 File::Copy: 0 File::ShareDir: 0.01 IRC::Formatting::HTML: 0.27 JSON: 2.12 List::MoreUtils: 0 Plack: 0 Plack::Session: 0 SQL::Abstract: 0 Text::MicroTemplate: 0.09 Try::Tiny: 0 Twiggy: 0 perl: 5.8.0 resources: license: http://dev.perl.org/licenses/ version: 0.19 App-Alice-0.19/MANIFEST0000644000175000017500000000501011437215257013274 0ustar leedoleedobin/alice Changes inc/Module/Install.pm inc/Module/Install/Base.pm inc/Module/Install/Can.pm inc/Module/Install/Fetch.pm inc/Module/Install/Makefile.pm inc/Module/Install/Metadata.pm inc/Module/Install/Scripts.pm inc/Module/Install/Share.pm inc/Module/Install/Win32.pm inc/Module/Install/WriteAll.pm lib/App/Alice.pm lib/App/Alice.pod lib/App/Alice/Commands.pm lib/App/Alice/Config.pm lib/App/Alice/History.pm lib/App/Alice/HTTPD.pm lib/App/Alice/InfoWindow.pm lib/App/Alice/IRC.pm lib/App/Alice/Logger.pm lib/App/Alice/MessageBuffer.pm lib/App/Alice/MessageStore/Memory.pm lib/App/Alice/MessageStore/Redis.pm lib/App/Alice/Notifier/Growl.pm lib/App/Alice/Notifier/LibNotify.pm lib/App/Alice/Signal.pm lib/App/Alice/Stream.pm lib/App/Alice/Test/MockIRC.pm lib/App/Alice/Test/NullHistory.pm lib/App/Alice/Window.pm Makefile.PL MANIFEST This list of files MANIFEST.SKIP META.yml share/commands.pl share/log.db share/log.sql share/static/alice-dark.css share/static/alice-default.css share/static/alice.js share/static/favicon.ico share/static/image/alice.png share/static/image/aquaTabClose.png share/static/image/background-self.png share/static/image/background.png share/static/image/config.png share/static/image/formatting.png share/static/image/overflow-active.png share/static/image/overflow.png share/static/image/pause.png share/static/image/play.png share/static/image/privateChatTab.png share/static/image/privateChatTabNewMessage.png share/static/image/roomTab.png share/static/image/roomTabDisabled.png share/static/image/roomTabNewHighlightMessage.png share/static/image/roomTabNewMessage.png share/static/image/shadow-bottom.png share/static/image/shadow-top.png share/static/image/smartTranscriptTab.png share/static/image/smartTranscriptTabActivity.png share/static/image/speech.png share/static/image/sprites.acorn share/static/image/sprites.png share/static/image/terminal.png share/templates/announcement.html share/templates/avatargrid.html share/templates/event.html share/templates/help.html share/templates/index_footer.html share/templates/index_head.html share/templates/login.html share/templates/logs.html share/templates/message.html share/templates/new_server.html share/templates/prefs.html share/templates/range.html share/templates/results.html share/templates/select.html share/templates/server_listitem.html share/templates/servers.html share/templates/tab.html share/templates/window.html share/templates/window_footer.html share/templates/window_head.html t/01-use.t t/02-app.t t/03-window.t t/04-irc.t t/alice/test_config t/pod.t