Merge branch 'master' of git.c3d2.de:c3d2-web

This commit is contained in:
koeart 2015-08-04 19:15:55 +02:00
commit 1038f2d661
135 changed files with 12963 additions and 15274 deletions

View File

@ -307,13 +307,10 @@ build/datenspuren/2013/mitschnitte-rss.xml: content/news/ds13-videomitschnitte-k
build/datenspuren/2014/mitschnitte-rss.xml: content/news/ds14-mitschnitte-online.xml $(STYLE)
$(call xml_process)
xhtml5-validate: $(patsubst build/%.html, build/%.html.xhtml5-validate, $(CONTENT))
build/%.html.xhtml5-validate: build/%.html
./scripts/validate_xhtml5.sh $<
### Die neuesten Chaosupdates holen ###
#$(WWW_CCC_DE_UPDATES).orig: $(DATESTAMP)
# $(RM) $@

View File

@ -18,13 +18,17 @@ Dresden. Ein Chaot erinnert sich:
<p title="Ziel">
Nicht alles ist gut. Darum reden wir darüber. Wir diskutieren und analysieren neue Trends in der IT - und wir wollen mit unseren kritischen Beiträgen sowohl den Gedankenaustausch zwischen Politik, Gesellschaft und Hackerschaft fördern, als auch zum Mitmachen und Mitgestalten anregen. Wir sind inhaltlich unabhängig von Sponsoren und rein ehrenamtlich organisiert.
</p>
<p title="2004">
<h3 xmlns="http://www.w3.org/1999/xhtml">Geschichte</h3>
<h4 xmlns="http://www.w3.org/1999/xhtml">2004</h4>
<p>
Im Mai 2004 veranstaltete der Chaos Computer Club Dresden die ersten
DatenSpuren [sic!] unter dem Motto "Privatsphäre war gestern", mit
einer Keynote von Prof. Andreas Pfitzmann (ehemaliger Professor für
Datenschutz &amp; -sicherheit an der TUD). Damals noch eine <link href="https://datenspuren.de/2004">eintägige Veranstaltung</link> in den Räumen des Pentacon, starteten die DatenSpuren mit gut 20 Vorträgen zu E-Mail Verschlüsselung (mit Keysigning Party), Vorratsdatenspeicherung, dem Mautsystem und Datenschutz in Sachsen und Europa.
</p>
<p title="2005">
<h4 xmlns="http://www.w3.org/1999/xhtml">2005</h4>
<p>
2005 zogen die Datenspuren vom Pentacon in die Scheune in der
Neustadt, in deren Räumlichkeiten die Veranstaltung über die nächsten
9 Jahre immer weiter wuchs. Damals hofften wir noch, dass man mit
@ -43,10 +47,12 @@ Protesten gegen den jährlich stattfindenden Neonazi-Aufmarsch zum
13. Februar massenhaft Handydaten von Bürgerinnen und Bürgern ganzer
Stadtteile gesammelt und ausgewertet wurden - illegal, <link href="https://netzpolitik.org/2013/landgericht-dresden-groste-funkzellenabfrage-von-handygate-2011-war-illegal/">wie später ein Gericht befand</link>.
</p>
<p title="2006 - 2014">
<h4 xmlns="http://www.w3.org/1999/xhtml">2006 - 2014</h4>
<p>
Im Jahr 2006 reichte uns ein Tag nicht mehr aus - die Voträge und Workshops wurden immer vielfältiger und wir erweiterten die Veranstaltung um einern weiteren Tag. Neue Themen und Diskussionen wie über den Wert von Aufenthaltsinformationen von Nutzern, Biometrie, Videoüberwachung, TOR, I2P, Verschlüsselung, IT-Security, Zensur, Open-Data, Informations-Freiheits und andere -Gesetze, sowie weiteres bereicherten die nunmehr DatenSpuren. Um die zukünftigen Generationen an unsere Themen heranzuführen, wurde das Programm der Datenspuren ab 2011 um einen Junghackertrack erweitert, der seitdem auch ein fester Bestandteil des Chaos Communication Congress ist. Bausätze für kleine und große Hacker und Haecksen wurden entwickelt, so z. B. der Pentabug.
</p>
<p title="2015">
<h4 xmlns="http://www.w3.org/1999/xhtml">2015</h4>
<p>
Nach 10 Jahren ziehen die Datenspuren wieder zurück nach Striesen - um
etwas neues auszuprobieren. In den <link href="http://www.tsd.de/">Technischen Sammlungen Dresden</link> haben wir thematisch eine passende Umgebung und viel mehr Raum für neue Ideen. Zu den Technischen Sammlungen passt auch unser diesjähriges Motto: <em>"Hackers in the house"</em>. Auch wenn es vielmehr um die Bedeutung der (digitalen) Privatsphäre gehen soll, die wir einerseits durch neue Smart &lt;Irgendwas&gt; Geräte und andererseits durch staatliche Stellen (siehe Snowden) gefährdet sehen. Hier sind neue Grenzen zu ziehen - worüber wir diskutieren wollen. Ein wichtiges Themengebiet soll daher auch die Kryptographie werden - und die politische Meinung dazu (<em>"Crypto is not a crime"</em>).
</p>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE page SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<page title="Call for Participation">
<p class="center">(Also available in <link href="cfp.html">German</link>)</p>
<p title="Datenspuren 2015: Call for Participation">
Technische Sammlungen Dresden, Sat+Sun October 24+25, 2015
</p>
<p class="block"><em>
Girls and boys,
get up on your feet and make some noise,
because hackers are in the house!
</em></p>
<p>
In June 2013, Edward Snowden came public with an intelligence affair, so
extensive and intricate, that it went straight into the history books. Since
that day, no day has passed without continuing intelligence scandals
continuously filling print and online news.These affairs create a new
awareness for our digital rights, which we once took
for granted but now find fading away to mere symbols in an erosive
atmosphere
of clever policing in our shiny new digital world.
</p>
<p>
<strong>Where</strong> "everyone is suspicious" is a mantra, which leaks out from
behind the
closed doors of the security agencies. This violates the fundamental trust
between citizens and the executive authorities, which is a key
ingredient for
democracies.
</p>
<p>
<strong>Where</strong> digitalisation makes every person transparent and privacy shall
remain a
state-owned privilege, we ask: how much secrecy does a democracy need
and how
much of it can we bear?
</p>
<p>
<strong>Where</strong> Big Data determines more and more of our everyday life and the vague
promised advantages provided by of smart applications suffices to vilify
perseverance on privacy rights as obsolete and reactionary, we need to
discuss
how much privacy to concede and who grants it.
</p>
<p>
Thus, Big Data requires a wholesome realignment of the relationship between
people, state and economy. It also requires the digital vanguards that
significantly further these developments in big corps' IT departments, at
universities, in hackerspaces, or in the data centres of the services to
rethink their responsibilities.
</p>
<p>
Therefore, the Chaos Computer Club Dresden (c3d2) invites you to submit
proposals for technical, scientific, and artistic contributions to our event
"Datenspuren 2015". Topics are, not exclusively:
</p>
<ul>
<li>Cryptowars back then and today</li>
<li>Usability of cryptography</li>
<li>Forms of digital self-defence</li>
<li>Computer safety and security</li>
<li>Opportunities and perils of smart devices</li>
<li>Making you own devices (Maker + Breaker Scene)</li>
<li>Free Software &amp; Open Hardware</li>
<li>Hacking as sub- and anticulture</li>
<li>Information ethics and privacy</li>
<li>Arts and electronics</li>
</ul>
<dl>
<dt>Deadline for submissions</dt>
<dd>16th of August 2015</dd>
<dt>Format</dt>
<dd>Abstract (max. 300 words) for a lecture, workshop,
installation/performance, young hackers track</dd>
<dt>Submission of lectures and workshops</dt>
<dd>with the conference system frab:
<link>https://frab.cccv.de/en/DS2015/cfp/session/new#new_user</link></dd>
<dt>Contact and questions</dt>
<dd>Organizing team: datenspuren@c3d2.de</dd>
<dd>Mailing list: datenspuren@lists.c3d2.de</dd>
</dl>
<p>
Notice on acceptance/rejection of submissions will be given until
15th of
September 2015.
</p>
<p>
The event Datenspuren is a non-commercial community event. As we neither ask
for entrance nor participation fees, we cannot pay any royalties. However,
subsidizing travel and overnight stay might be granted for limited
exceptions
if reasonably founded. Applications can be directed to the <link href="mailto:datenspuren@c3d2.de">organization
team.</link>
</p>
<p class="block">
<em>See you in da house!</em>
</p>
</page>

View File

@ -2,6 +2,7 @@
<!DOCTYPE page SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<page title="Call for Participation">
<p class="center">(Also available in <link href="cfp-en.html">English</link>)</p>
<p title="Datenspuren 2015: Call for Participation">
Technische Sammlungen Dresden, 24. + 25. Oktober 2015
</p>

View File

@ -2243,7 +2243,7 @@
<event title="Pentaradio">
<start>2014-09-23T21:30:00</start>
<end>2014-09-23T23:00:00</end>
<link>news/pentaradio24-20120923.html</link>
<link>news/pentaradio24-20140923.html</link>
</event>
<event title="Scala Hackathon Leipzig">
<start>2014-09-28T09:30:00</start>
@ -2253,17 +2253,17 @@
<event title="Pentaradio">
<start>2014-10-28T21:30:00</start>
<end>2014-10-28T23:00:00</end>
<link>news/pentaradio24-20121028.html</link>
<link>news/pentaradio24-20141028.html</link>
</event>
<event title="Pentaradio">
<start>2014-11-25T21:30:00</start>
<end>2014-11-25T23:00:00</end>
<link>news/pentaradio24-20121125.html</link>
<link>news/pentaradio24-20141125.html</link>
</event>
<event title="Pentaradio">
<start>2014-12-23T21:30:00</start>
<end>2014-12-23T23:00:00</end>
<link>news/pentaradio24-20121223.html</link>
<link>news/pentaradio24-20141223.html</link>
</event>
<event title="Vorstellungsrunde Build-Systeme">
<start>2014-10-09T20:00:00</start>
@ -2475,7 +2475,7 @@
<start>2015-01-27T21:30:00</start>
<end>2015-01-27T23:00:00</end>
<location>Coloradio, Radio-Initiative Dresden e.V., Jordanstr. 5 HH, 01099 Dresden</location>
<link>radio.html</link>
<link>news/pentaradio24-20150127.html</link>
</event>
<event title="Freie Software und Freies Wissen">
<start>2015-01-28T18:30:00</start>
@ -2557,7 +2557,7 @@
<start>2015-02-24T21:30:00</start>
<end>2015-02-24T23:00:00</end>
<location>Coloradio, Radio-Initiative Dresden e.V., Jordanstr. 5 HH, 01099 Dresden</location>
<link>radio.html</link>
<link>news/pentaradio24-20150224.html</link>
</event>
<event title="Freie Software und Freies Wissen">
<start>2015-02-25T18:30:00</start>
@ -2591,7 +2591,7 @@
<start>2015-03-24T21:30:00</start>
<end>2015-03-24T23:00:00</end>
<location>Coloradio, Radio-Initiative Dresden e.V., Jordanstr. 5 HH, 01099 Dresden</location>
<link>radio.html</link>
<link>news/pentaradio24-20150324.html</link>
</event>
<event title="Freie Software und Freies Wissen">
<start>2015-03-25T18:30:00</start>
@ -2674,7 +2674,7 @@
<start>2015-04-28T21:30:00</start>
<end>2015-04-28T23:00:00</end>
<location>Coloradio, Radio-Initiative Dresden e.V., Jordanstr. 5 HH, 01099 Dresden</location>
<link>radio.html</link>
<link>news/pentaradio24-20150428.html</link>
</event>
<event title="Dresden-Weekly">
<start>2015-04-29T18:00:00</start>
@ -2717,7 +2717,7 @@
<start>2015-05-26T21:30:00</start>
<end>2015-05-26T23:00:00</end>
<location>Coloradio, Radio-Initiative Dresden e.V., Jordanstr. 5 HH, 01099 Dresden</location>
<link>radio.html</link>
<link>news/pentaradio24-20150526.html</link>
</event>
<event title="GPN15">
<start>2015-06-04</start>
@ -2744,7 +2744,7 @@
<start>2015-06-23T21:30:00</start>
<end>2015-06-23T23:00:00</end>
<location>Coloradio, Radio-Initiative Dresden e.V., Jordanstr. 5 HH, 01099 Dresden</location>
<link>radio.html</link>
<link>news/pentaradio24-20150623.html</link>
</event>
<event title="Lange Nacht der Wissenschaft">
<start>2015-07-03</start>
@ -3025,4 +3025,60 @@
<location>Bautzner Straße 6, 01099 Dresden</location>
<link>http://edward-snowden-platz.de/</link>
</event>
<event title="Open Data">
<start>2015-07-01T19:00:00</start>
<location>GCHQ, Lingnerallee 3, Dresden</location>
<link>http://offenesdresden.de/</link>
</event>
<event title="Open Data">
<start>2015-07-15T19:00:00</start>
<location>GCHQ, Lingnerallee 3, Dresden</location>
<link>http://offenesdresden.de/</link>
</event>
<event title="Camp 2015">
<start>2015-08-13</start>
<end>2015-08-17</end>
<location>Ziegeleipark Mildenberg Zehdenick, Ziegelei 10, 16792 Zehdenick (OT Mildenberg)</location>
<link>http://events.ccc.de/camp/2015</link>
</event>
<event title="HQ-Audio&#173;equip&#173;ment&#173;be&#173;spre&#173;chung">
<start>2015-07-07T20:30:00</start>
<location>GCHQ, Lingnerallee 3, Dresden</location>
</event>
<event title="Blogging In Iran">
<start>2015-07-17T16:40:00</start>
<location>Gerber-Bau (GER/038/H), TU Dresden</location>
<link>http://dresden-postkolonial.de/vortrag-blogging-in-iran/</link>
</event>
<event title="MV VG Bürgernetz">
<start>2015-07-15T17:00:00</start>
<location>bei Volkssolidarität, Freiberger Str., 01067 Dresden</location>
</event>
<event title="PHP">
<start>2015-07-14T19:00:00</start>
<location>SLUB Dresden, Talleyrand-Zimmer, 2. Stock, Zellescher Weg 18, Dresden</location>
<link>http://wdcmdresden.com/softwareentwicklung%C2%AD-mit-php-in-2015-einblick-und-ausblick/</link>
</event>
<event title="10. Organisationstreffen für die Datenspuren 2015">
<start>2015-07-29T20:30:00</start>
<location>GCHQ, Lingnerallee 3, Dresden</location>
<link>https://pentapad.c3d2.de/p/ds15-10</link>
</event>
<event title="Pentaradio">
<start>2015-07-27T21:30:00</start>
<end>2015-07-27T23:00:00</end>
<location>Coloradio, Radio-Initiative Dresden e.V., Jordanstr. 5 HH, 01099 Dresden</location>
<link>news/pentaradio24-20150727.html</link>
</event>
<event title="Freifunkgrillen">
<start>2015-08-01T16:00:00</start>
<end>2015-08-01T19:00:00</end>
<location>Piraten Sachsen, Kamenzer Straße 13-15, Dresden</location>
<link>https://www.google.com/calendar/render?eid=ZjY4anBpaHNwM2VwNml2dDA4dmQ0ZGFwZmMgODh1NDk4anN0dXAzNHJsdmJ1c3FqcWlhdmdAZw&amp;ctz=Europe/Berlin&amp;sf=true&amp;output=xml&amp;pli=1#eventpage_6</link>
</event>
<event title="Open Data">
<start>2015-08-05T19:00:00</start>
<location>GCHQ, Lingnerallee 3, Dresden</location>
<link>http://offenesdresden.de/</link>
</event>
</calendar>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<item title="Mitschnitt Open-Data-Treffen"
date="2015-06-24T00:42:00"
author="rob">
<image title="OpenDataDresden/OKLab-DD">opendata4x.png</image>
<p>
Im Rahmen der Vorbereitung des OpenDataPortals für Sachsen trafen sich zum Erfahrunsaustausch und zur Abstimmung mit der Community, der Projektleiter OpenGovernmentData des Freistaates Sachsen und die mit der Vorstudie beauftragte Beratungsfirma Syncwork mit dem OpenKnowledge-Lab Dresden.
</p>
<resource title="Mitschnitt Open-Data-Treffen vom 17. Juni 2015"
size="192166988"
type="audio/mpeg"
url="http://ftp.c3d2.de/misc/20150617_OpenDataDresden.m4a" />
</item>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<item title="Internet &amp; Computer for Refugees - Meetup"
date="2015-07-10T16:50:00"
author="Hilfsbereit">
<event>
<start>2015-07-14T20:00:00</start>
<location><link href="space.html">GCHQ</link>, Lingnerallee 3</location>
</event>
</item>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<?xml-stylesheet type="text/xsl" href="../../xsl/html5.xsl" ?>
<item title="BSDienstag - Berkeley System Dienstag" date="2015-08-02T13:55:23" author="eri!, PwnyTail">
<image title='Daemon Hammer'>../daemon_hammer_240x360.jpg</image>
<event>
<start>2015-08-04T20:21:00</start>
<location><link href="space.html">GCHQ</link>, Lingnerallee 3</location>
</event>
<p>
Die <link href="https://www.c3d2.de/news/pentaradio24-20141125.html">Pentaradiosendung</link>
am 25. November 2014 zur Berkley Software Distribution hat uns
beschwingt den Fokus auf BSD zu setzen. So kam es,
dass wir uns, ähnlich zu einer BSD-Usergroup, für's erste immer
am 1. Dienstag des Monats in den Räumen des Dresdner C3 treffen.
</p>
<p>
Wer Gleichgesinnte sucht, Hilfe braucht, bis tief in die Nacht mit/an
BSD hacken oder einfach nur ein paar Folgen <link href="http://bsdnow.tv/">BSD Now</link>
mit uns schauen mag, ist bei uns richtig.
</p>
<p>
Für Wünsche und Anregungen kann auch die
<link href="https://wiki.c3d2.de/w/index.php?title=Diskussion:BSDienstag">
Diskussionseite zum BSDienstag
</link>
im Wiki des GCHQ genutzt werden.
</p>
</item>

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<?xml-stylesheet type="text/xsl" href="../../xsl/html5.xsl" ?>
<item title="Bundesverband Bitcoin" date="2015-07-23T23:42:05" author="vv01f">
<image title="bitcoin">bitcoin.png</image>
<event>
<start>2015-08-06T19:00:00</start>
<location><link href="http://www.room77.de/">room77</link>, Graefestraße 77, 10967 Berlin</location>
</event>
<p>
Der <link href="http://www.bundesverband-bitcoin.de/">Bundesverband Bitcoin</link> hat auf der diesjährigen Mitgliederversammlung beschlossen, regelmäßig auf die jeweils zwei folgenden Vorstandssitzungen hinzuweisen, zu denen jedes Mitglied und auch Besucher herzlich eingeladen sind.
</p>
<p>
Die nächsten Termine sind:
</p>
<ul>
<li>am Donnerstag, 06.08.2015 ab 19:00 Uhr im room77 in Berlin</li>
<li>am Donnerstag, 03.09.2015 ab 19:00 Uhr im room77 in Berlin</li>
</ul>
<p>
Bis auf weiteres bleibt es dabei, dass die Vorstandssitzungen regelmäßig am jeweils ersten Donnerstag im Monat an den Stammtisch im room77 geknüpft werden.
Adresse: room77 - Graefestr. 77 - 10967 Berlin
</p>
<p>
Ich bin mir noch nicht sicher, ob das nicht auf Dauer ein wenig lästig wird, wenn ihr jeden Monat eine Erinnerung an die Termine bekommt. Lasst mich einfach wissen, was ihr davon haltet.
</p>
<p>
Mit freundlichen Grüßen,
Bastian Lipp im Auftrag des Vorstands
</p>
</item>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<item title="Interfug #1"
date="2015-07-07T16:42:00"
author="Astro">
<image title="Chaos Chemnitz bei Nacht">chch_nacht.jpg</image>
<event>
<start>2015-08-29</start>
<end>2015-08-30</end>
<location>Chaostreff Chemnitz e.V., Augustusburger Str. 102, 09126 Chemnitz</location>
</event>
<p>
Der <link href="https://chaoschemnitz.de/">Chaostreff
Chemnitz</link> wird am letzten Augustwochenende seine erste
größere Veranstaltung ausrichten. Aus dem <link
href="https://www.interfug.de/cfp">Call for Papers:</link>
</p>
<p class="blockquote">
Für unsere Veranstaltung suchen wir interessante Beiträge aus den Bereichen freie Software, Hardware-Hacking, Kryptographie, Soziale Netzwerke, Eingebettete Systeme, Programmiersprachen, Anonymität und daraus resultierende gesellschaftliche Fragen.
Diese Liste ist aber keinesfalls abschließend. Der Interfug soll ein breites, interessantes Themenspektrum abdecken.
Die Vortragsslots sind auf 60 Minuten ausgelegt (40 Minuten Vortrag, 10 Minuten Diskussion, 10 Minuten Puffer). Wenn eurer Vortrag kürzer oder länger ist, teilt uns das entsprechend mit. Wir biegen das schon irgendwie hin.
Wir freuen uns auf eure rege Teilnahme. Es gibt Kuchen!
</p>
</item>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<item title="Citizenfour Kino im Kasten"
date="2015-07-01T20:30:00" author="vv01f">
<image title="citizenfour">citizenfour.jpg</image>
<event>
<start>2015-07-02T20:30:00</start>
<location>TU Dresden, Großer Hörsaal, Gebäude "ABS", August-Bebel-Str. 20, 01219 Dresden</location>
</event>
<p>
Das <link href="http://www.kino-im-kasten.de/programm.html">„Kino im Kasten“</link> präsentiert in Zusammenarbeit mit der <link href="">FSFW</link>: <link href="https://citizenfourfilm.com/"></link>Die Snowden-Dokumentation „Citizenfour“
</p>
</item>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<item title="pentaradio24: Go, die Program&#173;mier&#173;sprache, nicht das blöde Spiel"
date="2015-06-23T09:10:00"
author="Mic92">
<image title="(((pentaradio))">../pentaradio.png</image>
<p>
Das 21. Jahrhundert liefert neue Herausforderungen für Programmierer: Multicore-Prozessoren, immer komplexer werdende Software, verteilte Systeme.
Die Entwickler der Programmiersprache Go verfolgen den Ansatz, es einfach zu machen, sichere und effiziente Software zu schreiben.
In dieser Sendung wollen wir euch die Programmiersprache näher vorstellen.
</p>
<ul>
<li><link href="http://fusion.net/story/139838/this-drug-cartel-used-39-street-cameras-to-spy-on-everyone/">Drogenkartell benutzt 39 Straßenkameras</link></li>
<li><link href="http://www.heise.de/newsticker/meldung/Avast-gibt-Nutzungsdaten-an-Analysefirma-weiter-2677838.html"> Avast gibt Nutzungsdaten an Analysefirmen</link></li>
<li><link href="http://www.heise.de/newsticker/meldung/UN-Beauftragter-wirbt-fuer-Verschluesselung-und-Anonymitaet-2672561.html"> UN-Beauftragter wirbt für Verschlüsselung</link></li>
<li><link href="http://www.golem.de/news/ssh-sechs-jahre-alter-bug-bedroht-github-repositories-1506-114449.html"> SSH-Keys auf Github ausgewertet</link></li>
<li><link href="http://www.heise.de/newsticker/meldung/Nach-Trojaner-Angriff-Bundestag-soll-neues-Computer-Netzwerk-benoetigen-2687521.html"> Trojaner-Angriff auf den Bundestag</link></li>
<li><link href="http://edward-snowden-platz.de/"> Edward Snowden Platz in Dresden</link></li>
<li><link href="https://www.htw-dresden.de/index.php?id%3D23853%26vid%3D240"> 200. Datenbankstammtisch</link></li>
<li><link href="http://awesome-go.com/"> Bibliotheken in Go</link></li>
<li><link href="http://www.wired.com/2013/07/gopher/"> Gopher Team bei Google</link></li>
<li><link href="http://blog.golang.org/race-detector"> Go-Race Detector</link></li>
<li><link href="http://talks.golang.org/2015/gogo.slide#2"> Go 1.5: Go in Go geschrieben</link></li>
<li><link href="http://blog.golang.org/4years"> Projekte in Go</link></li>
</ul>
<addendum>
<p>
Hören könnt Ihr uns wie immer, Live am 4. Dienstag im Monat von 21:30 - 23:00 Uhr auf
<link href="http://coloradio.org/">coloRadio</link> (<link href="http://hub.fueralle.org/coloradio160mp3.m3u">Stream</link>).
Wenn Ihr Fragen oder Anmerkungen habt, erreicht Ihr uns auch während der Sendung im <link href="muc.html">Chat</link>.
</p>
</addendum>
<resource title="pentaradio24 vom 23. Juni 2015"
size="92839165"
type="application/ogg"
url="http://ftp.c3d2.de/pentaradio/pentaradio-2015-06-23.ogg">
<alternative size="84459626"
type="audio/mpeg"
url="http://ftp.c3d2.de/pentaradio/pentaradio-2015-06-23.mp3"/>
<alternative size="39017524"
type="audio/opus"
url="http://ftp.c3d2.de/pentaradio/pentaradio-2015-06-23.opus"/>
<chapters xmlns="http://podlove.de/simple-chapters">
<chapter start="0:00:00" title="News"/>
<chapter start="0:18:31" title="Musik: Write in C"/>
<chapter start="0:22:04" title="Thema"/>
<chapter start="1:11:00" title="Musik: The Java Life"/>
<chapter start="1:14:13" title="Thema"/>
</chapters>
</resource>
</item>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<item title="pentaradio24: die Program&#173;mier&#173;sprache Rust"
date="2015-07-27T20:23:00"
author="Astro">
<image title="(((pentaradio))">../pentaradio.png</image>
<p>
Schon oft in den News featured, nun widmen wir dem nächsten
Shooting Star der Systemprogrammiersprachenwelt eine ganze
Sendung: es geht um <link
href="http://www.rust-lang.org/">Rust</link>, und Spezialexperten
sind eingeladen.
</p>
<p>
Hören könnt Ihr uns wie immer, Live am 4. Dienstag im Monat von
21:30 - 23:00 Uhr auf <link
href="http://coloradio.org/">coloRadio</link> (<link
href="http://hub.fueralle.org/coloradio160mp3.m3u">Stream</link>).
Wenn Ihr Fragen oder Anmerkungen habt, erreicht Ihr uns auch
während der Sendung im <link href="muc.html">Chat</link>.
</p>
<ul>
<li><link href="http://www.heise.de/netze/meldung/Snowden-Appell-an-die-IETF-Schuetzt-die-Internetnutzer-2753289.html">Snowden zu IETF eingeladen</link></li>
<li><link href="http://www.heise.de/newsticker/meldung/Neuer-Bitkom-Chef-haelt-Datensparsamkeit-fuer-veraltet-und-hinderlich-2753840.html">Neuer Bitkom-Chef hält Datensparsamkeit für veraltet</link></li>
<li><link href="www.gainesville.com/article/20150720/ARTICLES/150729990">Grooveshark Gründer tot aufgefunden</link></li>
<li><link href="http://www.heise.de/ct/ausgabe/2015-17-Die-Spionagesoftware-Firma-Hacking-Team-wurde-gehackt-2755600.html">Hacking Team</link></li>
<li><link href="http://www.heise.de/security/meldung/Stagefright-Android-Smartphones-ueber-Kurznachrichten-angreifbar-2763764.html">Android-Exploit: Stagefright</link></li>
<li><link href="http://www.theregister.co.uk/2015/07/22/os_x_root_hole/">Mac OS X Priviledge Escalation in 300 Zeichen</link></li>
<li><link href="http://words.steveklabnik.com/a-new-introduction-to-rust">An alternative introduction to Rust</link></li>
<li><link href="http://graydon.livejournal.com/220853.html">Things Rust shipped without</link></li>
<li><link href="http://www.reddit.com/r/rust/comments/3clqta/what_motivates_the_raii_gc_reference_counting/">What motivates the RAII / GC / Reference Counting choice in a few performance-oriented languages?</link></li>
<li><link href="http://www.wilfred.me.uk/blog/2015/06/18/exploring-rust/">Exploring Rust</link></li>
<li><link href="http://servo.org/">Mozillas Rendering Engine: Servo</link></li>
<li><link href="http://www.piston.rs/">Gameengine: Piston</link></li>
<li><link href="https://crates.io/">crates.io</link></li>
</ul>
<resource title="pentaradio24 vom 28. Juli 2015"
size="58549969"
type="application/ogg"
url="http://ftp.c3d2.de/pentaradio/pentaradio-2015-07-28.ogg">
<alternative size="72592011"
type="audio/mpeg"
url="http://ftp.c3d2.de/pentaradio/pentaradio-2015-07-28.mp3"/>
<alternative size="36483365"
type="audio/opus"
url="http://ftp.c3d2.de/pentaradio/pentaradio-2015-07-28.opus"/>
</resource>
</item>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<item title="Er&#173;fah&#173;rung&#173;s&#173;aus&#173;tausch Elek&#173;t&#173;ro&#173;nik Löt&#173;kol&#173;ben, Si&#173;mu&#173;la&#173;to&#173;ren, De&#173;sign" date="2015-08-01T23:42:00" author="hakunamenta">
<event>
<start>2015-08-02T15:00:00</start>
<location><link href="space.html">GCHQ</link>, Lingnerallee 3</location>
</event>
<p>
Jemand und ich haben gerade entschieden Sonntag Nachmittag ein
bisschen Wissenstransfer in den Themenbereichen
Elektronikwerkzeuge, Simulatoren für Elektronik und weiteres zu
begehen.
</p>
</item>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<item title="TA: Clean Code Ⅲ" date="2015-07-30T23:10:00" author="Astro">
<image title="Clean Code?" width="232" height="207" >cleancode-thumb.png</image>
<event>
<start>2015-08-04T20:00:00</start>
<location><link href="space.html">GCHQ</link>, Lingnerallee 3</location>
</event>
<p>
Nächste Woche Dienstag folgt die Fortsetzung auf den zweiten <link
href="news/ta-clean-code-live-coding.html">Themenabend Clean
Code.</link> Dieses Mal wird es wieder einen praktischen Teil
geben.
</p>
</item>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<item title="TA: Clean Code Live-Coding" date="2015-07-24T23:05:00" author="Astro">
<image title="Clean Code?" width="232" height="207" >cleancode-thumb.png</image>
<event>
<start>2015-07-30T20:00:00</start>
<location><link href="space.html">GCHQ</link>, Lingnerallee 3</location>
</event>
<addendum>
<p>
Nächste Woche Donnerstag folgt die Fortsetzung auf den ersten
<link href="news/ta-clean-code.html">Themenabend Clean
Code.</link> Dieses Mal wird es einen praktischen Teil geben.
</p>
<p>
Für die Inhalte von gestern gibt es jetzt übrigens <link
href="news/ta-clean-code.html">einen teilweisen Mitschnitt</link>
und <link
href="https://wiki.c3d2.de/Themenabend/Clean_Code#Inhalt">die
Inhalte im Wiki.</link>
</p>
</addendum>
<p>
Siehe auch: <link
href="http://www.mensch-und-maschine.de/2015/07/31/c3d2-c2-ii-clean-code-talk-live-coding/">C3D2
C2 II Clean Code Talk // Live Coding</link> auf
www.mensch-und-maschine.de
</p>
</item>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE item SYSTEM "http://www.c3d2.de/dtd/c3d2web.dtd">
<item title="TA: Clean Code" date="2015-07-13T18:14:00" author="Astro">
<image title="Clean Code?" width="232" height="207" >cleancode-thumb.png</image>
<event>
<start>2015-07-23T20:00:00</start>
<location><link href="space.html">GCHQ</link>, Lingnerallee 3</location>
</event>
<addendum>
<p>
Bald gibt es wieder einen spannenden Themenabend. Der Referent
schreibt:
</p>
<p class="blockquote">
Hallo, ich bin Nico Krebs, seit 2004 Chief Software Architect und
beschäftige mich mit Fragen zum sehr allgemeinen Thema "Bessere
Software". Konkret möchte ich aktuelle Antworten auf folgende
Fragen geben:
</p>
<ul>
<li>Was ist schlechter Code und warum?</li>
<li>Was ist "guter" Code?</li>
<li>Wie schreibt man guten Code?</li>
</ul>
<dl>
<dt>Literatur</dt>
<dd>Clean Code (Robert C. Martin)</dd>
</dl>
</addendum>
<resource title="Videomitschnitt"
type="video/mp4"
size="463345159"
url="http://ftp.c3d2.de/themenabend/ta-2015-07-23.mp4"/>
<p>
Die Inhalte gibts <link
href="https://wiki.c3d2.de/Themenabend/Clean_Code#Inhalt">im
Wiki.</link>
</p>
</item>

View File

@ -1,8 +0,0 @@
docs
example/.htaccess
.DS_Store
._*
.idea
.ndproj/Data
.ndproj/Menu.txt
node_modules

30
content/static/candy/CONTRIBUTING.md Normal file → Executable file
View File

@ -7,6 +7,8 @@
## Learn & listen
[![Gitter chat](https://badges.gitter.im/candy-chat.png)](https://gitter.im/candy-chat)
* [Mailing list](http://groups.google.com/group/candy-chat)
* yes, non-gmail users can signup as well
* [FAQ](https://github.com/candy-chat/candy/wiki/Frequently-Asked-Questions)
@ -21,8 +23,7 @@ A few hopefully helpful hints to contributing to Candy
#### Using vagrant
1. [Fork](https://help.github.com/articles/fork-a-repo) Candy
2. [Install Vagrant](http://vagrantup.com/)
3. Follow instructions [for Candy Vagrant](https://github.com/candy-chat/vagrant)
4. Change the remote in the `candy` and `candy-plugins` repos: `git remote set-url origin git://github.com/YOURNAME/candy` (or candy-plugins)
3. Run `vagrant up`.
5. Create a branch based on the `dev` branch (`git checkout -B my-awesome-feature`)
6. Run `grunt watch` to automatically run jshint (syntax checker) and the build of `candy.bundle.js` and `candy.min.js` while developing.
7. Make your changes, fix eventual *jshint* errors & push them back to your fork
@ -34,13 +35,22 @@ Please note that you should have a working XMPP server to test your changes (the
1. [Fork](https://help.github.com/articles/fork-a-repo) Candy
2. Clone your fork
2. Checkout out `dev` branch (`git checkout dev`) & Update git submodules `git submodule update --init`
3. Install [Node.js](http://nodejs.org/)
4. Install [Grunt](http://gruntjs.com/) (`npm install -g grunt-cli`)
5. Install npm dependencies (`npm install` in candy root directory)
6. Create a branch based on the `dev` branch (`git checkout -B my-awesome-feature`)
7. Run `grunt watch` to automatically run jshint (syntax checker) and the build of `candy.bundle.js` and `candy.min.js` while developing.
8. Make your changes, fix eventual *jshint* errors & push them back to your fork
9. Create a [pull request](https://help.github.com/articles/using-pull-requests)
3. Checkout out `dev` branch (`git checkout dev`)
4. Install [Node.js](http://nodejs.org/)
5. Install [Grunt](http://gruntjs.com/) (`npm install -g grunt-cli`)
6. Install [Bower](http://bower.io/) (`npm install -g bower`)
7. Install npm dependencies (`npm install` in candy root directory)
8. Install bower dependencies (`bower install` in candy root directory)
9. Create a branch based on the `dev` branch (`git checkout -B my-awesome-feature`)
10. Run `grunt watch` to automatically run jshint (syntax checker) and the build of `candy.bundle.js` and `candy.min.js` while developing.
11. Make your changes, fix eventual *jshint* errors & push them back to your fork
12. Create a [pull request](https://help.github.com/articles/using-pull-requests)
In case you have any questions, don't hesitate to ask on the [Mailing list](http://groups.google.com/group/candy-chat).
### Running tests
* Tests are run using [Intern](http://theintern.io).
* `grunt` and `grunt watch` will each run unit tests in Chrome on Linux (for fast feedback).
* `grunt test` will run both unit and integration tests in a variety of environments. Tests are run using Selenium Standalone and Phantom.JS while developing, and on Sauce Labs in CI or using `grunt test`.
* If you don't want to use the Vagrant box to run Selenium/PhantomJS, set `CANDY_VAGRANT='false'` to run tests.

View File

@ -1,13 +1,14 @@
'use strict';
var localInternConfig = process.env.CANDY_VAGRANT === 'false' ? 'tests/intern.local' : 'tests/intern.vagrant';
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jshint: {
all: ['Gruntfile.js', './src/**/*.js'],
all: ['Gruntfile.js', './src/**/*.js', './tests/**/*.js'],
options: {
jshintrc: "./.jshintrc",
reporter: require('jshint-stylish')
@ -30,8 +31,11 @@ module.exports = function(grunt) {
'src/candy.js', 'src/core.js', 'src/view.js',
'src/util.js', 'src/core/action.js',
'src/core/chatRoom.js', 'src/core/chatRoster.js',
'src/core/chatUser.js', 'src/core/event.js',
'src/view/observer.js', 'src/view/pane.js',
'src/core/chatUser.js', 'src/core/contact.js',
'src/core/event.js', 'src/view/observer.js',
'src/view/pane/chat.js', 'src/view/pane/message.js',
'src/view/pane/privateRoom.js', 'src/view/pane/room.js',
'src/view/pane/roster.js', 'src/view/pane/window.js',
'src/view/template.js', 'src/view/translation.js'
]
},
@ -53,14 +57,15 @@ module.exports = function(grunt) {
},
libs: {
files: {
'libs/libs.bundle.js': [
'libs/strophejs/strophe.js',
'libs/strophejs-plugins/muc/strophe.muc.js',
'libs/strophejs-plugins/disco/strophe.disco.js',
'libs/strophejs-plugins/caps/strophe.caps.jsonly.js',
'libs/mustache.js/mustache.js',
'libs/jquery-i18n/jquery.i18n.js',
'libs/dateformat/dateFormat.js'
'libs.bundle.js': [
'bower_components/strophe/strophe.js',
'bower_components/strophejs-plugins/muc/strophe.muc.js',
'bower_components/strophejs-plugins/roster/strophe.roster.js',
'bower_components/strophejs-plugins/disco/strophe.disco.js',
'bower_components/strophejs-plugins/caps/strophe.caps.jsonly.js',
'bower_components/mustache/mustache.js',
'bower_components/jquery-i18n/jquery.i18n.js',
'vendor_libs/dateformat/dateFormat.js'
]
},
options: {
@ -73,23 +78,38 @@ module.exports = function(grunt) {
},
'libs-min': {
files: {
'libs/libs.min.js': ['libs/libs.bundle.js']
'libs.min.js': ['libs.bundle.js']
}
}
},
watch: {
clear: {
files: ['src/*.js', 'src/**/*.js', 'tests/**/*.js'],
tasks: ['clear']
},
grunt: {
files: ['Gruntfile.js']
},
bundle: {
files: ['src/*.js', 'src/**/*.js'],
tasks: ['jshint', 'uglify:bundle', 'uglify:min', 'notify:bundle']
files: ['src/**/*.js'],
tasks: ['todo:src', 'jshint', 'uglify:bundle', 'uglify:min', 'notify:bundle', 'intern:unit']
},
libs: {
files: ['libs/*/**/*.js'],
files: ['bower_components/*/**/*.js', 'vendor_libs/*/**/*.js'],
tasks: ['uglify:libs', 'uglify:libs-min', 'notify:libs']
},
tests: {
files: ['tests/candy/unit/**/*.js'],
tasks: ['todo:tests', 'jshint', 'intern:unit']
},
functional_tests: {
files: ['tests/candy/functional/**/*.js'],
tasks: ['todo:tests', 'jshint', 'intern:functional']
}
},
natural_docs: {
all: {
bin: process.env.NATURALDOCS_DIR + '/NaturalDocs',
bin: process.env.NATURALDOCS_DIR ? process.env.NATURALDOCS_DIR + '/NaturalDocs' : 'naturaldocs',
flags: ['-r'],
inputs: ['./src'],
output: './docs',
@ -98,7 +118,7 @@ module.exports = function(grunt) {
},
clean: {
bundle: ['./candy.bundle.js', './candy.bundle.map', './candy.min.js'],
libs: ['./libs/libs.bundle.js', './libs/libs.bundle.map', './libs/libs.min.js'],
libs: ['./libs.bundle.js', './libs.bundle.map', './libs.min.js'],
docs: ['./docs']
},
mkdir: {
@ -129,6 +149,41 @@ module.exports = function(grunt) {
message: 'JsHint & bundling done'
}
}
},
intern: {
all: {
options: {
runType: 'runner',
config: 'tests/intern'
}
},
unit: {
options: {
runType: 'runner',
config: localInternConfig,
functionalSuites: []
}
},
functional: {
options: {
runType: 'runner',
config: localInternConfig,
suites: []
}
}
},
coveralls: {
options: {
force: true // prevent from failing CI build if coveralls is down etc.
},
all: {
src: 'lcov.info',
}
},
todo: {
options: {},
src: ['src/**/*.js'],
tests: ['tests/**/*.js']
}
});
@ -140,10 +195,16 @@ module.exports = function(grunt) {
grunt.loadNpmTasks('grunt-mkdir');
grunt.loadNpmTasks('grunt-notify');
grunt.loadNpmTasks('grunt-sync-pkg');
grunt.loadNpmTasks('intern');
grunt.loadNpmTasks('grunt-clear');
grunt.loadNpmTasks('grunt-coveralls');
grunt.loadNpmTasks('grunt-todo');
grunt.registerTask('test', ['intern:all']);
grunt.registerTask('ci', ['todo', 'jshint', 'build', 'intern:all', 'coveralls:all', 'docs']);
grunt.registerTask('build', ['uglify:libs', 'uglify:libs-min', 'uglify:bundle', 'uglify:min']);
grunt.registerTask('default', [
'jshint', 'uglify:libs', 'uglify:libs-min',
'uglify:bundle', 'uglify:min', 'notify:default'
'jshint', 'build', 'notify:default', 'intern:unit'
]);
grunt.registerTask('docs', ['mkdir:docs', 'natural_docs', 'notify:docs']);
};
};

View File

@ -1,6 +1,9 @@
Candy — a JavaScript-based multi-user chat client
==================================================
[![Build Status](https://travis-ci.org/candy-chat/candy.png?branch=dev)](https://travis-ci.org/candy-chat/candy)
[![Coverage Status](https://coveralls.io/repos/candy-chat/candy/badge.png?branch=dev)](https://coveralls.io/r/candy-chat/candy)
Visit the official project page: http://candy-chat.github.io/candy
Features
@ -11,7 +14,7 @@ Features
- 100% well-documented JavaScript source code
- Built for Jabber (XMPP), using famous technologies
- Used and approved in a productive environment with up to 400 concurrent users
- Works with all major web browsers including IE7
- Works with all major web browsers including IE9
Plugins
-------
@ -20,6 +23,6 @@ If you wish to add new functionality (to your candy installation) or contribute
Support & Community
-------------------
Take a look at our [FAQ](https://github.com/candy-chat/candy/wiki/Frequently-Asked-Questions). If it doesn't solve your questions, you're welcome to join our [Mailinglist on Google Groups](http://groups.google.com/group/candy-chat).
You don't need to have a Gmail account for it.
You don't need to have a Gmail account for it.
[![githalytics.com alpha](https://cruel-carlota.pagodabox.com/a41a8075608abeaf99db685d7ef29cf6 "githalytics.com")](http://githalytics.com/candy-chat/candy)

20
content/static/candy/Vagrantfile vendored Normal file
View File

@ -0,0 +1,20 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.network :forwarded_port, guest: 80, host: 8080
config.vm.network :forwarded_port, guest: 5280, host: 5280
config.vm.network :forwarded_port, guest: 4444, host: 4444
config.vm.network :private_network, ip: '192.168.88.4'
config.vm.provision :shell, :path => "devbox/provisioning.sh"
config.vm.provider "virtualbox" do |v|
v.name = "candy"
v.customize ["modifyvm", :id, "--memory", 768]
end
end

View File

@ -29,5 +29,12 @@
"bower_components",
"test",
"tests"
]
}
],
"dependencies": {
"jquery": "~1.10.2",
"strophe": "1.1.3",
"strophejs-plugins": "benlangfeld/strophejs-plugins#30fb089457addc37e01d69c3536dee868a90a9ad",
"mustache": "0.3.0",
"jquery-i18n": "1.1.1"
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,58 +1,77 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Candy - Chats are not dead yet</title>
<link rel="shortcut icon" href="res/img/favicon.png" type="image/gif" />
<link rel="stylesheet" type="text/css" href="res/default.css" />
<meta charset="utf-8">
<title>Candy - Chats are not dead yet</title>
<link rel="shortcut icon" href="res/img/favicon.png" type="image/gif" />
<link rel="stylesheet" type="text/css" href="res/default.css" />
<script type="text/javascript" src="libs/jquery/1.10.2/jquery.min.js"></script>
<script type="text/javascript" src="libs/libs.min.js"></script>
<script type="text/javascript" src="candy.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
function rid() {
return "" + Math.ceil(99999999 * Math.random());
}
var nick = "Candy-" + rid();
var m;
if ((m = window.location.search.match(/nick=([^\&]+)/))) {
nick = decodeURIComponent(m[1]);
}
Candy.init('https://jabber.c3d2.de:5281/http-bind/', {
core: {
// only set this to true if developing / debugging errors
debug: false,
// autojoin is a *required* parameter if you don't have a plugin (e.g. roomPanel) for it
// true
// -> fetch info from server (NOTE: does only work with openfire server)
// ['test@conference.example.com']
// -> array of rooms to join after connecting
resource: rid() + "-" + rid() + "-" + rid(),
autojoin: ["c3d2@chat.c3d2.de"]
},
view: { assets: 'res/' }
});
<script type="text/javascript" src="jquery/1.10.2/jquery.min.js"></script>
Candy.Core.connect("candy@jabber.c3d2.de", "yummy", nick);
<script type="text/javascript" src="libs.min.js"></script>
<script type="text/javascript" src="candy.min.js"></script>
/**
* Thanks for trying Candy!
*
* If you need more information, please see here:
* - Setup instructions & config params: http://candy-chat.github.io/candy/#setup
* - FAQ & more: https://github.com/candy-chat/candy/wiki
*
* Mailinglist for questions:
* - http://groups.google.com/group/candy-chat
*
* Github issues for bugs:
* - https://github.com/candy-chat/candy/issues
*/
});
</script>
<script type="text/javascript" src="plugins/timeago/candy.js"></script>
<link rel="stylesheet" type="text/css" href="plugins/timeago/candy.css" />
<script type="text/javascript" src="plugins/inline-images/candy.js"></script>
<link rel="stylesheet" type="text/css" href="plugins/inline-images/candy.css" />
<script type="text/javascript" src="plugins/inline-videos/candy.js"></script>
<script type="text/javascript" src="plugins/typingnotifications/typingnotifications.js"></script>
<link rel="stylesheet" type="text/css" href="plugins/typingnotifications/typingnotifications.css" />
<script type="text/javascript" src="plugins/namecomplete/candy.js"></script>
<link rel="stylesheet" type="text/css" href="plugins/namecomplete/candy.css" />
<script type="text/javascript" src="plugins/notifications/candy.js"></script>
<script type="text/javascript" src="plugins/notifyme/candy.js"></script>
<link rel="stylesheet" type="text/css" href="plugins/notifyme/candy.css" />
<script type="text/javascript" src="plugins/me-does/candy.js"></script>
<script type="text/javascript">
$(document).ready(function() {
function rid() {
return "" + Math.ceil(99999999 * Math.random());
}
var nick = "Candy-" + rid();
var m;
if ((m = window.location.search.match(/nick=([^\&]+)/))) {
nick = decodeURIComponent(m[1]);
}
Candy.init('https://c3d2.de/http-bind/', {
core: {
// only set this to true if developing / debugging errors
debug: false,
// autojoin is a *required* parameter if you don't have a plugin (e.g. roomPanel) for it
// true
// -> fetch info from server (NOTE: does only work with openfire server)
// ['test@conference.example.com']
// -> array of rooms to join after connecting
resource: rid(),
autojoin: ["c3d2@chat.c3d2.de"]
},
view: { assets: 'res/' }
});
CandyShop.Timeago.init();
CandyShop.NotifyMe.init();
CandyShop.InlineImages.init();
CandyShop.InlineVideos.init();
CandyShop.TypingNotifications.init();
CandyShop.NameComplete.init();
CandyShop.Notifications.init();
CandyShop.NotifyMe.init();
CandyShop.MeDoes.init();
Candy.Core.connect("anon.jabber.c3d2.de", null, nick);
});
</script>
</head>
<body>
<div id="candy"></div>
<div id="candy"></div>
</body>
</html>

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

3
content/static/candy/libs.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -40,14 +40,21 @@
"homepage": "http://candy-chat.github.io/candy/",
"devDependencies": {
"grunt": "^0.4.5",
"grunt-contrib-clean": "~0.5.0",
"grunt-clear": "^0.2.1",
"grunt-contrib-clean": "^0.5.0",
"grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-uglify": "^0.4.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-mkdir": "~0.1.1",
"grunt-natural-docs": "~0.1.1",
"grunt-coveralls": "^0.3.0",
"grunt-mkdir": "^0.1.1",
"grunt-natural-docs": "^0.1.1",
"grunt-notify": "^0.3.0",
"grunt-sync-pkg": "~0.1.2",
"jshint-stylish": "^0.2.0"
"grunt-sync-pkg": "^0.1.2",
"intern": "^2.0.1",
"jshint-stylish": "^0.2.0",
"sinon": "^1.10.3",
"sinon-chai": "^2.5.0",
"lolex": "^1.2.0",
"grunt-todo": "~0.4.0"
}
}

View File

@ -0,0 +1,7 @@
.DS_Store
._*
.idea
.*.sw*
bower_components
node_modules
lcov.info

View File

@ -0,0 +1,65 @@
'use strict';
var localInternConfig = process.env.CANDY_VAGRANT === 'false' ? 'tests/intern.local' : 'tests/intern.vagrant';
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
jshint: {
all: ['Gruntfile.js', '**/*.js', '!node_modules/**', '!bower_components/**'],
options: {
jshintrc: "./.jshintrc",
reporter: require('jshint-stylish')
}
},
watch: {
grunt: {
files: ['Gruntfile.js']
},
clear: {
files: ['**/*.js'],
tasks: ['clear', 'todo', 'jshint', 'intern:unit']
}
},
intern: {
all: {
options: {
runType: 'runner',
config: 'tests/intern'
}
},
unit: {
options: {
runType: 'runner',
config: localInternConfig,
functionalSuites: []
}
}
},
coveralls: {
options: {
force: true // prevent from failing CI build if coveralls is down etc.
},
all: {
src: 'lcov.info',
}
},
todo: {
options: {},
all: ['**/*.js', '!node_modules/**', '!bower_components/**']
}
});
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('intern');
grunt.loadNpmTasks('grunt-clear');
grunt.loadNpmTasks('grunt-coveralls');
grunt.loadNpmTasks('grunt-todo');
grunt.registerTask('test', ['intern:unit']);
grunt.registerTask('ci', ['todo', 'jshint', 'intern:all', 'coveralls:all']);
grunt.registerTask('default', ['jshint', 'intern:unit']);
};

View File

@ -0,0 +1,7 @@
Copyright (c) 2014 Misc
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,63 @@
# Candy Plugins
[![Build Status](https://travis-ci.org/candy-chat/candy-plugins.png)](https://travis-ci.org/candy-chat/candy-plugins)
[![Coverage Status](https://coveralls.io/repos/candy-chat/candy-plugins/badge.png)](https://coveralls.io/r/candy-chat/candy-plugins)
This is the official plugin repository for [Candy](http://candy-chat.github.com/candy), a JavaScript based multi-user chat client.
## List of available plugins
* __Auto-Join Invites__ - Automatically joins any and all incoming MUC invites.
* __available-rooms__ - A plugin to show & join public rooms.
* __Chat Recall__ - Saves the last {x} messages to scroll through with up and down arrows, similar to terminal/cmd.
* __Clearchat__ - Clears chat window on click or if typing `/clear`
* __Colors__ - Send and receive colored messages.
* __Colors XHTML__ - Send and receive colored messages formatted with XHTML.
* __Create Room__ - Creates a clickable UI for creating and joining rooms.
* __Emphasis__ - basic text formatting via textile, BBcode, or xhtml
* __Fullscreen Display__ - Shows incoming messages to specified users starting with @ + username + : as large as the browser's content area, overlaying everything else.
* __Inline Images__ - If a user posts a URL to an image, that image gets rendered directly inside of Candy.
* __Inline Videos__ - If a user posts a URL to youtube video, it embeds the youtube video iframe into Candy.
* __join__ A plugin that allows to type `/join room [password]` to join a room.
* __jQuery-Ui__ - jQuery UI lightness theme
* __Left Tabs__ - Moves the tabs to the left side and uses a bit of Bootstrap3-friendly theme elements.
* __Modify Role__ - Adds **add moderator** and **remove moderator** context menu links.
* __Me Does__ - special formatting for /me messages
* __Namecomplete__ - Autocompletes names of users within room
* __Nickchange__ - Enable your users to change the nick using a toolbar icon
* __Notifications__ - OS Notifications in webkit
* __Notifyme__ - Notifies yourself in case one does use your nickname in a message
* __Refocus__ - This plugin puts the focus on the entry box if the user clicks somewhere in the message list.
* __Remove Ignore__ - Removes the option to ignore/unignore a user from the roster.
* __Replies__ - Highlight any message that contains "@my_username"
* __MUC Room Bar__ - Adds a bar to the top of the message pane that displays the room topic and allows moderators to click-to-edit.
* __Room Panel__ - Provides a list of rooms available to join.
* __Static Lobby__ - Creates a static lobby UI and pulls in a global roster. Allows you to invite people from global roster to other MUCs you are participating in.
* __Sticky Subject__ - Retains the subject of the room underneath the tab itself.
* __Timeago__ - Replaces the exact time/date with fuzzy timestamps like "2 minutes ago".
* __Typing Notifications__ - Displays a user's typing notification status above the text entry form.
## Contributing
Please submit a pull request with your plugin or your changes to a plugin. We'll gladly merge it.
After a successful merge of a pull request, we will give you **push access** to this repository. You can then update your plugin on your own. If you update other plugins, please consider creating a pull request in order to inform the original plugin owner.
When contributing, please make sure that your code is of **high quality** and similar to other code in this repository. Also please submit a **screenshot** and a **README.md**.
1. [Setup the Vagrant environment from Candy core](https://github.com/candy-chat/candy/blob/dev/CONTRIBUTING.md)
2. Install [Node.js](http://nodejs.org/)
3. Install [Grunt](http://gruntjs.com/) (`npm install -g grunt-cli`)
4. Install [Bower](http://bower.io/) (`npm install -g bower`)
5. Install npm dependencies (`npm install` in candy-plugins root directory)
6. Install bower dependencies (`bower install` in candy-plugins root directory)
7. Run `grunt watch` to automatically run jshint (syntax checker) and the tests while developing.
### Running tests
* Tests are run using [Intern](http://theintern.io).
* `grunt` and `grunt watch` will each run unit tests in Chrome on Linux (for fast feedback).
* `grunt test` will run both unit and integration tests in a variety of environments. Tests are run using Selenium Standalone and Phantom.JS while developing, and on Sauce Labs in CI or using `grunt test`.
* If you don't want to use the Vagrant box to run Selenium/PhantomJS, set `CANDY_VAGRANT='false'` to run tests.
## Support & Community
Take a look at our [FAQ](https://github.com/candy-chat/candy/wiki/Frequently-Asked-Questions). If it doesn't solve your questions, you're welcome to join our [Mailinglist on Google Groups](http://groups.google.com/group/candy-chat).
You don't need to have a Gmail account for it.

View File

@ -0,0 +1,34 @@
{
"name": "candy-shop",
"version": "1.0.0",
"homepage": "http://candy-chat.github.io/candy/",
"authors": [
"Michael Weibel <michael.weibel@gmail.com>",
"Patrick Stadler <patrick.stadler@gmail.com>"
],
"description": "Multi-user XMPP web client plugins",
"main": [
],
"keywords": [
"xmpp",
"muc",
"multi-user",
"websocket",
"bosh",
"chat"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/candy-chat/candy-plugins.git"
},
"ignore": [
"node_modules",
"bower_components",
"tests"
],
"dependencies": {
"candy": "1.7.0",
"jquery": "~1.10.2"
}
}

View File

@ -0,0 +1,24 @@
# Inline Images
If a user posts a URL to an image, that image gets rendered directly inside of Candy.
![Inline Images](screenshot.png)
## Usage
Include the JavaScript and CSS files:
```HTML
<script type="text/javascript" src="candyshop/inline-images/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/inline-images/candy.css" />
```
To enable the Inline Images plugin, just add one of the ´init´ methods to your bootstrap:
```JavaScript
// init with default settings:
CandyShop.InlineImages.init();
// customized initialization:
CandyShop.InlineImages.initWithFileExtensions(['png','jpg']); // only recognize PNG and JPG files as image
CandyShop.InlineImages.initWithMaxImageSize(150); // resize images to a maximum edge size of 150px
CandyShop.InlineImages.initWithFileExtensionsAndMaxImageSize(['png','jpg'], 150); // combination of the above examples
```

View File

@ -0,0 +1,13 @@
.inlineimages-link {
text-decoration:none;
position:relative;
display:inline-block;
}
.inlineimages-link:hover:before {
content: url('overlay.png');
position: absolute;
top: 5px;
left: 5px;
opacity: .8;
}

View File

@ -0,0 +1,199 @@
/*
* inline-images
* @version 1.0
* @author Manuel Alabor (manuel@alabor.me)
* @author Jonatan Männchen <jonatan@maennchen.ch>
*
* If a user posts a URL to an image, that image gets rendered directly
* inside of Candy.
*/
/* global Candy, jQuery, Image */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.InlineImages = (function(self, Candy, $) {
var _options = {
fileExtensions: ['png','jpg','jpeg','gif']
, maxImageSize: 100
, noInlineSizing: false
};
/** Function: init
* Initializes the inline-images plugin with the default settings.
*/
self.init = function(options) {
// Apply the supplied options to the defaults specified
$.extend(true, _options, options);
$(Candy).on('candy:view.message.before-show', handleBeforeShow);
$(Candy).on('candy:view.message.after-show', handleOnShow);
};
/** Function: initWithFileExtensions
* Initializes the inline-images plugin with the possibility to pass an
* array with all the file extensions you want to display as image.
*
* Parameters:
* (String array) fileExtensions - Array with extensions (jpg, png, ...)
*/
self.initWithFileExtensions = function(fileExtensions) {
_options.fileExtensions = fileExtensions;
self.init();
};
/** Function: initWithMaxImageSize
* Initializes the inline-images plugin with the possibility to pass the
* maximum image size for displayed images.
*
* Parameters:
* (int) maxImageSize - Maximum edge size for images
*/
self.initWithMaxImageSize = function(maxImageSize) {
_options.maxImageSize = maxImageSize;
self.init();
};
/** Function: initWithFileExtensionsAndMaxImageSize
* Initializes the inline-images plugin with the possibility to pass an
* array with all the file extensions you want to display as image and
* the maximum image size for displayed images.
*
* Parameters:
* (String array) fileExtensions - Array with extensions (jpg, png, ...)
* (int) maxImageSize - Maximum edge size for images
*/
self.initWithFileExtensionsAndMaxImageSize = function(fileExtensions, maxImageSize) {
_options.fileExtensions = fileExtensions;
_options.maxImageSize = maxImageSize;
self.init();
};
/** Function: handleBeforeShow
* Handles the beforeShow event of a message.
*
* Paramteres:
* (Object) args - {roomJid, element, nick, message}
*
* Returns:
* (String)
*/
var handleBeforeShow = function(e, args) {
args.message = replaceLinksWithLoaders(args.message);
if (args.xhtmlMessage) {
args.xhtmlMessage = replaceLinksWithLoaders(args.xhtmlMessage);
}
return true;
};
/** Function replaceLinksWithLoaders
* Replaces anchor tags with image loader elements where applicable
*
* Parameters:
* (String) message
*
* Returns:
* (String) the replaced message
*/
var replaceLinksWithLoaders = function(message) {
var dummyContainer = document.createElement('div');
dummyContainer.innerHTML = message;
$(dummyContainer).find('a').each(function(index, anchor) {
if (anchorHasMatchingFileExtension(anchor)) {
anchor.innerHTML = buildImageLoaderSource(anchor.href);
}
});
return dummyContainer.innerHTML;
};
/** Function anchorHasMatchingFileExtension
* Identifies whether or not an anchor tag links to a file with one of the matching extensions we're looking for
*
* Parameters:
* (Element) element
*
* Returns:
* (true, false)
*/
var anchorHasMatchingFileExtension = function(element) {
var dotPosition = element.pathname.lastIndexOf(".");
if(dotPosition > -1) {
if(_options.fileExtensions.indexOf(element.pathname.substr(dotPosition+1)) != -1) {
return true;
}
}
return false;
};
/** Function: handleOnShow
* Each time a message gets displayed, this method checks for possible
* image loaders (created by buildImageLoaderSource).
* If there is one, the image "behind" the loader gets loaded in the
* background. As soon as the image is loaded, the image loader gets
* replaced by proper scaled image.
*
* Parameters:
* (Array) args
*/
var handleOnShow = function(e, args) {
$('.inlineimages-loader').each(function(index, element) {
$(element).removeClass('inlineimages-loader');
var url = $(element).attr('longdesc');
var imageLoader = new Image();
$(imageLoader).load(function() {
var origWidth = this.width;
var origHeight = this.height;
if(origWidth > _options.maxImageSize || origHeight > _options.maxImageSize) {
var ratio = Math.min(_options.maxImageSize / origWidth, _options.maxImageSize / origHeight);
var width = Math.round(ratio * origWidth);
var height = Math.round(ratio * origHeight);
}
$(element).replaceWith(buildImageSource(url, width, height))
});
imageLoader.src = url;
});
};
/** Function: buildImageLoaderSource
* Returns a loader indicator. The handleOnShow method fullfills afterwards
* the effective image loading.
*
* Parameters:
* (String) url - image url
*
* Returns:
* (String)
*/
var buildImageLoaderSource = function(url) {
return '<img class="inlineimages-loader" longdesc="' + url + '" src="ui/candy-plugins/inline-images/spinner.gif" />';
};
/** Function: buildImageSource
* Returns HTML source to show a URL as an image.
*
* Parameters:
* (String) url - image url
*
* Returns:
* (String)
*/
var buildImageSource = function(url, width, height) {
if (_options.noInlineSizing) {
return '<img src="' + url + '" />';
} else {
return '<img src="' + url + '" width="' + width + '" height="' + height + '"/>';
}
};
return self;
}(CandyShop.InlineImages || {}, Candy, jQuery));

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,19 @@
# Inline Videos Plugin
If a user posts a URL to a youtube video, that video gets rendered directly inside of Candy.
## Usage
Include the JavaScript file:
```HTML
<script type="text/javascript" src="path_to_plugins/inline-videos/candy.js"></script>
```
Call its `init()` method after Candy has been initialized:
```JavaScript
Candy.init('/http-bind/');
CandyShop.InlineVideos.init();
Candy.Core.connect();
```

View File

@ -0,0 +1,42 @@
/** File: candy.js
* Candy - Chats are not dead yet.
*
* Authors
* - Jonatan Männchen <jonatan.maennchen@amiadogroup.com>
*/
/* global Candy, jQuery */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
/** Class: InlineVideos
* If a user posts a URL to a video, that video gets rendered directly
* inside of Candy.
*/
CandyShop.InlineVideos = (function(self, Candy, $) {
/** Function: init
* Initializes the inline-videos plugin with the default settings.
*
* Parameters:
* (Object) options - An options packet to apply to this plugin
*/
self.init = function() {
// add a listener to these events
$(Candy).on('candy:view.message.before-show', self.handleBeforeShow);
};
/** Function: handleBeforeShow
* Handles the beforeShow event of a message.
*
* Parameters:
* (String) message - the message to process
*
* Returns:
* (String)
*/
self.handleBeforeShow = function(e, args) {
args.message = args.message.replace(/\>(https?:\/\/w{0,3}\.?youtube.com\/watch\?v=([^\s^&]*)([^\s]*))\<\/a\>/i, '>$1<br /><iframe width="300" height="200" src="//www.youtube.com/embed/$2" frameborder="0" allowfullscreen></iframe></a><br />');
};
return self;
}(CandyShop.InlineVideos || {}, Candy, jQuery));

View File

@ -0,0 +1,10 @@
#Candy jQuery UI lightness Theme plugin
This plugin replaces the default theme with the jQuery UI lightness Theme. (http://jqueryui.com/)
##Usage
To enable jQuery UI lightness Theme you have to include its stylesheet:
```html
<link rel="stylesheet" type="text/css" href="candy/plugins/jquery-ui/ui-lightness/css/ui-lightness.css" />
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -0,0 +1,115 @@
html, body {
font-family: Trebuchet MS,Tahoma,Verdana,Arial,sans-serif;
}
div#candy {
background-color: #EEE;
}
div#candy div#chat-pane ul#chat-tabs {
background-image: url('../images/ui-bg_gloss-wave_35_f6a828_500x100.png');
background-repeat: repeat-x;
background-color: #F6A828;
}
div#candy div#chat-pane ul#chat-tabs li {
border-right: 1px solid #CCC;
}
div#candy div#chat-pane ul#chat-tabs li a {
color: #1C94C4;
}
div#candy div#chat-pane ul#chat-tabs li.active a {
color: #E78F08;
}
div#candy div#chat-pane ul#chat-tabs li.roomtype-chat small.unread {
background-color: #F6A828;
}
div#candy div#chat-pane ul#chat-tabs li.roomtype-groupchat small.unread {
background-color: #F6A828;
}
div#candy div#chat-pane ul#chat-toolbar {
background-color: #EEE;
border-top: 1px solid #DDD;
}
div#candy div#chat-pane div#chat-rooms.rooms div.room-pane form.message-form input.submit {
padding: 2px 5px 5px 5px;
background-color: #F6F6F6;
border: 1px solid #CCC;
border-radius: 4px;
color: #1C94C4;
}
div#candy div#chat-pane div#chat-rooms.rooms div.room-pane form.message-form input.submit:hover {
background-color: #FDF9E1;
border: 1px solid #FBCB09;
color: #E78F08;
}
div#candy div#chat-pane div#chat-rooms.rooms div.room-pane div.message-form-wrapper {
border-top: 1px solid #DDD;
}
div#candy div#chat-pane div#chat-rooms.rooms div.room-pane div.message-pane-wrapper dl.message-pane dd span.label a.name {
color: #888;
}
div#candy div#chat-pane div#chat-rooms.rooms div.room-pane div.message-pane-wrapper dl.message-pane dd.adminmessage {
color: #000;
}
div#candy div#chat-pane div#chat-rooms.rooms div.room-pane div.message-pane-wrapper dl.message-pane dd.subject {
color: #E78F08;
}
div#candy div#chat-pane div#chat-rooms.rooms div.room-pane div.roster-pane div.user {
background-color: #F8F8F8;
border: 1px solid #CCC;
border-radius: 4px;
color: #1C94C4;
}
div#candy div#chat-pane div#chat-rooms.rooms div.room-pane div.roster-pane div.user:hover {
cursor: pointer;
background-color: #FDF9E1;
border: 1px solid #FBCB09;
color: #E78F08;
}
div#candy div#chat-pane div#chat-rooms.rooms div.room-pane div.roster-pane ul li.context {
background-image: url('../images/action/menu.png');
}
div#candy div#chat-pane div#chat-rooms.rooms div.room-pane div.roster-pane ul li.context:hover {
background-image: url('../images/action/menu-hover.png');
background-color: #F6A828;
}
#context-menu {
position: absolute;
z-index: 10;
display: none;
padding: 15px 10px;
margin: 8px -28px -8px -12px;
background: url('../images/context-arrows.gif') no-repeat left bottom;
}
#context-menu ul {
background-color: #F8F8F8;
border: 1px solid #CCC;
border-radius: 4px;
color: #1C94C4;
}
#context-menu li:hover {
background-color: #FDF9E1 !important;
color: #E78F08;
}
#chat-modal {
background: url('../images/modal-bg.png');
color: #000;
}
#chat-modal a#admin-message-cancel.close {
color: #000;
}
#chat-modal a#admin-message-cancel.close:hover {
background-color: #F6A828;
color: #FFF;
}
#chat-modal-overlay {
background-image: url('../images/overlay.png');
filter:alpha(opacity=50);
opacity: 0.5;
-moz-opacity:0.5;
}
#tooltip {
background: url('../images/tooltip-arrows.gif') no-repeat left bottom;
}
#tooltip div {
background-color: #F6A828;
color: #FFF;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,31 @@
# /me Does
Format /me messages, implementing XEP-0245
## Usage
Include the JavaScript file:
```HTML
<script type="text/javascript" src="candyshop/me-does/candy.js"></script>
```
Call its `init()` method after Candy has been initialized:
```javascript
Candy.init('/http-bind/', {});
// enable /me handling
CandyShop.MeDoes.init();
Candy.Core.connect();
```
Now all messages starting with '/me 'will use infoMessage formatting.
```
/me takes screenshot
```
![Color Picker](me-does-screenshot.png)
**Please note**: As `me-does` reroutes message output, it's call to `init()` should happen after the `init()` of most other plugins, including, `inline-images`.

View File

@ -0,0 +1,17 @@
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.MeDoes = (function(self, Candy, $) {
self.init = function() {
$(Candy).on("candy:view.message.before-show", function(e, args) {
if (args && args.message && args.message.match(/^\/me /i)) {
var message = args.message.match(/^\/([^\s]+)(?:\s+(.*))?$/m)[2];
Candy.View.Pane.Chat.infoMessage(args.roomJid, null, '<span><strong>' + args.name + '</strong> ' + message + '</span>');
return false;
}
});
};
return self;
}(CandyShop.MeDoes || {}, Candy, jQuery));

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1,29 @@
# Modify role
Adds **add moderator** and **remove moderator** privilege links to context menu.
![Modify role screenshot](screenshot.png)
## Usage
To enable *Modify role* you have to include its JavaScript code and stylesheet:
```HTML
<script type="text/javascript" src="candyshop/modify-role/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/modify-role/candy.css" />
```
Call its `init()` method after Candy has been initialized:
```JavaScript
Candy.init('/http-bind/');
// enable ModifyRole plugin
CandyShop.ModifyRole.init();
Candy.Core.connect();
```
## Credits
Thanks to [famfamfam silk icons](http://www.famfamfam.com/lab/icons/silk/) for the icons.
## License
MIT

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,6 @@
#context-menu .add-moderator {
background-image: url(add-moderator.png);
}
#context-menu .remove-moderator {
background-image: url(remove-moderator.png);
}

View File

@ -0,0 +1,97 @@
/** File: candy.js
* Plugin for modifying roles. Currently implemented: op & deop
*
* Authors:
* - Michael Weibel <michael.weibel@gmail.com>
*
* License: MIT
*
* Copyright:
* (c) 2014 Michael Weibel. All rights reserved.
*/
/* global Candy, jQuery, Strophe, $iq */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
/** Class: CandyShop.ModifyRole
* Remove the ignore option in the roster
*/
CandyShop.ModifyRole = (function(self, Candy, $) {
var modifyRole = function modifyRole(role, roomJid, user) {
var conn = Candy.Core.getConnection(),
nick = user.getNick(),
iq = $iq({
'to': Candy.Util.escapeJid(roomJid),
'type': 'set'
});
iq.c('query', {'xmlns': Strophe.NS.MUC_ADMIN})
.c('item', {'nick': nick, 'role': role});
conn.sendIQ(iq.tree());
};
var applyTranslations = function applyTranslations() {
var addModeratorActionLabel = {
'en' : 'Grant moderator status',
'de' : 'Moderator status geben'
};
var removeModeratorActionLabel = {
'en' : 'Remove moderator status',
'de' : 'Moderator status nehmen'
};
$.each(addModeratorActionLabel, function(k, v) {
if(Candy.View.Translation[k]) {
Candy.View.Translation[k].addModeratorActionLabel = v;
}
});
$.each(removeModeratorActionLabel, function(k, v) {
if(Candy.View.Translation[k]) {
Candy.View.Translation[k].removeModeratorActionLabel = v;
}
});
};
var isOwnerOrAdmin = function(user) {
return ['owner', 'admin'].indexOf(user.getAffiliation()) !== -1;
};
var isModerator = function(user) {
return user.getRole() === 'moderator';
};
/** Function: init
* Initializes the plugin by adding an event which modifies
* the contextmenu links.
*/
self.init = function init() {
applyTranslations();
$(Candy).bind('candy:view.roster.context-menu', function(e, args) {
args.menulinks.addModerator = {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && isOwnerOrAdmin(me) && !isOwnerOrAdmin(user) && !isModerator(user);
},
'class' : 'add-moderator',
'label' : $.i18n._('addModeratorActionLabel'),
'callback' : function(e, roomJid, user) {
modifyRole('moderator', roomJid, user);
}
};
args.menulinks.removeModerator = {
requiredPermission: function(user, me) {
return me.getNick() !== user.getNick() && isOwnerOrAdmin(me) && !isOwnerOrAdmin(user) && isModerator(user);
},
'class' : 'remove-moderator',
'label' : $.i18n._('removeModeratorActionLabel'),
'callback' : function(e, roomJid, user) {
modifyRole('participant', roomJid, user);
}
};
});
};
return self;
}(CandyShop.ModifyRole || {}, Candy, jQuery));

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,26 @@
# MUC Room Bar
A plugin for Candy Chat to enable a room bar that displays the room topic and allows moderators to edit it with a click, as well as adds a button to pop up a GUI for inviting users to a MUC.
## Dependencies
Depends on `CandyShop.StaticLobby` for its `Invite` object's `Send` method.
With LeftTabs plugin:
![MUC Room Bar with LeftTabs Plugin](screenshot-left.png)
Without LeftTabs plugin:
![MUC Room Bar without LeftTabs Plugin](screenshot-normal.png)
## Usage
Include the JavaScript and CSS files for the plugin:
```HTML
<script type="text/javascript" src="candyshop/mucroombar/mucroombar.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/mucroombar/mucroombar.css" />
```
Also be sure to include [Twitter Typeahead](https://github.com/twitter/typeahead.js)'s packaged JS file (includes Bloodhound).
To enable this plugin, add its `init` method after you `init` Candy:
```JavaScript
CandyShop.RoomBar.init();
```

View File

@ -0,0 +1,81 @@
.roombar {
background-color: #1763b0;
border-bottom: 1px solid #e7e7e7;
height: 30px;
padding: 2px 3px;
width: 100%;
z-index: 2;
overflow: hidden;
margin-top: -30px;
}
.roombar .topic {
color: white;
cursor: pointer;
float: left;
font-weight: 1.5em;
font-size: 1.2em;
height: 100%;
padding-left: 5px;
width: 100%;
}
.roombar input[type="text"] {
background-color: rgba(255,255,255,0.1);
border: 1px solid rgba(0,0,0,0.1);
bottom: 1px;
font-weight: 100;
letter-spacing: 2px;
padding: 0 3px;
position: relative;
width: 100%;
}
.message-pane-wrapper {
padding-top: 30px;
margin-bottom: -30px;
padding-bottom: 30px;
}
#chat-rooms .roster-wrapper .pane-heading .invite-users {
float: right;
}
.tt-dropdown-menu {
background: white;
width: 100%;
}
#invite-users-muc input:disabled {
display: none;
}
#invite-users-muc input {
background: white !important;
}
.tagholder {
max-height: 150px;
overflow-y: scroll;
}
.input-tag {
background-color: rgba(23, 99, 176, 0.5);
border: 1px solid rgba(23, 99, 176, 0.69);
border-radius: 6px;
color: white;
cursor: default;
display: block;
margin: 2px;
padding: 2px;
text-shadow: 1px 1px 1px rgba(0,0,0,0.2);
white-space: nowrap;
}
.input-tag .close-input-tag {
padding-left: 10px;
}
.tt-cursor {
background-color: rgba(23,90,176,0.2);
}

View File

@ -0,0 +1,211 @@
/** File: mucroombar.js
* Candy Plugin Auto-Join Incoming MUC Invites
* Author: Melissa Adamaitis <madamei@mojolingo.com>
* Dependency: CandyShop.StaticLobby
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.RoomBar = (function(self, Candy, $) {
/** Object: about
*
* Contains:
* (String) name - Candy Plugin Add MUC Management Bar
* (Float) version - Candy Plugin Add MUC Management Bar
*/
self.about = {
name: 'Candy Plugin Add MUC Management Bar',
version: '1.0'
};
/**
* Initializes the RoomBar plugin with the default settings.
*/
self.init = function() {
// Add a room bar when the room is first created.
$(Candy).on('candy:view.room.after-show', function(ev, obj) {
CandyShop.RoomBar.addRoomBar(obj);
CandyShop.RoomBar.appendInviteUsersButton(obj.roomJid);
return undefined;
});
// Change the topic in the roombar when it is changed.
$(Candy).on('candy:view.room.after-subject-change', function(ev, obj) {
CandyShop.RoomBar.showTopic(obj.subject, obj.element);
});
// Remove the now-useless "Change Subject" menu item
$(Candy).on('candy:view.roster.context-menu', function (ev, obj) {
delete obj.menulinks.subject;
});
};
self.addRoomBar = function(obj){
if($('div.room-pane.roomtype-groupchat[data-roomjid="' + obj.roomJid + '"] .message-pane-wrapper .roombar').length === 0) {
var roombarHtml = self.Template.roombar;
$('div.room-pane.roomtype-groupchat[data-roomjid="' + obj.roomJid + '"] .message-pane-wrapper').prepend(roombarHtml);
}
$('#' + obj.element.context.id + ' .message-pane-wrapper .roombar .topic').click(function() {
self.updateRoomTopic(obj.roomJid, obj.element.context.id, $(this).html());
});
};
self.showTopic = function(topic, element) {
$(element).find(' .message-pane-wrapper .roombar .topic').html(topic);
};
self.updateRoomTopic = function(roomJid, elementId, currentTopic) {
// If we're a room moderator, be able to edit the room topic.
if(Candy.Core.getRoom(roomJid) !== null && Candy.Core.getRoom(roomJid).user !== null && Candy.Core.getRoom(roomJid).user.getRole() === 'moderator') {
// If there isn't an active input for room topic already, create input interface.
if($('#' + elementId + ' .message-pane-wrapper .roombar .topic input').length === 0) {
// Replace topic with an input field
if(currentTopic === ' ') { currentTopic = ''; }
var fieldHtml = '<input type="text" value="' + currentTopic + '" />';
$('#' + elementId + ' .message-pane-wrapper .roombar .topic').html(fieldHtml);
// Add focus to the new element.
$('#' + elementId + ' .message-pane-wrapper .roombar .topic input').focus();
// Set listener for on return press or lose focus.
$('#' + elementId + ' .message-pane-wrapper .roombar .topic input').blur(function() {
if(currentTopic !== $(this).val()) {
CandyShop.RoomBar.sendNewTopic(roomJid, $(this).val());
} else {
$('#' + elementId + ' .message-pane-wrapper .roombar .topic').html(currentTopic);
}
});
$('#' + elementId + ' .message-pane-wrapper .roombar .topic input').keypress(function(ev) {
var keycode = (ev.keyCode ? ev.keyCode : ev.which);
if(keycode === 13) {
if(currentTopic !== $(this).val()) {
CandyShop.RoomBar.sendNewTopic(roomJid, $(this).val());
} else {
$('#' + elementId + ' .message-pane-wrapper .roombar .topic').html(currentTopic);
}
}
});
}
}
};
self.appendInviteUsersButton = function(roomJid) {
var paneHeading = $('#chat-rooms > div.roomtype-groupchat[data-roomjid="' + roomJid + '"] .roster-wrapper .pane-heading');
if ($(paneHeading).find('.invite-users').length === 0) {
var html = self.Template.inviteButton;
$(paneHeading).append(html);
$(paneHeading).find('.invite-users').click(function() {
// Pop up a modal with an invite-users dialogue.
Candy.View.Pane.Chat.Modal.show(Mustache.to_html(self.Template.inviteModal, {
roomjid: roomJid
}), true, false);
self.centerModal(true);
// Bloodhound suggestion engine
var bhUsers = new Bloodhound({
name: 'users',
local: $.map(Candy.Core.getRoster().items, function(item) {
return { name: item.getName(), jid: item.getJid() };
}),
datumTokenizer: function(d) {
return Bloodhound.tokenizers.whitespace(d.name);
},
queryTokenizer: Bloodhound.tokenizers.whitespace
});
bhUsers.initialize();
// Typeahead UI
$('#users-input').typeahead({
itemValue: 'jid',
itemText: 'name',
hint: true,
highlight: true,
minLength: 1
},{
name: 'users',
displayKey: 'name',
source: bhUsers.ttAdapter()
});
// Add a new place for tags to go
$('#users-input').before(self.Template.tagholder);
// Bind the selection event for typeahead.
$('#users-input').bind('typeahead:selected', function(ev, suggestion) {
// Append the tag
if ($('.tagholder .input-tag[data-userjid="' + suggestion.jid + '"]').length === 0) {
$('.tagholder').append(Mustache.to_html(self.Template.tag, {
userjid: suggestion.jid,
username: suggestion.name
}));
}
$('#users-input').val('');
self.centerModal();
$('.tagholder').scrollTop($('.tagholder').height());
// Add remove button click handler
$('.tagholder .input-tag .close-input-tag').click(function() {
$(this).parent().remove();
});
});
// Form submission handler
$('#invite-users-muc').submit(function(ev) {
ev.preventDefault();
// Get all of the users chosen.
var userTags = $('.tagholder .input-tag');
// Send them invites.
for (var i = 0; i < userTags.length; i++) {
CandyShop.StaticLobby.Invite.Send($(userTags[i]).attr('data-userjid'), roomJid);
$('.tagholder .input-tag[data-userjid="' + $(userTags[i]).attr('data-userjid') + '"]').remove();
}
Candy.View.Pane.Chat.Modal.hide();
return false;
});
});
}
};
self.centerModal = function(first) {
// Center the modal better
var windowHeight = $(window).height(),
windowWidth = $(window).width(),
objectHeight = $('#chat-modal').outerHeight(),
objectWidth = $('#chat-modal').outerWidth(),
newTop = (windowHeight / 2) - (objectHeight / 2),
newLeft = (windowWidth / 2) + (objectWidth / 2);
if (first) {
$('#chat-modal').css({
left: newLeft,
top: newTop
});
} else {
$('#chat-modal').animate({
left: newLeft,
top: newTop
}, 'fast');
}
};
// Display the set topic modal and add submit handler.
self.sendNewTopic = function(roomJid, topic) {
if(topic === '') { topic = ' '; }
// Even though it does the exact same thing, Candy.View.Pane.Room.setSubject(roomJid, topic) was not sending the stanza out.
Candy.Core.getConnection().muc.setTopic(Candy.Util.escapeJid(roomJid), topic);
};
self.Template = {
tagholder: '<div class="tagholder"></div>',
tag: '<span class="input-tag" data-userjid={{userjid}}>{{username}}<span class="close-input-tag">x</span></span>',
roombar: '<div class="roombar"><div class="topic"></div></div>',
inviteButton: '<button class="invite-users btn btn-default btn-sm">Invite Users</button>',
inviteModal: '<h4>Invite Users</h4><form id="invite-users-muc" data-roomjid={{roomjid}}><div class="form-group">' +
'<input type="text" name="bhUsers" class="tm-input form-control" ' +
'id="users-input"/></div><button class="btn btn-default" type="submit">Send Invitations</button></form>'
};
return self;
}(CandyShop.RoomBar || {}, Candy, jQuery));

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

@ -0,0 +1,31 @@
# Name completion plugin
This plugin will complete the names of users in the room when a specified key is pressed.
### Usage
<script type="text/javascript" src="path_to_plugins/namecomplete/candy.js"></script>
<link rel="stylesheet" type="text/css" href="path_to_plugins/namecomplete/candy.css" />
...
CandyShop.NameComplete.init();
### Configuration options
`nameIdentifier` - String - The identifier to look for in a string. Defaults to `'@'`
`completeKeyCode` - Integer - The key code of the key to use. Defaults to `9` (tab)
### Example configurations
// complete the name when the user types +nick and hits the right arrow
// +troymcc -> +troymccabe
CandyShop.NameComplete.init({
nameIdentifier: '+',
completeKeyCode: '39'
});
// complete the name when the user types -nick and hits the up arrow
// +troymcc ^ +troymccabe
CandyShop.NameComplete.init({
nameIdentifier: '-',
completeKeyCode: '38'
});

View File

@ -0,0 +1,7 @@
#context-menu li.selected {
background-color: #ccc;
}
#context-menu li.candy-namecomplete-option {
padding: 3px 5px;
}

View File

@ -0,0 +1,260 @@
/** File: candy.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Troy McCabe <troy.mccabe@geeksquad.com>
* - Ben Klang <bklang@mojolingo.com>
*
* Copyright:
* (c) 2012 Geek Squad. All rights reserved.
* (c) 2014 Power Home Remodeling Group. All rights reserved.
*/
/* global document, Candy, jQuery */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
/** Class: CandyShop.NameComplete
* Allows for completion of a name in the roster
*/
CandyShop.NameComplete = (function(self, Candy, $) {
/** Object: _options
* Options:
* (String) nameIdentifier - Prefix to append to a name to look for. '@' now looks for '@NICK', '' looks for 'NICK', etc. Defaults to '@'
* (Integer) completeKeyCode - Which key to use to complete
*/
var _options = {
nameIdentifier: '@',
completeKeyCode: 9
};
/** Array: _nicks
* An array of nicks to complete from
* Populated after 'candy:core.presence'
*/
var _nicks = [];
/** String: _selector
* The selector for the visible message box
*/
var _selector = 'input[name="message"]:visible';
/** Boolean:_autocompleteStarted
* Keeps track of whether we're in the middle of autocompleting a name
*/
var _autocompleteStarted = false;
/** Function: init
* Initialize the NameComplete plugin
* Show options for auto completion of names
*
* Parameters:
* (Object) options - Options to apply to this plugin
*/
self.init = function(options) {
// apply the supplied options to the defaults specified
$.extend(true, _options, options);
// listen for keydown when autocomplete options exist
$(document).on('keypress', _selector, function(e) {
if (e.which === _options.nameIdentifier.charCodeAt()) {
_autocompleteStarted = true;
}
if (_autocompleteStarted) {
// update the list of nicks to grab
self.populateNicks();
// set up the vars for this method
// break it on spaces, and get the last word in the string
var field = $(this);
var msgParts = field.val().split(' ');
var lastWord = new RegExp( "^" + msgParts[msgParts.length - 1] + String.fromCharCode(e.which), "i");
var matches = [];
// go through each of the nicks and compare it
$(_nicks).each(function(index, item) {
// if we have results
if (item.match(lastWord) !== null) {
matches.push(item);
}
});
// if we only have one match, no need to show the picker, just replace it
// else show the picker of the name matches
if (matches.length === 1) {
self.replaceName(matches[0]);
// Since the name will be autocompleted, throw away the last character
e.preventDefault();
} else if (matches.length > 1) {
self.showPicker(matches, field);
}
}
});
};
/** Function: keyDown
* The listener for keydown in the menu
*/
self.keyDown = function(e) {
// get the menu and the content element
var menu = $('#context-menu');
var content = menu.find('ul');
var selected = content.find('li.selected');
if(menu.css('display') === 'none') {
$(document).unbind('keydown', self.keyDown);
return;
}
// switch the key code
switch (e.which) {
// up arrow
case 38:
// down arrow
case 40:
var newEl;
if (e.which === 38) {
// move the selected thing up
newEl = selected.prev();
} else {
// move the selected thing down
newEl = selected.next();
}
// Prevent going off either end of the list
if ($(newEl).length > 0) {
selected.removeClass('selected');
newEl.addClass('selected');
}
// don't perform any key actions
e.preventDefault();
break;
// esc key
case 27:
// delete Key
case 8:
case 46:
self.endAutocomplete();
break;
// the key code for completion
case _options.completeKeyCode:
case 13:
// get the text of the selected item
var val = content.find('li.selected').text();
// replace the last item with the selected item
self.replaceName(val);
// don't perform any key actions
e.preventDefault();
break;
}
};
/** Function: endAutocomplete
* Disables autocomplete mode, hiding the context menu
*/
self.endAutocomplete = function() {
_autocompleteStarted = false;
$(_selector).unbind('keydown', self.keyDown);
$('#context-menu').hide();
};
/** Function: selectOnClick
* The listener for click on decision in the menu
*
* Parameters:
* (Event) e - The click event
*/
self.selectOnClick = function(e) {
self.replaceName($(e.currentTarget).text());
$(_selector).focus();
e.preventDefault();
};
/** Function: populateNicks
* Populate the collection of nicks to autocomplete from
*/
self.populateNicks = function() {
// clear the nick collection
_nicks = [];
// grab the roster in the current room
var room = Candy.Core.getRoom(Candy.View.getCurrent().roomJid);
if (room !== null) {
var roster = room.getRoster().getAll();
// iterate and add the nicks to the collection
$.each(roster, function(index, item) {
_nicks.push(_options.nameIdentifier + item.getNick());
});
}
};
/** Function: replaceName
*
*/
self.replaceName = function(replaceText) {
// get the parts of the message
var $msgBox = $(_selector);
var msgParts = $msgBox.val().split(' ');
// If the name is the first word, add a colon to the end
if (msgParts.length === 1) {
replaceText += ": ";
} else {
replaceText += " ";
}
// replace the last part with the item
msgParts[msgParts.length - 1] = replaceText;
// put the string back together on spaces
$msgBox.val(msgParts.join(' '));
self.endAutocomplete();
};
/** Function: showPicker
* Show the picker for the list of names that match
*/
self.showPicker = function(matches, elem) {
// get the element
elem = $(elem);
// get the necessary items
var pos = elem.offset(),
menu = $('#context-menu'),
content = $('ul', menu),
i;
// clear the content if needed
content.empty();
// add the matches to the list
for(i = 0; i < matches.length; i++) {
content.append('<li class="candy-namecomplete-option">' + matches[i] + '</li>');
}
// select the first item
$(content.find('li')[0]).addClass('selected');
content.find('li').click(self.selectOnClick);
// bind the keydown to move around the menu
$(_selector).bind('keydown', self.keyDown);
var posLeft = elem.val().length * 7,
posTop = Candy.Util.getPosTopAccordingToWindowBounds(menu, pos.top);
// show it
menu.css({'left': posLeft, 'top': posTop.px, backgroundPosition: posLeft.backgroundPositionAlignment + ' ' + posTop.backgroundPositionAlignment});
menu.fadeIn('fast');
return true;
};
return self;
}(CandyShop.NameComplete || {}, Candy, jQuery));

View File

@ -0,0 +1,29 @@
# Notifications
Send HTML5 Notifications when a message is received and the window is not in focus. This only works with webkit browsers.
## Usage
To enable *Notifications* you have to include its JavaScript code and stylesheet:
```HTML
<script type="text/javascript" src="candyshop/notifications/candy.js"></script>
```
Call its `init()` method after Candy has been initialized:
```JavaScript
Candy.init('/http-bind/');
CandyShop.Notifications.init();
Candy.Core.connect();
```
It is possible to configure the Plugin.
```JavaScript
CandyShop.Notifications.init({
notifyNormalMessage: false, // Send a notification for every message. Defaults to false
notifyPersonalMessage: true, // Send a notification if the user is mentioned. (Requires NotfiyMe Plugin) Defaults to true
closeTime: 3000 // Close notification after X milliseconds. Zero means it doesn't close automaticly. Defaults to 3000
});
```

View File

@ -0,0 +1,111 @@
/*
* HTML5 Notifications
* @version 1.0
* @author Jonatan Männchen <jonatan@maennchen.ch>
* @author Melissa Adamaitis <madamei@mojolingo.com>
*
* Notify user if new messages come in.
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Notifications = (function(self, Candy, $) {
/** Object: _options
* Options for this plugin's operation
*
* Options:
* (Boolean) notifyNormalMessage - Notification on normalmessage. Defaults to false
* (Boolean) notifyPersonalMessage - Notification for private messages. Defaults to true
* (Boolean) notifyMention - Notification for mentions. Defaults to true
* (Integer) closeTime - Time until closing the Notification. (0 = Don't close) Defaults to 3000
* (String) title - Title to be used in notification popup. Set to null to use the contact's name.
* (String) icon - Path to use for image/icon for notification popup.
*/
var _options = {
notifyNormalMessage: false,
notifyPersonalMessage: true,
notifyMention: true,
closeTime: 3000,
title: null,
icon: window.location.origin + '/' + Candy.View.getOptions().assets + '/img/favicon.png'
};
/** Function: init
* Initializes the notifications plugin.
*
* Parameters:
* (Object) options - The options to apply to this plugin
*
* @return void
*/
self.init = function(options) {
// apply the supplied options to the defaults specified
$.extend(true, _options, options);
// Just init if notifications are supported
if (window.Notification) {
// Setup Permissions (has to be kicked on with some user-events)
jQuery(document).one('click keydown', self.setupPermissions);
// Add Listener for Notifications
$(Candy).on('candy:view.message.notify', self.handleNotification);
}
};
/** Function: checkPermissions
* Check if the plugin has permission to send notifications.
*
* @return boid
*/
self.setupPermissions = function() {
// Check if permissions is given
if (window.Notification !== 0) { // 0 is PERMISSION_ALLOWED
// Request for it
window.Notification.requestPermission();
}
};
/** Function: handleNotification
* Descriptions
*
* Parameters:
* (Array) args
*
* @return void
*/
self.handleNotification = function(e, args) {
// Check if window has focus, so no notification needed
if (!document.hasFocus()) {
if(_options.notifyNormalMessage ||
(self.mentionsMe(args.message) && _options.notifyMention) ||
(_options.notifyPersonalMessage && Candy.View.Pane.Chat.rooms[args.roomJid].type === 'chat')) {
// Create the notification.
var title = !_options.title ? args.name : _options.title ,
notification = new window.Notification(title, {
icon: _options.icon,
body: args.message
});
// Close it after 3 Seconds
if(_options.closeTime) {
window.setTimeout(function() { notification.close(); }, _options.closeTime);
}
}
}
};
self.mentionsMe = function(message) {
var message = message.toLowerCase(),
nick = Candy.Core.getUser().getNick().toLowerCase(),
cid = Strophe.getNodeFromJid(Candy.Core.getUser().getJid()).toLowerCase(),
jid = Candy.Core.getUser().getJid().toLowerCase();
if (message.indexOf(nick) === -1 &&
message.indexOf(cid) === -1 &&
message.indexOf(jid) === -1) {
return false;
}
return true;
};
return self;
}(CandyShop.Notifications || {}, Candy, jQuery));

View File

@ -0,0 +1,32 @@
# Notify me plugin
This plugin will notify users when their names are mentioned and prefixed with a specific token
### Usage
<script type="text/javascript" src="path_to_plugins/notifyme/candy.js"></script>
<link rel="stylesheet" type="text/css" href="path_to_plugins/notifyme/candy.css" />
...
CandyShop.NotifyMe.init();
### Configuration options
`nameIdentifier` - String - The identifier to look for in a string. Defaults to `'@'`
`playSound` - Boolean - Whether to play a sound when the username is mentioned. Defaults to `true`
`highlightInRoom` - Boolean - Whether to highlight the username when it is mentioned. Defaults to `true`
`normalizeNickname` - Boolean - Whether to normalize the casing of the nickname to the way you entered it. Otherwise, leave the casing as the sender wrote it. Defaults to `true`
### Example configurations
// Highlight my name when it's prefixed with a '+'
CandyShop.NotifyMe.init({
nameIdentifier: '+',
playSound: false
});
// Highlight and play a sound if my name is prefixed with a '-'
CandyShop.NotifyMe.init({
nameIdentifier: '-'
});

View File

@ -0,0 +1,3 @@
.candy-notifyme-highlight {
background: #FFFF00;
}

View File

@ -0,0 +1,96 @@
/** File: candy.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Troy McCabe <troy.mccabe@geeksquad.com>
*
* Copyright:
* (c) 2012 Geek Squad. All rights reserved.
*/
/* global Candy, jQuery */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
/** Class: CandyShop.NotifyMe
* Notifies with a sound and highlights the text in the chat when a nick is called out
*/
CandyShop.NotifyMe = (function(self, Candy, $) {
/** Object: _options
* Options for this plugin's operation
*
* Options:
* (String) nameIdentifier - Prefix to append to a name to look for. '@' now looks for '@NICK', '' looks for 'NICK', etc. Defaults to '@'
* (Boolean) playSound - Whether to play a sound when identified. Defaults to true
* (Boolean) highlightInRoom - Whether to highlight the name in the room. Defaults to true
* (Boolean) normalizeNickname - Whether to normalize the casing of the nickname to the way you entered it. Otherwise, leave the casing as the sender wrote it. Defaults to true
*/
var _options = {
nameIdentifier: '@',
playSound: true,
highlightInRoom: true,
normalizeNickname: true
};
var _getNick = function() {
return Candy.Core.getUser().getNick();
};
var _getSearchTerm = function() {
// make it what is searched
// search for <identifier>name in the whole message
return _options.nameIdentifier + _getNick();
};
/** Function: init
* Initialize the NotifyMe plugin
* Bind to beforeShow, play sound and higlight if specified
*
* Parameters:
* (Object) options - The options to apply to this plugin
*/
self.init = function(options) {
// apply the supplied options to the defaults specified
$.extend(true, _options, options);
// bind to the beforeShow event
$(Candy).on('candy:view.message.before-show', function(e, args) {
var searchRegExp = new RegExp('^(.*)(\s?' + _getSearchTerm() + ')', 'ig');
// if it's in the message and it's not from me, do stuff
// I wouldn't want to say 'just do @{MY_NICK} to get my attention' and have it knock...
if (searchRegExp.test(args.message) && args.name != _getNick()) {
// play the sound if specified
if (_options.playSound) {
Candy.View.Pane.Chat.Toolbar.playSound();
}
// Save that I'm mentioned in args
args.forMe = true;
}
return args.message;
});
// bind to the beforeShow event
$(Candy).on('candy:view.message.before-render', function(e, args) {
var searchTerm = _getSearchTerm();
var searchMatch = new RegExp('^(.*)(\s?' + searchTerm + ')', 'ig').exec(args.templateData.message);
// if it's in the message and it's not from me, do stuff
// I wouldn't want to say 'just do @{MY_NICK} to get my attention' and have it knock...
if (searchMatch != null && args.templateData.name != _getNick()) {
// highlight if specified
if (_options.highlightInRoom) {
var displayNickName = searchTerm;
if (!_options.normalizeNickname) {
displayNickName = searchMatch[2];
}
args.templateData.message = args.templateData.message.replace(searchMatch[2], '<span class="candy-notifyme-highlight">' + displayNickName + '</span>');
}
}
});
};
return self;
}(CandyShop.NotifyMe || {}, Candy, jQuery));

View File

@ -0,0 +1,49 @@
{
"name": "candy-shop",
"version": "1.0.0",
"description": "Multi-user XMPP web client plugins",
"directories": {},
"scripts": {
"test": "grunt ci"
},
"repository": {
"type": "git",
"url": "git://github.com/candy-chat/candy-plugins.git"
},
"keywords": [
"xmpp",
"muc",
"multi-user",
"websocket",
"bosh",
"chat"
],
"contributors": [
{
"name": "Michael Weibel",
"email": "michael.weibel@gmail.com"
},
{
"name": "Patrick Stadler",
"email": "patrick.stadler@gmail.com",
"url": "http://pstadler.sh"
}
],
"license": "MIT",
"bugs": {
"url": "https://github.com/candy-chat/candy-plugins/issues"
},
"homepage": "http://candy-chat.github.io/candy/",
"devDependencies": {
"grunt": "^0.4.5",
"grunt-contrib-jshint": "^0.10.0",
"grunt-contrib-watch": "^0.6.1",
"grunt-coveralls": "^0.3.0",
"intern": "^2.0.1",
"jshint-stylish": "^0.2.0",
"sinon": "git+https://github.com/cjohansen/Sinon.JS.git",
"sinon-chai": "^2.5.0",
"grunt-todo": "~0.4.0",
"grunt-clear": "~0.2.1"
}
}

View File

@ -0,0 +1,20 @@
#Candy Timeago plugin
This plugin replaces the exact time/date with 'fuzzy timestamps' (e.g. 'less than a minute ago', '2 minutes ago', 'about an hour ago'). The timestamps update dynamically. All the heavy lifting is done by Ryan McGeary's excellent jQuery Timeago plugin (http://timeago.yarp.com/).
##Usage
To enable Timeago include it's JavaScript code and CSS file (after the main Candy script and CSS):
```html
<script type="text/javascript" src="candyshop/timeago/candy.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/timeago/candy.css" />
```
Then call its init() method after Candy has been initialized:
```html
Candy.init('/http-bind/');
CandyShop.Timeago.init();
Candy.Core.connect();
```

View File

@ -0,0 +1,3 @@
.message-pane li abbr {
border-bottom: none;
}

View File

@ -0,0 +1,192 @@
/*
* candy-timeago-plugin
* @version 0.1 (2011-07-15)
* @author David Devlin (dave.devlin@gmail.com)
*
* Integrates the jQuery Timeago plugin (http://timeago.yarp.com/) with Candy.
*/
/* global document, Candy, jQuery */
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.Timeago = (function(self, Candy, $) {
self.init = function() {
Candy.View.Template.Chat.adminMessage = '<li><small><abbr title="{{time}}">{{time}}</abbr></small><div class="adminmessage"><span class="label">{{sender}}</span><span class="spacer">▸</span>{{subject}} {{message}}</div></li>';
Candy.View.Template.Chat.infoMessage = '<li><small><abbr title="{{time}}">{{time}}</abbr></small><div class="infomessage"><span class="spacer">•</span>{{subject}} {{message}}</div></li>';
Candy.View.Template.Room.subject = '<li><small><abbr title="{{time}}">{{time}}</abbr></small><div class="subject"><span class="label">{{roomName}}</span><span class="spacer">▸</span>{{_roomSubject}} {{subject}}</div></li>';
Candy.View.Template.Message.item = '<li><small><abbr title="{{time}}">{{time}}</abbr></small><div><a class="label" href="#" class="name">{{displayName}}</a><span class="spacer">▸</span>{{{message}}}</div></li>';
Candy.Util.localizedTime = function(dateTime) {
if (dateTime === undefined) {
return undefined;
}
var date = Candy.Util.iso8601toDate(dateTime);
return date.format($.i18n._('isoDateTime'));
};
var applyTimeago = function(e, args) {
var $elem = args.element ? $('abbr', args.element) : $('abbr');
$elem.timeago();
};
$(Candy).on('candy:view.message.after-show', applyTimeago);
$(Candy).on('candy:view.room.after-subject-change', applyTimeago);
// the following handlers run timeago() on all <abbr> tags
$(Candy).on('candy:core.presence.room', applyTimeago);
$(Candy).on('candy:view.chat.admin-message', applyTimeago);
};
return self;
}(CandyShop.Timeago || {}, Candy, jQuery));
/*
* timeago: a jQuery plugin, version: 0.9.3 (2011-01-21)
* @requires jQuery v1.2.3 or later
*
* Timeago is a jQuery plugin that makes it easy to support automatically
* updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
*
* For usage and examples, visit:
* http://timeago.yarp.com/
*
* Licensed under the MIT:
* http://www.opensource.org/licenses/mit-license.php
*
* Copyright (c) 2008-2011, Ryan McGeary (ryanonjavascript -[at]- mcgeary [*dot*] org)
*/
(function($) {
$.timeago = function(timestamp) {
if (timestamp instanceof Date) {
return inWords(timestamp);
} else if (typeof timestamp === "string") {
return inWords($.timeago.parse(timestamp));
} else {
return inWords($.timeago.datetime(timestamp));
}
};
var $t = $.timeago;
$.extend($.timeago, {
settings: {
refreshMillis: 60000,
allowFuture: false,
strings: {
prefixAgo: null,
prefixFromNow: null,
suffixAgo: "ago",
suffixFromNow: "from now",
seconds: "less than a minute",
minute: "about a minute",
minutes: "%d minutes",
hour: "about an hour",
hours: "about %d hours",
day: "a day",
days: "%d days",
month: "about a month",
months: "%d months",
year: "about a year",
years: "%d years",
numbers: []
}
},
inWords: function(distanceMillis) {
var $l = this.settings.strings;
var prefix = $l.prefixAgo;
var suffix = $l.suffixAgo;
if (this.settings.allowFuture) {
if (distanceMillis < 0) {
prefix = $l.prefixFromNow;
suffix = $l.suffixFromNow;
}
distanceMillis = Math.abs(distanceMillis);
}
var seconds = distanceMillis / 1000;
var minutes = seconds / 60;
var hours = minutes / 60;
var days = hours / 24;
var years = days / 365;
function substitute(stringOrFunction, number) {
var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
var value = ($l.numbers && $l.numbers[number]) || number;
return string.replace(/%d/i, value);
}
var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
seconds < 90 && substitute($l.minute, 1) ||
minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
minutes < 90 && substitute($l.hour, 1) ||
hours < 24 && substitute($l.hours, Math.round(hours)) ||
hours < 48 && substitute($l.day, 1) ||
days < 30 && substitute($l.days, Math.floor(days)) ||
days < 60 && substitute($l.month, 1) ||
days < 365 && substitute($l.months, Math.floor(days / 30)) ||
years < 2 && substitute($l.year, 1) ||
substitute($l.years, Math.floor(years));
return $.trim([prefix, words, suffix].join(" "));
},
parse: function(iso8601) {
var s = $.trim(iso8601);
s = s.replace(/\.\d\d\d+/,""); // remove milliseconds
s = s.replace(/-/,"/").replace(/-/,"/");
s = s.replace(/T/," ").replace(/Z/," UTC");
s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
return new Date(s);
},
datetime: function(elem) {
// jQuery's `is()` doesn't play well with HTML5 in IE
var isTime = $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
var iso8601 = isTime ? $(elem).attr("datetime") : $(elem).attr("title");
return $t.parse(iso8601);
}
});
$.fn.timeago = function() {
var self = this;
self.each(refresh);
var $s = $t.settings;
if ($s.refreshMillis > 0) {
setInterval(function() { self.each(refresh); }, $s.refreshMillis);
}
return self;
};
function refresh() {
var data = prepareData(this);
if (!isNaN(data.datetime)) {
$(this).text(inWords(data.datetime));
}
return this;
}
function prepareData(element) {
element = $(element);
if (!element.data("timeago")) {
element.data("timeago", { datetime: $t.datetime(element) });
var text = $.trim(element.text());
if (text.length > 0) {
element.attr("title", text);
}
}
return element.data("timeago");
}
function inWords(date) {
return $t.inWords(distance(date));
}
function distance(date) {
return (new Date().getTime() - date.getTime());
}
// fix for IE6 suckage
document.createElement("abbr");
document.createElement("time");
}(jQuery));

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

View File

@ -0,0 +1,22 @@
# Typing Notifications
A plugin for Candy Chat to enable typing notifications to show up. Fully compatible with the lefttabs plugin.
## Todo
It would be nice to extend this to groupchat as well. Currenly only working for private chat. (Simpler.)
![Typing Notifications - Regular](screenshot1.png)
![Typing Notifications - Left Tabs](screenshot2.png)
## Usage
Include the JavaScript and CSS files:
```HTML
<script type="text/javascript" src="candyshop/typingnotifications/typingnotifications.js"></script>
<link rel="stylesheet" type="text/css" href="candyshop/typingnotifications/typingnotifications.css" />
```
To enable this typing notifications plugin, add its `init` method after you `init` Candy, but before `Candy.connect()`:
```JavaScript
CandyShop.TypingNotifications.init();
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

@ -0,0 +1,16 @@
/**
* TypingNotifications CSS
*
* Author: Melissa Adamaitis <madamei@mojolingo.com>
*/
.message-pane-wrapper {
padding-bottom: 5px;
}
.typing-notification-area {
position: fixed;
bottom: 34px;
color: #ADADAD;
font-style: italic;
margin-left: 7px;
font-size: 0.8em;
}

View File

@ -0,0 +1,55 @@
/** File: typingnotifications.js
* Candy Plugin Typing Notifications
* Author: Melissa Adamaitis <madamei@mojolingo.com>
*/
var CandyShop = (function(self) { return self; }(CandyShop || {}));
CandyShop.TypingNotifications = (function(self, Candy, $) {
/** Object: about
*
* Contains:
* (String) name - Candy Plugin Typing Notifications
* (Float) version - Candy Plugin Typing Notifications
*/
self.about = {
name: 'Candy Plugin Typing Notifications',
version: '1.0'
};
/**
* Initializes the Typing Notifications plugin with the default settings.
*/
self.init = function(){
// After a room is added, make sure to tack on a little div that we can put the typing notification into.
$(Candy).on('candy:view.private-room.after-open', function(ev, obj){
self.addTypingNotificationDiv(obj);
});
// When a typing notification is recieved, display it.
$(Candy).on('candy:core.message.chatstate', function(ev, obj) {
var pane, chatstate_string;
pane = Candy.View.Pane.Room.getPane(obj.roomJid);
chatstate_string = self.getChatstateString(obj.chatstate, obj.name);
$(pane).find('.typing-notification-area').html(chatstate_string);
return true;
});
};
self.getChatstateString = function(chatstate, name) {
switch (chatstate) {
case 'paused': return name + ' has entered text.';
case 'inactive': return name + ' is away from the window.';
case 'composing': return name + ' is composing...';
case 'gone': return name + ' has closed the window.';
default: return '';
}
};
self.addTypingNotificationDiv = function(obj){
var pane_html = Candy.View.Pane.Room.getPane(obj.roomJid),
typing_notification_div_html = '<div class="typing-notification-area"></div>';
$(pane_html).find('.message-form-wrapper').append(typing_notification_div_html);
};
return self;
}(CandyShop.TypingNotifications || {}, Candy, jQuery));

View File

@ -167,11 +167,11 @@ ul {
}
#chat-statusmessage-control {
background: url(img/action/statusmessage-off.png);
background-image: url(img/action/statusmessage-off.png);
}
#chat-statusmessage-control.checked {
background: url(img/action/statusmessage-on.png);
background-image: url(img/action/statusmessage-on.png);
}
#chat-toolbar .usercount {
@ -575,7 +575,7 @@ ul {
width: 15px;
}
#chat-modal {
#chat-modal.modal-common {
background: #eee;
width: 300px;
padding: 20px 5px;
@ -641,6 +641,21 @@ ul {
color: #333;
}
#chat-modal.login-with-domains {
width: 650px;
margin-left: -330px;
}
#chat-modal span.at-symbol {
float: left;
padding: 6px;
font-size: 14px;
}
#chat-modal select[name=domain] {
width: 320px;
}
#chat-modal label {
text-align: right;
padding-right: 1em;

Binary file not shown.

Binary file not shown.

Binary file not shown.

54
content/static/candy/setup.sh Executable file
View File

@ -0,0 +1,54 @@
#!/usr/bin/env bash
#
# Easy installation for contributing to candy
#
# Copyright 2014 Michael Weibel <michael.weibel@gmail.com>
# License: MIT
#
# Show errors in case of undefined variables
set -o nounset
echo
echo "Welcome to the Candy Vagrant setup"
echo
echo "This script will setup a Vagrant box with development dependencies on it."
echo "It will also build Candy and run tests to verify that everything is working."
echo
echo "In case of an error, use 'install.log' for log informations."
echo
touch install.log
echo "" > install.log
echo -n "* Booting Vagrant box (this might take a while)..."
if vagrant up --no-provision >> install.log 2>&1
then echo "done"
else
echo "failed!"
echo "Do you have 'vagrant' installed in your PATH?"
echo "Please check install.log"
echo
echo "Aborting"
exit 2
fi
echo -n "* Provisioning Vagrant box (this might take a few minutes)..."
if vagrant provision >> install.log 2>&1
then echo "done"
else
echo "failed!"
echo "Please check install.log"
echo
echo "Aborting"
exit 2
fi
echo -n "* Building Candy and running tests..."
vagrant ssh -c "cd /vagrant && grunt && grunt test"
echo
echo "Candy is now running on http://localhost:8080"
echo
exit 0

View File

@ -1,56 +0,0 @@
/** File: candy.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
* (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
*/
'use strict';
/* global jQuery */
/** Class: Candy
* Candy base class for initalizing the view and the core
*
* Parameters:
* (Candy) self - itself
* (jQuery) $ - jQuery
*/
var Candy = (function(self, $) {
/** Object: about
* About candy
*
* Contains:
* (String) name - Candy
* (Float) version - Candy version
*/
self.about = {
name: 'Candy',
version: '1.7.1'
};
/** Function: init
* Init view & core
*
* Parameters:
* (String) service - URL to the BOSH interface
* (Object) options - Options for candy
*
* Options:
* (Boolean) debug - Debug (Default: false)
* (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
*/
self.init = function(service, options) {
if (!options.viewClass) {
options.viewClass = self.View;
}
options.viewClass.init($('#candy'), options.view);
self.Core.init(service, options.core);
};
return self;
}(Candy || {}, jQuery));

View File

@ -1,415 +0,0 @@
/** File: core.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
* (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
*/
'use strict';
/* global Candy, window, Strophe, jQuery */
/** Class: Candy.Core
* Candy Chat Core
*
* Parameters:
* (Candy.Core) self - itself
* (Strophe) Strophe - Strophe JS
* (jQuery) $ - jQuery
*/
Candy.Core = (function(self, Strophe, $) {
/** PrivateVariable: _connection
* Strophe connection
*/
var _connection = null,
/** PrivateVariable: _service
* URL of BOSH service
*/
_service = null,
/** PrivateVariable: _user
* Current user (me)
*/
_user = null,
/** PrivateVariable: _rooms
* Opened rooms, containing instances of Candy.Core.ChatRooms
*/
_rooms = {},
/** PrivateVariable: _anonymousConnection
* Set in <Candy.Core.connect> when jidOrHost doesn't contain a @-char.
*/
_anonymousConnection = false,
/** PrivateVariable: _status
* Current Strophe connection state
*/
_status,
/** PrivateVariable: _options
* Options:
* (Boolean) debug - Debug (Default: false)
* (Array|Boolean) autojoin - Autojoin these channels. When boolean true, do not autojoin, wait if the server sends something.
*/
_options = {
/** Boolean: autojoin
* If set to `true` try to get the bookmarks and autojoin the rooms (supported by ejabberd, Openfire).
* You may want to define an array of rooms to autojoin: `['room1@conference.host.tld', 'room2...]` (ejabberd, Openfire, ...)
*/
autojoin: undefined,
debug: false,
disableWindowUnload: false,
/** Integer: presencePriority
* Default priority for presence messages in order to receive messages across different resources
*/
presencePriority: 1,
/** String: resource
* JID resource to use when connecting to the server.
* Specify `''` (an empty string) to request a random resource.
*/
resource: Candy.about.name
},
/** PrivateFunction: _addNamespace
* Adds a namespace.
*
* Parameters:
* (String) name - namespace name (will become a constant living in Strophe.NS.*)
* (String) value - XML Namespace
*/
_addNamespace = function(name, value) {
Strophe.addNamespace(name, value);
},
/** PrivateFunction: _addNamespaces
* Adds namespaces needed by Candy.
*/
_addNamespaces = function() {
_addNamespace('PRIVATE', 'jabber:iq:private');
_addNamespace('BOOKMARKS', 'storage:bookmarks');
_addNamespace('PRIVACY', 'jabber:iq:privacy');
_addNamespace('DELAY', 'jabber:x:delay');
_addNamespace('PUBSUB', 'http://jabber.org/protocol/pubsub');
},
_getEscapedJidFromJid = function(jid) {
var node = Strophe.getNodeFromJid(jid),
domain = Strophe.getDomainFromJid(jid);
return node ? Strophe.escapeNode(node) + '@' + domain : domain;
};
/** Function: init
* Initialize Core.
*
* Parameters:
* (String) service - URL of BOSH/Websocket service
* (Object) options - Options for candy
*/
self.init = function(service, options) {
_service = service;
// Apply options
$.extend(true, _options, options);
// Enable debug logging
if(_options.debug) {
if(typeof window.console !== undefined && typeof window.console.log !== undefined) {
// Strophe has a polyfill for bind which doesn't work in IE8.
if(Function.prototype.bind && Candy.Util.getIeVersion() > 8) {
self.log = Function.prototype.bind.call(console.log, console);
} else {
self.log = function() {
Function.prototype.apply.call(console.log, console, arguments);
};
}
}
self.log('[Init] Debugging enabled');
}
_addNamespaces();
// Connect to BOSH/Websocket service
_connection = new Strophe.Connection(_service);
_connection.rawInput = self.rawInput.bind(self);
_connection.rawOutput = self.rawOutput.bind(self);
// set caps node
_connection.caps.node = 'https://candy-chat.github.io/candy/';
// Window unload handler... works on all browsers but Opera. There is NO workaround.
// Opera clients getting disconnected 1-2 minutes delayed.
if (!_options.disableWindowUnload) {
window.onbeforeunload = self.onWindowUnload;
}
};
/** Function: registerEventHandlers
* Adds listening handlers to the connection.
*
* Use with caution from outside of Candy.
*/
self.registerEventHandlers = function() {
self.addHandler(self.Event.Jabber.Version, Strophe.NS.VERSION, 'iq');
self.addHandler(self.Event.Jabber.Presence, null, 'presence');
self.addHandler(self.Event.Jabber.Message, null, 'message');
self.addHandler(self.Event.Jabber.Bookmarks, Strophe.NS.PRIVATE, 'iq');
self.addHandler(self.Event.Jabber.Room.Disco, Strophe.NS.DISCO_INFO, 'iq', 'result');
self.addHandler(_connection.disco._onDiscoInfo.bind(_connection.disco), Strophe.NS.DISCO_INFO, 'iq', 'get');
self.addHandler(_connection.disco._onDiscoItems.bind(_connection.disco), Strophe.NS.DISCO_ITEMS, 'iq', 'get');
self.addHandler(_connection.caps._delegateCapabilities.bind(_connection.caps), Strophe.NS.CAPS);
};
/** Function: connect
* Connect to the jabber host.
*
* There are four different procedures to login:
* connect('JID', 'password') - Connect a registered user
* connect('domain') - Connect anonymously to the domain. The user should receive a random JID.
* connect('domain', null, 'nick') - Connect anonymously to the domain. The user should receive a random JID but with a nick set.
* connect('JID') - Show login form and prompt for password. JID input is hidden.
* connect() - Show login form and prompt for JID and password.
*
* See:
* <Candy.Core.attach()> for attaching an already established session.
*
* Parameters:
* (String) jidOrHost - JID or Host
* (String) password - Password of the user
* (String) nick - Nick of the user. Set one if you want to anonymously connect but preset a nick. If jidOrHost is a domain
* and this param is not set, Candy will prompt for a nick.
*/
self.connect = function(jidOrHost, password, nick) {
// Reset before every connection attempt to make sure reconnections work after authfail, alltabsclosed, ...
_connection.reset();
self.registerEventHandlers();
/** Event: candy:core.before-connect
* Triggered before a connection attempt is made.
*
* Plugins should register their stanza handlers using this event
* to ensure that they are set.
*
* See also <#84 at https://github.com/candy-chat/candy/issues/84>.
*
* Parameters:
* (Strophe.Connection) conncetion - Strophe connection
*/
$(Candy).triggerHandler('candy:core.before-connect', {
connection: _connection
});
_anonymousConnection = !_anonymousConnection ? jidOrHost && jidOrHost.indexOf("@") < 0 : true;
if(jidOrHost && password) {
// authentication
_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + _options.resource, password, Candy.Core.Event.Strophe.Connect);
if (nick) {
_user = new self.ChatUser(jidOrHost, nick);
} else {
_user = new self.ChatUser(jidOrHost, Strophe.getNodeFromJid(jidOrHost));
}
} else if(jidOrHost && nick) {
// anonymous connect
_connection.connect(_getEscapedJidFromJid(jidOrHost) + '/' + _options.resource, null, Candy.Core.Event.Strophe.Connect);
_user = new self.ChatUser(null, nick); // set jid to null because we'll later receive it
} else if(jidOrHost) {
Candy.Core.Event.Login(jidOrHost);
} else {
// display login modal
Candy.Core.Event.Login();
}
};
/** Function: attach
* Attach an already binded & connected session to the server
*
* _See_ Strophe.Connection.attach
*
* Parameters:
* (String) jid - Jabber ID
* (Integer) sid - Session ID
* (Integer) rid - rid
*/
self.attach = function(jid, sid, rid) {
_user = new self.ChatUser(jid, Strophe.getNodeFromJid(jid));
self.registerEventHandlers();
_connection.attach(jid, sid, rid, Candy.Core.Event.Strophe.Connect);
};
/** Function: disconnect
* Leave all rooms and disconnect
*/
self.disconnect = function() {
if(_connection.connected) {
$.each(self.getRooms(), function() {
Candy.Core.Action.Jabber.Room.Leave(this.getJid());
});
_connection.disconnect();
}
};
/** Function: addHandler
* Wrapper for Strophe.Connection.addHandler() to add a stanza handler for the connection.
*
* Parameters:
* (Function) handler - The user callback.
* (String) ns - The namespace to match.
* (String) name - The stanza name to match.
* (String) type - The stanza type attribute to match.
* (String) id - The stanza id attribute to match.
* (String) from - The stanza from attribute to match.
* (String) options - The handler options
*
* Returns:
* A reference to the handler that can be used to remove it.
*/
self.addHandler = function(handler, ns, name, type, id, from, options) {
return _connection.addHandler(handler, ns, name, type, id, from, options);
};
/** Function: getUser
* Gets current user
*
* Returns:
* Instance of Candy.Core.ChatUser
*/
self.getUser = function() {
return _user;
};
/** Function: setUser
* Set current user. Needed when anonymous login is used, as jid gets retrieved later.
*
* Parameters:
* (Candy.Core.ChatUser) user - User instance
*/
self.setUser = function(user) {
_user = user;
};
/** Function: getConnection
* Gets Strophe connection
*
* Returns:
* Instance of Strophe.Connection
*/
self.getConnection = function() {
return _connection;
};
/** Function: removeRoom
* Removes a room from the rooms list
*
* Parameters:
* (String) roomJid - roomJid
*/
self.removeRoom = function(roomJid) {
delete _rooms[roomJid];
};
/** Function: getRooms
* Gets all joined rooms
*
* Returns:
* Object containing instances of Candy.Core.ChatRoom
*/
self.getRooms = function() {
return _rooms;
};
/** Function: getStropheStatus
* Get the status set by Strophe.
*
* Returns:
* (Strophe.Status.*) - one of Strophe's statuses
*/
self.getStropheStatus = function() {
return _status;
};
/** Function: setStropheStatus
* Set the strophe status
*
* Called by:
* Candy.Core.Event.Strophe.Connect
*
* Parameters:
* (Strophe.Status.*) status - Strophe's status
*/
self.setStropheStatus = function(status) {
_status = status;
};
/** Function: isAnonymousConnection
* Returns true if <Candy.Core.connect> was first called with a domain instead of a jid as the first param.
*
* Returns:
* (Boolean)
*/
self.isAnonymousConnection = function() {
return _anonymousConnection;
};
/** Function: getOptions
* Gets options
*
* Returns:
* Object
*/
self.getOptions = function() {
return _options;
};
/** Function: getRoom
* Gets a specific room
*
* Parameters:
* (String) roomJid - JID of the room
*
* Returns:
* If the room is joined, instance of Candy.Core.ChatRoom, otherwise null.
*/
self.getRoom = function(roomJid) {
if (_rooms[roomJid]) {
return _rooms[roomJid];
}
return null;
};
/** Function: onWindowUnload
* window.onbeforeunload event which disconnects the client from the Jabber server.
*/
self.onWindowUnload = function() {
// Enable synchronous requests because Safari doesn't send asynchronous requests within unbeforeunload events.
// Only works properly when following patch is applied to strophejs: https://github.com/metajack/strophejs/issues/16/#issuecomment-600266
_connection.options.sync = true;
self.disconnect();
_connection.flush();
};
/** Function: rawInput
* (Overridden from Strophe.Connection.rawInput)
*
* Logs all raw input if debug is set to true.
*/
self.rawInput = function(data) {
this.log('RECV: ' + data);
};
/** Function rawOutput
* (Overridden from Strophe.Connection.rawOutput)
*
* Logs all raw output if debug is set to true.
*/
self.rawOutput = function(data) {
this.log('SENT: ' + data);
};
/** Function: log
* Overridden to do something useful if debug is set to true.
*
* See: Candy.Core#init
*/
self.log = function() {};
return self;
}(Candy.Core || {}, Strophe, jQuery));

View File

@ -1,419 +0,0 @@
/** File: action.js
* Candy - Chats are not dead yet.
*
* Authors:
* - Patrick Stadler <patrick.stadler@gmail.com>
* - Michael Weibel <michael.weibel@gmail.com>
*
* Copyright:
* (c) 2011 Amiado Group AG. All rights reserved.
* (c) 2012-2014 Patrick Stadler & Michael Weibel. All rights reserved.
*/
'use strict';
/* global Candy, $iq, navigator, Candy, $pres, Strophe, jQuery, $msg */
/** Class: Candy.Core.Action
* Chat Actions (basicly a abstraction of Jabber commands)
*
* Parameters:
* (Candy.Core.Action) self - itself
* (Strophe) Strophe - Strophe
* (jQuery) $ - jQuery
*/
Candy.Core.Action = (function(self, Strophe, $) {
/** Class: Candy.Core.Action.Jabber
* Jabber actions
*/
self.Jabber = {
/** Function: Version
* Replies to a version request
*
* Parameters:
* (jQuery.element) msg - jQuery element
*/
Version: function(msg) {
Candy.Core.getConnection().sendIQ($iq({
type: 'result',
to: Candy.Util.escapeJid(msg.attr('from')),
from: Candy.Util.escapeJid(msg.attr('to')),
id: msg.attr('id')
}).c('query', {
name: Candy.about.name,
version: Candy.about.version,
os: navigator.userAgent
}));
},
/** Function: SetNickname
* Sets the supplied nickname for all rooms (if parameter "room" is not specified) or
* sets it only for the specified rooms
*
* Parameters:
* (String) nickname - New nickname
* (Array) rooms - Rooms
*/
SetNickname: function(nickname, rooms) {
rooms = rooms instanceof Array ? rooms : Candy.Core.getRooms();
var roomNick, presence,
conn = Candy.Core.getConnection();
$.each(rooms, function(roomJid) {
roomNick = Candy.Util.escapeJid(roomJid + '/' + nickname);
presence = $pres({
to: roomNick,
from: conn.jid,
id: 'pres:' + conn.getUniqueId()
});
Candy.Core.getConnection().send(presence);
});
},
/** Function: Roster
* Sends a request for a roster
*/
Roster: function() {
Candy.Core.getConnection().sendIQ($iq({
type: 'get',
xmlns: Strophe.NS.CLIENT
}).c('query', {xmlns: Strophe.NS.ROSTER}).tree());
},
/** Function: Presence
* Sends a request for presence
*
* Parameters:
* (Object) attr - Optional attributes
* (Strophe.Builder) el - Optional element to include in presence stanza
*/
Presence: function(attr, el) {
var conn = Candy.Core.getConnection();
attr = attr || {};
if(!attr.id) {
attr.id = 'pres:' + conn.getUniqueId();
}
var pres = $pres(attr).c('priority').t(Candy.Core.getOptions().presencePriority.toString())
.up().c('c', conn.caps.generateCapsAttrs())
.up();
if(el) {
pres.node.appendChild(el.node);
}
conn.send(pres.tree());
},
/** Function: Services
* Sends a request for disco items
*/
Services: function() {
Candy.Core.getConnection().sendIQ($iq({
type: 'get',
xmlns: Strophe.NS.CLIENT
}).c('query', {xmlns: Strophe.NS.DISCO_ITEMS}).tree());
},
/** Function: Autojoin
* When Candy.Core.getOptions().autojoin is true, request autojoin bookmarks (OpenFire)
*
* Otherwise, if Candy.Core.getOptions().autojoin is an array, join each channel specified.
* Channel can be in jid:password format to pass room password if needed.
* Triggers:
* candy:core.autojoin-missing in case no autojoin info has been found
*/
Autojoin: function() {
// Request bookmarks
if(Candy.Core.getOptions().autojoin === true) {
Candy.Core.getConnection().sendIQ($iq({
type: 'get',
xmlns: Strophe.NS.CLIENT
})
.c('query', {xmlns: Strophe.NS.PRIVATE})
.c('storage', {xmlns: Strophe.NS.BOOKMARKS})
.tree());
var pubsubBookmarkRequest = Candy.Core.getConnection().getUniqueId('pubsub');
Candy.Core.addHandler(Candy.Core.Event.Jabber.Bookmarks, Strophe.NS.PUBSUB, 'iq', 'result', pubsubBookmarkRequest);
Candy.Core.getConnection().sendIQ($iq({
type: 'get',
id: pubsubBookmarkRequest
})
.c('pubsub', { xmlns: Strophe.NS.PUBSUB })
.c('items', { node: Strophe.NS.BOOKMARKS })
.tree());
// Join defined rooms
} else if($.isArray(Candy.Core.getOptions().autojoin)) {
$.each(Candy.Core.getOptions().autojoin, function() {
self.Jabber.Room.Join.apply(null, this.valueOf().split(':',2));
});
} else {
/** Event: candy:core.autojoin-missing
* Triggered when no autojoin information has been found
*/
$(Candy).triggerHandler('candy:core.autojoin-missing');
}
},
/** Function: ResetIgnoreList
* Create new ignore privacy list (and reset the previous one, if it exists).
*/
ResetIgnoreList: function() {
Candy.Core.getConnection().sendIQ($iq({
type: 'set',
from: Candy.Core.getUser().getEscapedJid()
})
.c('query', {xmlns: Strophe.NS.PRIVACY })
.c('list', {name: 'ignore'})
.c('item', {'action': 'allow', 'order': '0'})
.tree());
},
/** Function: RemoveIgnoreList
* Remove an existing ignore list.
*/
RemoveIgnoreList: function() {
Candy.Core.getConnection().sendIQ($iq({
type: 'set',
from: Candy.Core.getUser().getEscapedJid()
})
.c('query', {xmlns: Strophe.NS.PRIVACY })
.c('list', {name: 'ignore'}).tree());
},
/** Function: GetIgnoreList
* Get existing ignore privacy list when connecting.
*/
GetIgnoreList: function() {
var iq = $iq({
type: 'get',
from: Candy.Core.getUser().getEscapedJid()
})
.c('query', {xmlns: Strophe.NS.PRIVACY})
.c('list', {name: 'ignore'}).tree();
var iqId = Candy.Core.getConnection().sendIQ(iq);
// add handler (<#200 at https://github.com/candy-chat/candy/issues/200>)
Candy.Core.addHandler(Candy.Core.Event.Jabber.PrivacyList, null, 'iq', null, iqId);
},
/** Function: SetIgnoreListActive
* Set ignore privacy list active
*/
SetIgnoreListActive: function() {
Candy.Core.getConnection().sendIQ($iq({
type: 'set',
from: Candy.Core.getUser().getEscapedJid()})
.c('query', {xmlns: Strophe.NS.PRIVACY })
.c('active', {name:'ignore'}).tree());
},
/** Function: GetJidIfAnonymous
* On anonymous login, initially we don't know the jid and as a result, Candy.Core._user doesn't have a jid.
* Check if user doesn't have a jid and get it if necessary from the connection.
*/
GetJidIfAnonymous: function() {
if (!Candy.Core.getUser().getJid()) {
Candy.Core.log("[Jabber] Anonymous login");
Candy.Core.getUser().data.jid = Candy.Core.getConnection().jid;
}
},
/** Class: Candy.Core.Action.Jabber.Room
* Room-specific commands
*/
Room: {
/** Function: Join
* Requests disco of specified room and joins afterwards.
*
* TODO:
* maybe we should wait for disco and later join the room?
* but what if we send disco but don't want/can join the room
*
* Parameters:
* (String) roomJid - Room to join
* (String) password - [optional] Password for the room
*/
Join: function(roomJid, password) {
self.Jabber.Room.Disco(roomJid);
roomJid = Candy.Util.escapeJid(roomJid);
var conn = Candy.Core.getConnection(),
roomNick = roomJid + '/' + Candy.Core.getUser().getNick(),
pres = $pres({ to: roomNick, id: 'pres:' + conn.getUniqueId() })
.c('x', {xmlns: Strophe.NS.MUC});
if (password) {
pres.c('password').t(password);
}
pres.up().c('c', conn.caps.generateCapsAttrs());
conn.send(pres.tree());
},
/** Function: Leave
* Leaves a room.
*
* Parameters:
* (String) roomJid - Room to leave
*/
Leave: function(roomJid) {
var user = Candy.Core.getRoom(roomJid).getUser();
roomJid = Candy.Util.escapeJid(roomJid);
if (user) {
Candy.Core.getConnection().muc.leave(roomJid, user.getNick(), function() {});
}
},
/** Function: Disco
* Requests <disco info of a room at http://xmpp.org/extensions/xep-0045.html#disco-roominfo>.
*
* Parameters:
* (String) roomJid - Room to get info for
*/
Disco: function(roomJid) {
Candy.Core.getConnection().sendIQ($iq({
type: 'get',
from: Candy.Core.getUser().getEscapedJid(),
to: Candy.Util.escapeJid(roomJid)
}).c('query', {xmlns: Strophe.NS.DISCO_INFO}).tree());
},
/** Function: Message
* Send message
*
* Parameters:
* (String) roomJid - Room to which send the message into
* (String) msg - Message
* (String) type - "groupchat" or "chat" ("chat" is for private messages)
* (String) xhtmlMsg - XHTML formatted message [optional]
*
* Returns:
* (Boolean) - true if message is not empty after trimming, false otherwise.
*/
Message: function(roomJid, msg, type, xhtmlMsg) {
// Trim message
msg = $.trim(msg);
if(msg === '') {
return false;
}
var nick = null;
if(type === 'chat') {
nick = Strophe.getResourceFromJid(roomJid);
roomJid = Strophe.getBareJidFromJid(roomJid);
}
// muc takes care of the escaping now.
Candy.Core.getConnection().muc.message(roomJid, nick, msg, xhtmlMsg, type);
return true;
},
/** Function: Invite
* Sends an invite stanza to multiple JIDs
*
* Parameters:
* (String) roomJid - Room to which send the message into
* (Array) invitees - Array of JIDs to be invited to the room
* (String) reason - Message to include with the invitation [optional]
* (String) password - Password for the MUC, if required [optional]
*/
Invite: function(roomJid, invitees, reason, password) {
reason = $.trim(reason);
var message = $msg({to: roomJid});
var x = message.c('x', {xmlns: Strophe.NS.MUC_USER});
$.each(invitees, function(i, invitee) {
invitee = Strophe.getBareJidFromJid(invitee);
x.c('invite', {to: invitee});
if (typeof reason !== 'undefined' && reason !== '') {
x.c('reason', reason);
}
});
if (typeof password !== 'undefined' && password !== '') {
x.c('password', password);
}
Candy.Core.getConnection().send(message);
},
/** Function: IgnoreUnignore
* Checks if the user is already ignoring the target user, if yes: unignore him, if no: ignore him.
*
* Uses the ignore privacy list set on connecting.
*
* Parameters:
* (String) userJid - Target user jid
*/
IgnoreUnignore: function(userJid) {
Candy.Core.getUser().addToOrRemoveFromPrivacyList('ignore', userJid);
Candy.Core.Action.Jabber.Room.UpdatePrivacyList();
},
/** Function: UpdatePrivacyList
* Updates privacy list according to the privacylist in the currentUser
*/
UpdatePrivacyList: function() {
var currentUser = Candy.Core.getUser(),
iq = $iq({type: 'set', from: currentUser.getEscapedJid()})
.c('query', {xmlns: 'jabber:iq:privacy' })
.c('list', {name: 'ignore'}),
privacyList = currentUser.getPrivacyList('ignore');
if (privacyList.length > 0) {
$.each(privacyList, function(index, jid) {
iq.c('item', {type:'jid', value: Candy.Util.escapeJid(jid), action: 'deny', order : index})
.c('message').up().up();
});
} else {
iq.c('item', {action: 'allow', order : '0'});
}
Candy.Core.getConnection().sendIQ(iq.tree());
},
/** Class: Candy.Core.Action.Jabber.Room.Admin
* Room administration commands
*/
Admin: {
/** Function: UserAction
* Kick or ban a user
*
* Parameters:
* (String) roomJid - Room in which the kick/ban should be done
* (String) userJid - Victim
* (String) type - "kick" or "ban"
* (String) msg - Reason
*
* Returns:
* (Boolean) - true if sent successfully, false if type is not one of "kick" or "ban".
*/
UserAction: function(roomJid, userJid, type, reason) {
roomJid = Candy.Util.escapeJid(roomJid);
userJid = Candy.Util.escapeJid(userJid);
var itemObj = {nick: Strophe.getResourceFromJid(userJid)};
switch(type) {
case 'kick':
itemObj.role = 'none';
break;
case 'ban':
itemObj.affiliation = 'outcast';
break;
default:
return false;
}
Candy.Core.getConnection().sendIQ($iq({
type: 'set',
from: Candy.Core.getUser().getEscapedJid(),
to: roomJid
}).c('query', {xmlns: Strophe.NS.MUC_ADMIN })
.c('item', itemObj).c('reason').t(reason).tree());
return true;
},
/** Function: SetSubject
* Sets subject (topic) of a room.
*
* Parameters:
* (String) roomJid - Room
* (String) subject - Subject to set
*/
SetSubject: function(roomJid, subject) {
Candy.Core.getConnection().muc.setTopic(Candy.Util.escapeJid(roomJid), subject);
}
}
}
};
return self;
}(Candy.Core.Action || {}, Strophe, jQuery));

Some files were not shown because too many files have changed in this diff Show More