aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Zotlabs/Lib/Enotify.php136
-rw-r--r--Zotlabs/Lib/ThreadItem.php5
-rw-r--r--Zotlabs/Module/Mail.php2
-rw-r--r--Zotlabs/Module/Sse.php108
-rw-r--r--Zotlabs/Module/Sse_bs.php519
-rwxr-xr-xboot.php60
-rw-r--r--doc/hooklist.bb3
-rw-r--r--include/channel.php5
-rw-r--r--include/event.php12
-rw-r--r--view/css/widgets.css5
-rw-r--r--view/de-de/hstrings.php2
-rw-r--r--view/js/main.js531
-rwxr-xr-xview/js/sse_worker.js14
-rw-r--r--view/php/theme_init.php1
-rw-r--r--view/tpl/activity_filter_widget.tpl2
-rwxr-xr-xview/tpl/conv_item.tpl4
-rwxr-xr-xview/tpl/conv_list.tpl2
-rwxr-xr-xview/tpl/js_strings.tpl2
-rw-r--r--view/tpl/notifications_widget.tpl28
19 files changed, 1378 insertions, 63 deletions
diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php
index 92a488f67..de2bfba24 100644
--- a/Zotlabs/Lib/Enotify.php
+++ b/Zotlabs/Lib/Enotify.php
@@ -550,6 +550,11 @@ class Enotify {
if ((\App::$language === 'en' || (! \App::$language)) && strpos($msg,', '))
$msg = substr($msg,strpos($msg,', ')+1);
+ $datarray['id'] = $notify_id;
+ $datarray['msg'] = $msg;
+
+ call_hooks('enotify_store_end', $datarray);
+
$r = q("update notify set msg = '%s' where id = %d and uid = %d",
dbesc($msg),
intval($notify_id),
@@ -838,7 +843,7 @@ class Enotify {
'addr' => (($item[$who]['xchan_addr']) ? $item[$who]['xchan_addr'] : $item[$who]['xchan_url']),
'url' => $item[$who]['xchan_url'],
'photo' => $item[$who]['xchan_photo_s'],
- 'when' => relative_date(($edit)? $item['edited'] : $item['created']),
+ 'when' => (($edit) ? datetime_convert('UTC', date_default_timezone_get(), $item['edited']) : datetime_convert('UTC', date_default_timezone_get(), $item['created'])),
'class' => (intval($item['item_unseen']) ? 'notify-unseen' : 'notify-seen'),
'b64mid' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? 'b64.' . base64url_encode($item['thr_parent']) : 'b64.' . base64url_encode($item['mid'])),
'notify_id' => 'undefined',
@@ -846,7 +851,7 @@ class Enotify {
'message' => strip_tags(bbcode($itemem_text)),
// these are for the superblock addon
'hash' => $item[$who]['xchan_hash'],
- 'uid' => local_channel(),
+ 'uid' => $item['uid'],
'display' => true
);
@@ -858,4 +863,131 @@ class Enotify {
return $x;
}
+ static public function format_notify($tt) {
+
+ $message = trim(strip_tags(bbcode($tt['msg'])));
+
+ if(strpos($message, $tt['xname']) === 0)
+ $message = substr($message, strlen($tt['xname']) + 1);
+
+ $mid = basename($tt['link']);
+ $mid = ((strpos($mid, 'b64.') === 0) ? @base64url_decode(substr($mid, 4)) : $mid);
+
+ if(in_array($tt['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) {
+ // we need the thread parent
+ $r = q("select thr_parent from item where mid = '%s' and uid = %d limit 1",
+ dbesc($mid),
+ intval(local_channel())
+ );
+ $b64mid = ((strpos($r[0]['thr_parent'], 'b64.') === 0) ? $r[0]['thr_parent'] : 'b64.' . base64url_encode($r[0]['thr_parent']));
+ }
+ else {
+ $b64mid = ((strpos($mid, 'b64.') === 0) ? $mid : 'b64.' . base64url_encode($mid));
+ }
+
+ $x = [
+ 'notify_link' => z_root() . '/notify/view/' . $tt['id'],
+ 'name' => $tt['xname'],
+ 'url' => $tt['url'],
+ 'photo' => $tt['photo'],
+ 'when' => datetime_convert('UTC', date_default_timezone_get(), $tt['created']),
+ 'hclass' => (($tt['seen']) ? 'notify-seen' : 'notify-unseen'),
+ 'b64mid' => (($tt['otype'] == 'item') ? $b64mid : 'undefined'),
+ 'notify_id' => (($tt['otype'] == 'item') ? $tt['id'] : 'undefined'),
+ 'message' => $message
+ ];
+
+ return $x;
+
+ }
+
+ static public function format_intros($rr) {
+
+ $x = [
+ 'notify_link' => z_root() . '/connections/ifpending',
+ 'name' => $rr['xchan_name'],
+ 'addr' => $rr['xchan_addr'],
+ 'url' => $rr['xchan_url'],
+ 'photo' => $rr['xchan_photo_s'],
+ 'when' => datetime_convert('UTC', date_default_timezone_get(), $rr['abook_created']),
+ 'hclass' => ('notify-unseen'),
+ 'message' => t('added your channel')
+ ];
+
+ return $x;
+
+ }
+
+ static public function format_files($rr) {
+
+ $x = [
+ 'notify_link' => z_root() . '/sharedwithme',
+ 'name' => $rr['author']['xchan_name'],
+ 'addr' => $rr['author']['xchan_addr'],
+ 'url' => $rr['author']['xchan_url'],
+ 'photo' => $rr['author']['xchan_photo_s'],
+ 'when' => datetime_convert('UTC', date_default_timezone_get(), $rr['created']),
+ 'hclass' => ('notify-unseen'),
+ 'message' => t('shared a file with you')
+ ];
+
+ return $x;
+
+ }
+
+ static public function format_mail($rr) {
+
+ $x = [
+ 'notify_link' => z_root() . '/mail/' . $rr['id'],
+ 'name' => $rr['xchan_name'],
+ 'addr' => $rr['xchan_addr'],
+ 'url' => $rr['xchan_url'],
+ 'photo' => $rr['xchan_photo_s'],
+ 'when' => datetime_convert('UTC', date_default_timezone_get(), $rr['created']),
+ 'hclass' => (intval($rr['mail_seen']) ? 'notify-seen' : 'notify-unseen'),
+ 'message' => t('sent you a private message'),
+ ];
+
+ return $x;
+
+ }
+
+ static public function format_all_events($rr) {
+
+ $bd_format = t('g A l F d') ; // 8 AM Friday January 18
+ $strt = datetime_convert('UTC', (($rr['adjust']) ? date_default_timezone_get() : 'UTC'), $rr['dtstart']);
+ $today = ((substr($strt, 0, 10) === datetime_convert('UTC', date_default_timezone_get(), 'now', 'Y-m-d')) ? true : false);
+ $when = day_translate(datetime_convert('UTC', (($rr['adjust']) ? date_default_timezone_get() : 'UTC'), $rr['dtstart'], $bd_format)) . (($today) ? ' ' . t('[today]') : '');
+
+ $x = [
+ 'notify_link' => z_root() . '/cdav/calendar/' . $rr['event_hash'],
+ 'name' => $rr['xchan_name'],
+ 'addr' => $rr['xchan_addr'],
+ 'url' => $rr['xchan_url'],
+ 'photo' => $rr['xchan_photo_s'],
+ 'when' => $when,
+ 'hclass' => ('notify-unseen'),
+ 'message' => t('posted an event')
+ ];
+
+ return $x;
+
+ }
+
+ static public function format_register($rr) {
+
+ $x = [
+ 'notify_link' => z_root() . '/admin/accounts',
+ 'name' => $rr['account_email'],
+ 'addr' => $rr['account_email'],
+ 'url' => '',
+ 'photo' => z_root() . '/' . get_default_profile_photo(48),
+ 'when' => datetime_convert('UTC', date_default_timezone_get(),$rr['account_created']),
+ 'hclass' => ('notify-unseen'),
+ 'message' => t('requires approval')
+ ];
+
+ return $x;
+
+ }
}
diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php
index 5e4600df2..174af7f0e 100644
--- a/Zotlabs/Lib/ThreadItem.php
+++ b/Zotlabs/Lib/ThreadItem.php
@@ -369,7 +369,7 @@ class ThreadItem {
'folders' => $body['folders'],
'text' => strip_tags($body['html']),
'id' => $this->get_id(),
- 'mid' => $item['mid'],
+ 'mid' => 'b64.' . base64url_encode($item['mid']),
'parent' => $item['parent'],
'author_id' => (($item['author']['xchan_addr']) ? $item['author']['xchan_addr'] : $item['author']['xchan_url']),
'isevent' => $isevent,
@@ -467,10 +467,9 @@ class ThreadItem {
'previewing' => ($conv->is_preview() ? true : false ),
'preview_lbl' => t('This is an unsaved preview'),
'wait' => t('Please wait'),
- 'submid' => str_replace(['+','='], ['',''], base64_encode($item['mid'])),
'thread_level' => $thread_level,
'settings' => $settings,
- 'thr_parent' => (($item['parent_mid'] != $item['thr_parent']) ? $item['thr_parent'] : '')
+ 'thr_parent' => (($item['parent_mid'] != $item['thr_parent']) ? 'b64.' . base64url_encode($item['thr_parent']) : '')
);
$arr = array('item' => $item, 'output' => $tmp_item);
diff --git a/Zotlabs/Module/Mail.php b/Zotlabs/Module/Mail.php
index 7c344966b..636fc4e33 100644
--- a/Zotlabs/Module/Mail.php
+++ b/Zotlabs/Module/Mail.php
@@ -145,7 +145,7 @@ class Mail extends \Zotlabs\Web\Controller {
$o = '';
nav_set_selected('Mail');
-
+
if(! local_channel()) {
notice( t('Permission denied.') . EOL);
return login();
diff --git a/Zotlabs/Module/Sse.php b/Zotlabs/Module/Sse.php
new file mode 100644
index 000000000..97fee5f49
--- /dev/null
+++ b/Zotlabs/Module/Sse.php
@@ -0,0 +1,108 @@
+<?php
+
+namespace Zotlabs\Module;
+
+use App;
+use Zotlabs\Lib\Apps;
+use Zotlabs\Web\Controller;
+use Zotlabs\Lib\Enotify;
+
+class Sse extends Controller {
+
+ public static $uid;
+ public static $ob_hash;
+ public static $vnotify;
+
+ function init() {
+
+ // this is important!
+ session_write_close();
+
+ $sys = get_sys_channel();
+
+ self::$uid = local_channel();
+ self::$ob_hash = get_observer_hash();
+ self::$vnotify = get_pconfig(self::$uid, 'system', 'vnotify');
+
+ $sleep_seconds = 3;
+
+ header("Content-Type: text/event-stream");
+ header("Cache-Control: no-cache");
+ header("Connection: keep-alive");
+ header("X-Accel-Buffering: no");
+
+ while(true) {
+
+ /**
+ * Update chat presence indication (if applicable)
+ */
+
+ if(self::$ob_hash) {
+ $r = q("select cp_id, cp_room from chatpresence where cp_xchan = '%s' and cp_client = '%s' and cp_room = 0 limit 1",
+ dbesc(self::$ob_hash),
+ dbesc($_SERVER['REMOTE_ADDR'])
+ );
+ $basic_presence = false;
+ if($r) {
+ $basic_presence = true;
+ q("update chatpresence set cp_last = '%s' where cp_id = %d",
+ dbesc(datetime_convert()),
+ intval($r[0]['cp_id'])
+ );
+ }
+ if(! $basic_presence) {
+ q("insert into chatpresence ( cp_xchan, cp_last, cp_status, cp_client)
+ values( '%s', '%s', '%s', '%s' ) ",
+ dbesc(self::$ob_hash),
+ dbesc(datetime_convert()),
+ dbesc('online'),
+ dbesc($_SERVER['REMOTE_ADDR'])
+ );
+ }
+ }
+
+ /**
+ * Chatpresence continued... if somebody hasn't pinged recently, they've most likely left the page
+ * and shouldn't count as online anymore. We allow an expection for bots.
+ */
+ q("delete from chatpresence where cp_last < %s - INTERVAL %s and cp_client != 'auto' ",
+ db_utcnow(),
+ db_quoteinterval('3 MINUTE')
+ );
+
+ $x = q("SELECT v FROM xconfig WHERE xchan = '%s' AND cat = 'sse' AND k = 'notifications'",
+ dbesc(self::$ob_hash)
+ );
+
+ if($x) {
+ $result = unserialize($x[0]['v']);
+ }
+
+ if($result) {
+ echo "event: notifications\n";
+ echo 'data: ' . json_encode($result);
+ echo "\n\n";
+
+ del_xconfig(self::$ob_hash, 'sse', 'notifications');
+ unset($result);
+ }
+
+ // always send heartbeat to detect disconnected clients
+ echo "event: heartbeat\n";
+ echo 'data: {}';
+ echo "\n\n";
+
+ ob_end_flush();
+ flush();
+
+ if(connection_status() != CONNECTION_NORMAL || connection_aborted()) {
+ break;
+ }
+
+ sleep($sleep_seconds);
+
+ }
+
+ }
+
+}
diff --git a/Zotlabs/Module/Sse_bs.php b/Zotlabs/Module/Sse_bs.php
new file mode 100644
index 000000000..672a6c5ef
--- /dev/null
+++ b/Zotlabs/Module/Sse_bs.php
@@ -0,0 +1,519 @@
+<?php
+
+namespace Zotlabs\Module;
+
+use App;
+use Zotlabs\Lib\Apps;
+use Zotlabs\Web\Controller;
+use Zotlabs\Lib\Enotify;
+
+class Sse_bs extends Controller {
+
+ public static $uid;
+ public static $ob_hash;
+ public static $vnotify;
+ public static $evdays;
+ public static $limit;
+ public static $offset;
+ public static $xchans;
+
+ function init() {
+
+ self::$uid = local_channel();
+ self::$ob_hash = get_observer_hash();
+ self::$vnotify = get_pconfig(self::$uid, 'system', 'vnotify');
+ self::$evdays = intval(get_pconfig(self::$uid, 'system', 'evdays'));
+ self::$limit = 100;
+ self::$offset = 0;
+ self::$xchans = '';
+
+ if(!empty($_GET['nquery']) && $_GET['nquery'] !== '%') {
+ $nquery = $_GET['nquery'];
+
+ $x = q("SELECT xchan_hash FROM xchan WHERE xchan_name LIKE '%s' OR xchan_addr LIKE '%s'",
+ dbesc($nquery . '%'),
+ dbesc($nquery . '%')
+ );
+
+ self::$xchans = ids_to_querystr($x, 'xchan_hash', true);
+ }
+
+ if(intval(argv(2)) > 0)
+ self::$offset = argv(2);
+ else
+ $_SESSION['sse_loadtime'] = datetime_convert();
+
+ $network = false;
+ $home = false;
+ $pubs = false;
+ $f = '';
+
+ switch (argv(1)) {
+ case 'network':
+ $network = true;
+ $f = 'bs_network';
+ break;
+ case 'home':
+ $home = true;
+ $f = 'bs_home';
+ break;
+ case 'pubs':
+ $pubs = true;
+ $f = 'bs_pubs';
+ break;
+ default:
+ }
+
+ //hz_syslog('init: ' . argv(1));
+ //hz_syslog('offset: ' . argv(2));
+
+ if(self::$offset && $f) {
+ $result = self::$f(true);
+ json_return_and_die($result);
+ }
+
+ $result = array_merge(
+ self::bs_network($network),
+ self::bs_home($home),
+ self::bs_notify(),
+ self::bs_intros(),
+ self::bs_forums(),
+ self::bs_pubs($pubs),
+ self::bs_files(),
+ self::bs_mail(),
+ self::bs_all_events(),
+ self::bs_register()
+ );
+
+ json_return_and_die($result);
+ }
+
+ function bs_network($notifications) {
+
+ $result['network']['notifications'] = [];
+ $result['network']['count'] = 0;
+
+ if(! self::$uid)
+ return $result;
+
+ $limit = intval(self::$limit);
+ $offset = self::$offset;
+
+ $sql_extra = '';
+ if(! (self::$vnotify & VNOTIFY_LIKE))
+ $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+
+ $sql_extra2 = '';
+ if(self::$xchans)
+ $sql_extra2 = " AND (author_xchan IN (" . self::$xchans . ") OR owner_xchan IN (" . self::$xchans . ")) ";
+
+ $item_normal = item_normal();
+
+ if ($notifications) {
+ $items = q("SELECT * FROM item
+ WHERE uid = %d
+ AND created <= '%s'
+ AND item_unseen = 1 AND item_wall = 0
+ AND author_xchan != '%s'
+ $item_normal
+ $sql_extra
+ $sql_extra2
+ ORDER BY created DESC LIMIT $limit OFFSET $offset",
+ intval(self::$uid),
+ dbescdate($_SESSION['sse_loadtime']),
+ dbesc(self::$ob_hash)
+ );
+
+ if($items) {
+ $result['network']['offset'] = ((count($items) == $limit) ? intval($offset + $limit) : -1);
+ xchan_query($items);
+ foreach($items as $item) {
+ $result['network']['notifications'][] = Enotify::format($item);
+ }
+ }
+ else {
+ $result['network']['offset'] = -1;
+ }
+
+ }
+
+ $r = q("SELECT count(id) as total FROM item
+ WHERE uid = %d and item_unseen = 1 AND item_wall = 0
+ $item_normal
+ $sql_extra
+ AND author_xchan != '%s'",
+ intval(self::$uid),
+ dbesc(self::$ob_hash)
+ );
+
+ if($r)
+ $result['network']['count'] = intval($r[0]['total']);
+
+ return $result;
+ }
+
+ function bs_home($notifications) {
+
+ $result['home']['notifications'] = [];
+ $result['home']['count'] = 0;
+
+ if(! self::$uid)
+ return $result;
+
+ $limit = intval(self::$limit);
+ $offset = self::$offset;
+
+ $sql_extra = '';
+ if(! (self::$vnotify & VNOTIFY_LIKE))
+ $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+
+ $sql_extra2 = '';
+ if(self::$xchans)
+ $sql_extra2 = " AND (author_xchan IN (" . self::$xchans . ") OR owner_xchan IN (" . self::$xchans . ")) ";
+
+
+ $item_normal = item_normal();
+
+ if ($notifications) {
+ $items = q("SELECT * FROM item
+ WHERE uid = %d
+ AND created <= '%s'
+ AND item_unseen = 1 AND item_wall = 1
+ AND author_xchan != '%s'
+ $item_normal
+ $sql_extra
+ $sql_extra2
+ ORDER BY created DESC LIMIT $limit OFFSET $offset",
+ intval(self::$uid),
+ dbescdate($_SESSION['sse_loadtime']),
+ dbesc(self::$ob_hash)
+ );
+
+ if($items) {
+ $result['home']['offset'] = ((count($items) == $limit) ? intval($offset + $limit) : -1);
+ xchan_query($items);
+ foreach($items as $item) {
+ $result['home']['notifications'][] = Enotify::format($item);
+ }
+ }
+ else {
+ $result['home']['offset'] = -1;
+ }
+
+ }
+
+ $r = q("SELECT count(id) as total FROM item
+ WHERE uid = %d and item_unseen = 1 AND item_wall = 1
+ $item_normal
+ $sql_extra
+ AND author_xchan != '%s'",
+ intval(self::$uid),
+ dbesc(self::$ob_hash)
+ );
+
+ if($r)
+ $result['home']['count'] = intval($r[0]['total']);
+
+ return $result;
+ }
+
+ function bs_pubs($notifications) {
+
+ $result['pubs']['notifications'] = [];
+ $result['pubs']['count'] = 0;
+
+ if(! isset($_SESSION['static_loadtime']))
+ $_SESSION['static_loadtime'] = datetime_convert();
+
+ $limit = intval(self::$limit);
+ $offset = self::$offset;
+
+ $sys = get_sys_channel();
+ $sql_extra = '';
+ if(! (self::$vnotify & VNOTIFY_LIKE))
+ $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+
+ $sql_extra2 = '';
+ if(self::$xchans)
+ $sql_extra2 = " AND (author_xchan IN (" . self::$xchans . ") OR owner_xchan IN (" . self::$xchans . ")) ";
+
+ $item_normal = item_normal();
+
+ if ($notifications) {
+ $items = q("SELECT * FROM item
+ WHERE uid = %d
+ AND created <= '%s'
+ AND item_unseen = 1
+ AND author_xchan != '%s'
+ AND created > '%s'
+ $item_normal
+ $sql_extra
+ $sql_extra2
+ ORDER BY created DESC LIMIT $limit OFFSET $offset",
+ intval($sys['channel_id']),
+ dbescdate($_SESSION['sse_loadtime']),
+ dbesc(self::$ob_hash),
+ dbescdate($_SESSION['static_loadtime'])
+ );
+
+ if($items) {
+ $result['pubs']['offset'] = ((count($items) == $limit) ? intval($offset + $limit) : -1);
+ xchan_query($items);
+ foreach($items as $item) {
+ $result['pubs']['notifications'][] = Enotify::format($item);
+ }
+ }
+ else {
+ $result['pubs']['offset'] = -1;
+ }
+
+
+ }
+
+ $r = q("SELECT count(id) as total FROM item
+ WHERE uid = %d AND item_unseen = 1
+ AND created > '%s'
+ $item_normal
+ $sql_extra
+ AND author_xchan != '%s'",
+ intval($sys['channel_id']),
+ dbescdate($_SESSION['static_loadtime']),
+ dbesc(self::$ob_hash)
+ );
+
+ if($r)
+ $result['pubs']['count'] = intval($r[0]['total']);
+
+ return $result;
+ }
+
+
+ function bs_notify() {
+
+ $result['notify']['notifications'] = [];
+ $result['notify']['count'] = 0;
+ $result['notify']['offset'] = -1;
+
+ if(! self::$uid)
+ return $result;
+
+ $r = q("SELECT * FROM notify WHERE uid = %d AND seen = 0 ORDER BY created DESC",
+ intval(self::$uid)
+ );
+ if($r) {
+ foreach($r as $rr) {
+ $result['notify']['notifications'][] = Enotify::format_notify($rr);
+ }
+ $result['notify']['count'] = count($r);
+ }
+
+ return $result;
+
+ }
+
+ function bs_intros() {
+
+ $result['intros']['notifications'] = [];
+ $result['intros']['count'] = 0;
+ $result['intros']['offset'] = -1;
+
+ if(! self::$uid)
+ return $result;
+
+ $r = q("SELECT * FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash where abook_channel = %d and abook_pending = 1 and abook_self = 0 and abook_ignored = 0 and xchan_deleted = 0 and xchan_orphan = 0 ORDER BY abook_created DESC LIMIT 50",
+ intval(self::$uid)
+ );
+
+ if($r) {
+ foreach($r as $rr) {
+ $result['intros']['notifications'][] = Enotify::format_intros($rr);
+ }
+ $result['intros']['count'] = count($r);
+ }
+
+ return $result;
+
+ }
+
+ function bs_forums() {
+
+ $result['forums']['notifications'] = [];
+ $result['forums']['count'] = 0;
+ $result['forums']['offset'] = -1;
+
+ if(! self::$uid)
+ return $result;
+
+ $forums = get_forum_channels(self::$uid);
+
+ if($forums) {
+ $item_normal = item_normal();
+
+ $sql_extra = '';
+ if(! (self::$vnotify & VNOTIFY_LIKE))
+ $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
+
+ $fcount = count($forums);
+ $i = 0;
+
+ for($x = 0; $x < $fcount; $x ++) {
+ $p = q("SELECT oid AS parent FROM term WHERE uid = %d AND ttype = %d AND term = '%s'",
+ intval(self::$uid),
+ intval(TERM_FORUM),
+ dbesc($forums[$x]['xchan_name'])
+ );
+
+ $p_str = ids_to_querystr($p, 'parent');
+ $p_sql = (($p_str) ? "OR parent IN ( $p_str )" : '');
+
+ $r = q("select count(id) as unseen from item
+ where uid = %d and ( owner_xchan = '%s' OR author_xchan = '%s' $p_sql ) and item_unseen = 1 $sql_extra $item_normal",
+ intval(self::$uid),
+ dbesc($forums[$x]['xchan_hash']),
+ dbesc($forums[$x]['xchan_hash'])
+ );
+
+ if($r[0]['unseen']) {
+ $forums[$x]['notify_link'] = (($forums[$x]['private_forum']) ? $forums[$x]['xchan_url'] : z_root() . '/network/?f=&pf=1&unseen=1&cid=' . $forums[$x]['abook_id']);
+ $forums[$x]['name'] = $forums[$x]['xchan_name'];
+ $forums[$x]['addr'] = $forums[$x]['xchan_addr'];
+ $forums[$x]['url'] = $forums[$x]['xchan_url'];
+ $forums[$x]['photo'] = $forums[$x]['xchan_photo_s'];
+ $forums[$x]['unseen'] = $r[0]['unseen'];
+ $forums[$x]['private_forum'] = (($forums[$x]['private_forum']) ? 'lock' : '');
+ $forums[$x]['message'] = (($forums[$x]['private_forum']) ? t('Private forum') : t('Public forum'));
+
+ unset($forums[$x]['abook_id']);
+ unset($forums[$x]['xchan_hash']);
+ unset($forums[$x]['xchan_name']);
+ unset($forums[$x]['xchan_url']);
+ unset($forums[$x]['xchan_photo_s']);
+
+ $i = $i + $r[0]['unseen'];
+
+ }
+ else {
+ unset($forums[$x]);
+ }
+ }
+
+ $result['forums']['count'] = $i;
+ $result['forums']['notifications'] = array_values($forums);
+
+ }
+
+ return $result;
+
+ }
+
+ function bs_files() {
+
+ $result['files']['notifications'] = [];
+ $result['files']['count'] = 0;
+ $result['files']['offset'] = -1;
+
+ if(! self::$uid)
+ return $result;
+
+ $r = q("SELECT * FROM item
+ WHERE verb = '%s'
+ AND obj_type = '%s'
+ AND uid = %d
+ AND owner_xchan != '%s'
+ AND item_unseen = 1",
+ dbesc(ACTIVITY_POST),
+ dbesc(ACTIVITY_OBJ_FILE),
+ intval(self::$uid),
+ dbesc(self::$ob_hash)
+ );
+ if($r) {
+ xchan_query($r);
+ foreach($r as $rr) {
+ $result['files']['notifications'][] = Enotify::format_files($rr);
+ }
+ $result['files']['count'] = count($r);
+ }
+
+ return $result;
+
+ }
+
+ function bs_mail() {
+
+ $result['mail']['notifications'] = [];
+ $result['mail']['count'] = 0;
+ $result['mail']['offset'] = -1;
+
+ if(! self::$uid)
+ return $result;
+
+ $r = q("select mail.*, xchan.* from mail left join xchan on xchan_hash = from_xchan
+ where channel_id = %d and mail_seen = 0 and mail_deleted = 0
+ and from_xchan != '%s' order by created desc",
+ intval(self::$uid),
+ dbesc(self::$ob_hash)
+ );
+
+ if($r) {
+ foreach($r as $rr) {
+ $result['mail']['notifications'][] = Enotify::format_mail($rr);
+ }
+ $result['mail']['count'] = count($r);
+ }
+
+ return $result;
+
+ }
+
+ function bs_all_events() {
+
+ $result['all_events']['notifications'] = [];
+ $result['all_events']['count'] = 0;
+ $result['all_events']['offset'] = -1;
+
+ if(! self::$uid)
+ return $result;
+
+ $r = q("SELECT * FROM event left join xchan on event_xchan = xchan_hash
+ WHERE event.uid = %d AND dtstart < '%s' AND dtstart > '%s' and dismissed = 0
+ and etype in ( 'event', 'birthday' )
+ ORDER BY dtstart DESC",
+ intval(self::$uid),
+ dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + ' . intval(self::$evdays) . ' days')),
+ dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days'))
+ );
+
+ if($r) {
+ foreach($r as $rr) {
+ $result['all_events']['notifications'][] = Enotify::format_all_events($rr);
+ }
+ $result['all_events']['count'] = count($r);
+ }
+
+ return $result;
+ }
+
+ function bs_register() {
+
+ $result['register']['notifications'] = [];
+ $result['register']['count'] = 0;
+ $result['register']['offset'] = -1;
+
+ if(! self::$uid && ! is_site_admin())
+ return $result;
+
+ $r = q("SELECT account_email, account_created from account where (account_flags & %d) > 0",
+ intval(ACCOUNT_PENDING)
+ );
+ if($r) {
+ foreach($r as $rr) {
+ $result['register']['notifications'][] = Enotify::format_register($rr);
+ }
+ $result['register']['count'] = count($r);
+ }
+
+ return $result;
+
+ }
+
+}
diff --git a/boot.php b/boot.php
index a9d93b5ae..9588d2c2a 100755
--- a/boot.php
+++ b/boot.php
@@ -1811,6 +1811,8 @@ function can_view_public_stream() {
* @param string $s Text to display
*/
function notice($s) {
+
+/*
if(! session_id())
return;
@@ -1826,6 +1828,34 @@ function notice($s) {
if(App::$interactive) {
$_SESSION['sysmsg'][] = $s;
}
+*/
+
+ $hash = get_observer_hash();
+
+ if (! $hash)
+ return;
+
+
+ $t = get_xconfig($hash, 'sse', 'timestamp');
+
+ if(datetime_convert('UTC', 'UTC', $t) < datetime_convert('UTC', 'UTC', '- 30 seconds')) {
+ del_xconfig($hash, 'sse', 'notifications');
+ }
+
+ $x = get_xconfig($hash, 'sse', 'notifications');
+
+ if ($x === false)
+ $x = [];
+
+ if (isset($x['notice']) && in_array($s, $x['notice']['notifications']))
+ return;
+
+ if (App::$interactive) {
+ $x['notice']['notifications'][] = $s;
+ set_xconfig($hash, 'sse', 'timestamp', datetime_convert());
+ set_xconfig($hash, 'sse', 'notifications', $x);
+ }
+
}
/**
@@ -1839,8 +1869,11 @@ function notice($s) {
* @param string $s Text to display
*/
function info($s) {
+
+/*
if(! session_id())
return;
+
if(! x($_SESSION, 'sysmsg_info'))
$_SESSION['sysmsg_info'] = array();
@@ -1849,6 +1882,33 @@ function info($s) {
if(App::$interactive)
$_SESSION['sysmsg_info'][] = $s;
+*/
+
+ $hash = get_observer_hash();
+
+ if (! $hash)
+ return;
+
+ $t = get_xconfig($hash, 'sse', 'timestamp');
+
+ if(datetime_convert('UTC', 'UTC', $t) < datetime_convert('UTC', 'UTC', '- 30 seconds')) {
+ del_xconfig($hash, 'sse', 'notifications');
+ }
+
+ $x = get_xconfig($hash, 'sse', 'notifications');
+
+ if($x === false)
+ $x = [];
+
+ if(isset($x['info']) && in_array($s, $x['info']['notifications']))
+ return;
+
+ if(App::$interactive) {
+ $x['info']['notifications'][] = $s;
+ set_xconfig($hash, 'sse', 'timestamp', datetime_convert());
+ set_xconfig($hash, 'sse', 'notifications', $x);
+ }
+
}
/**
diff --git a/doc/hooklist.bb b/doc/hooklist.bb
index a923e7ae3..4774b9978 100644
--- a/doc/hooklist.bb
+++ b/doc/hooklist.bb
@@ -259,6 +259,9 @@ Hooks allow plugins/addons to "hook into" the code at many points and alter the
[zrl=[baseurl]/help/hook/enotify_store]enotify_store[/zrl]
called when storing a notification record
+[zrl=[baseurl]/help/hook/enotify_store_end]enotify_store_end[/zrl]
+ called after a notification record has been stored
+
[zrl=[baseurl]/help/hook/event_created]event_created[/zrl]
called when an event record is created
diff --git a/include/channel.php b/include/channel.php
index 29835eac6..32bd596fc 100644
--- a/include/channel.php
+++ b/include/channel.php
@@ -2953,3 +2953,8 @@ function pchan_to_chan($pchan) {
function channel_url($channel) {
return (($channel) ? z_root() . '/channel/' . $channel['channel_address'] : z_root());
}
+
+function get_channel_hashes() {
+ $r = q("SELECT channel_hash FROM channel WHERE channel_removed = 0");
+ return flatten_array_recursive($r);
+}
diff --git a/include/event.php b/include/event.php
index 6be1b6705..9d76aabd6 100644
--- a/include/event.php
+++ b/include/event.php
@@ -553,9 +553,19 @@ function event_store_event($arr) {
dbesc($hash),
intval($arr['uid'])
);
- if($r)
+ if($r) {
+
+ /**
+ * @hooks event_store_event_end
+ * Called after an event record was stored.
+ * * \e array \b event
+ */
+ call_hooks('event_store_event_end', $r[0]);
+
return $r[0];
+ }
+
return false;
}
diff --git a/view/css/widgets.css b/view/css/widgets.css
index 995647d1c..ca7267189 100644
--- a/view/css/widgets.css
+++ b/view/css/widgets.css
@@ -181,7 +181,6 @@ a.wikilist {
.notifications-textinput {
padding: .75rem 0.85rem;
- position: relative;
}
.notifications-textinput input {
@@ -233,3 +232,7 @@ a.wikilist {
margin-top: 25px;
opacity: 0.8;
}
+
+#cid-filter-wrapper {
+ position: relative;
+}
diff --git a/view/de-de/hstrings.php b/view/de-de/hstrings.php
index 5e16040e2..2f411a1d1 100644
--- a/view/de-de/hstrings.php
+++ b/view/de-de/hstrings.php
@@ -77,7 +77,7 @@ App::$strings["Search"] = "Suche";
App::$strings["Search site @name, !forum, #tag, ?docs, content"] = "Hub durchsuchen: @Name, !Forum, #Schlagwort, ?Dokumentation, Inhalt";
App::$strings["Admin"] = "Administration";
App::$strings["Site Setup and Configuration"] = "Seiten-Einrichtung und -Konfiguration";
-App::$strings["Loading"] = "Lädt...";
+App::$strings["Loading"] = "Lädt";
App::$strings["@name, !forum, #tag, ?doc, content"] = "@Name, !Forum, #Schlagwort, ?Dokumentation, Inhalt";
App::$strings["Please wait..."] = "Bitte warten...";
App::$strings["Add Apps"] = "Apps hinzufügen";
diff --git a/view/js/main.js b/view/js/main.js
index f3b8151b0..f15636a35 100644
--- a/view/js/main.js
+++ b/view/js/main.js
@@ -24,7 +24,31 @@ var contentHeightDiff = 0;
var liveRecurse = 0;
var savedTitle = '';
var initialLoad = true;
+var window_needs_alert = true;
+
+var sse_bs_active = false;
+var sse_offset = 0;
+var sse_type;
+var sse_partial_result = false;
+
+// take care of tab/window reloads on channel change
+if(localStorage.getItem('uid') !== localUser.toString()) {
+ localStorage.setItem('uid', localUser.toString());
+}
+window.onstorage = function(e) {
+ if(e.key === 'uid' && parseInt(e.newValue) !== localUser) {
+ if(window_needs_alert) {
+ window_needs_alert = false;
+ localStorage.clear();
+ sessionStorage.clear();
+ alert("Your identity has changed. Page reload required!");
+ window.location.reload();
+ return;
+ }
+ }
+}
+/*
// Clear the session and local storage if we switch channel or log out
var cache_uid = '';
if(sessionStorage.getItem('uid') !== null) {
@@ -35,6 +59,7 @@ if(cache_uid !== localUser.toString()) {
localStorage.clear();
sessionStorage.setItem('uid', localUser.toString());
}
+*/
$.ajaxSetup({cache: false});
@@ -55,28 +80,114 @@ $(document).ready(function() {
}
});
- var tf = new Function('n', 's', 'var k = s.split("/")['+aStr['plural_func']+']; return (k ? k : s);');
-
- jQuery.timeago.settings.strings = {
- prefixAgo : aStr['t01'],
- prefixFromNow : aStr['t02'],
- suffixAgo : aStr['t03'],
- suffixFromNow : aStr['t04'],
- seconds : aStr['t05'],
- minute : aStr['t06'],
- minutes : function(value){return tf(value, aStr['t07']);},
- hour : aStr['t08'],
- hours : function(value){return tf(value, aStr['t09']);},
- day : aStr['t10'],
- days : function(value){return tf(value, aStr['t11']);},
- month : aStr['t12'],
- months : function(value){return tf(value, aStr['t13']);},
- year : aStr['t14'],
- years : function(value){return tf(value, aStr['t15']);},
- wordSeparator : aStr['t16'],
- numbers : aStr['t17'],
- };
+ var tf = new Function('n', 's', 'var k = s.split("/")['+aStr['plural_func']+']; return (k ? k : s);');
+
+ jQuery.timeago.settings.strings = {
+ prefixAgo : aStr['t01'],
+ prefixFromNow : aStr['t02'],
+ suffixAgo : aStr['t03'],
+ suffixFromNow : aStr['t04'],
+ seconds : aStr['t05'],
+ minute : aStr['t06'],
+ minutes : function(value){return tf(value, aStr['t07']);},
+ hour : aStr['t08'],
+ hours : function(value){return tf(value, aStr['t09']);},
+ day : aStr['t10'],
+ days : function(value){return tf(value, aStr['t11']);},
+ month : aStr['t12'],
+ months : function(value){return tf(value, aStr['t13']);},
+ year : aStr['t14'],
+ years : function(value){return tf(value, aStr['t15']);},
+ wordSeparator : aStr['t16'],
+ numbers : aStr['t17'],
+ };
+
+
+ if(typeof(window.SharedWorker) === 'undefined') {
+ // notifications with multiple tabs open will not work very well in this scenario
+ var evtSource = new EventSource('/sse');
+
+ evtSource.addEventListener('notifications', function(e) {
+ var obj = JSON.parse(e.data);
+ sse_handleNotifications(obj, false, false);
+ }, false);
+
+ document.addEventListener('visibilitychange', function() {
+ if (!document.hidden) {
+ sse_offset = 0;
+ sse_bs_init();
+ }
+ }, false);
+
+ }
+ else {
+ var myWorker = new SharedWorker('/view/js/sse_worker.js', localUser);
+
+ myWorker.port.onmessage = function(e) {
+ obj = e.data;
+ console.log(obj);
+ sse_handleNotifications(obj, false, false);
+ }
+
+ myWorker.onerror = function(e) {
+ myWorker.port.close();
+ }
+
+ myWorker.port.start();
+ }
+
+ sse_bs_init();
+
+ $('.notification-link').on('click', { replace: true, followup: false }, sse_bs_notifications);
+
+ $('.notification-filter').on('keypress', function(e) {
+ if(e.which == 13) { // enter
+ this.blur();
+ sse_offset = 0;
+ $("#nav-" + sse_type + "-menu").html('');
+ $("#nav-" + sse_type + "-loading").show();
+
+ var cn_val = $('#cn-' + sse_type + '-input').length ? $('#cn-' + sse_type + '-input').val().toString().toLowerCase() : '';
+
+ $.get('/sse_bs/' + sse_type + '/' + sse_offset + '?nquery=' + encodeURIComponent(cn_val), function(obj) {
+ console.log('sse: bootstraping ' + sse_type);
+ console.log(obj);
+ sse_handleNotifications(obj, true, false);
+ sse_bs_active = false;
+ sse_partial_result = true;
+ sse_offset = obj[sse_type].offset;
+ if(sse_offset < 0)
+ $("#nav-" + sse_type + "-loading").hide();
+
+ });
+ }
+ });
+
+ $('.notifications-textinput-clear').on('click', function(e) {
+ if(! sse_partial_result)
+ return;
+
+ $("#nav-" + sse_type + "-menu").html('');
+ $("#nav-" + sse_type + "-loading").show();
+ $.get('/sse_bs/' + sse_type, function(obj) {
+ console.log('sse: bootstraping ' + sse_type);
+ console.log(obj);
+ sse_handleNotifications(obj, true, false);
+ sse_bs_active = false;
+ sse_partial_result = false;
+ sse_offset = obj[sse_type].offset;
+ if(sse_offset < 0)
+ $("#nav-" + sse_type + "-loading").hide();
+
+ });
+ });
+ $('.notification-content').on('scroll', function() {
+ if(this.scrollTop > this.scrollHeight - this.clientHeight - (this.scrollHeight/7)) {
+ if(!sse_bs_active)
+ sse_bs_notifications(sse_type, false, true);
+ }
+ });
//mod_mail only
$(".mail-conv-detail .autotime").timeago();
@@ -85,6 +196,7 @@ $(document).ready(function() {
updateInit();
+/*
$('a.notification-link').click(function(e){
var notifyType = $(this).data('type');
@@ -102,6 +214,7 @@ $(document).ready(function() {
$('#nav-' + notifyType + '-sub').addClass('show');
loadNotificationItems(notifyType);
}
+*/
// Allow folks to stop the ajax page updates with the pause/break key
$(document).keydown(function(event) {
@@ -462,6 +575,7 @@ function markItemRead(itemId) {
$('.unseen-wall-indicator-'+itemId).hide();
}
+/*
function notificationsUpdate(cached_data) {
var pingCmd = 'ping' + ((localUser != 0) ? '?f=&uid=' + localUser : '');
@@ -545,7 +659,7 @@ function handleNotifications(data) {
$.each(data, function(index, item) {
//do not process those
- var arr = ['notice', 'info', 'invalid'];
+ var arr = ['notice', 'info', 'invalid', 'network', 'home', 'notify', 'intros'];
if(arr.indexOf(index) !== -1)
return;
@@ -565,10 +679,11 @@ function handleNotificationsItems(notifyType, data) {
notify_menu.html('');
- $(data).each(function() {
+ $(data.reverse()).each(function() {
html = notifications_tpl.format(this.notify_link,this.photo,this.name,this.addr,this.message,this.when,this.hclass,this.b64mid,this.notify_id,this.thread_top,this.unseen,this.private_forum);
- notify_menu.append(html);
+ notify_menu.prepend(html);
});
+ $(".notifications-autotime").timeago();
datasrc2src('#notifications .notification img[data-src]');
@@ -578,6 +693,7 @@ function handleNotificationsItems(notifyType, data) {
if($('#cn-' + notifyType + '-input').length) {
var filter = $('#cn-' + notifyType + '-input').val().toString().toLowerCase();
if(filter) {
+ filter = filter.indexOf('%') == 0 ? filter.substring(1) : filter;
$('#nav-' + notifyType + '-menu .notification').each(function(i, el){
var cn = $(el).data('contact_name').toString().toLowerCase();
var ca = $(el).data('contact_addr').toString().toLowerCase();
@@ -589,6 +705,7 @@ function handleNotificationsItems(notifyType, data) {
}
}
}
+*/
function contextualHelp() {
var container = $("#contextual-help-content");
@@ -718,13 +835,10 @@ function updateConvItems(mode,data) {
}
}
-
-
// trigger the autotime function on all newly created content
-
$("> .wall-item-outside-wrapper .autotime, > .thread-wrapper .autotime",this).timeago();
$("> .shared_header .autotime",this).timeago();
-
+
if((mode === 'append' || mode === 'replace') && (loadingPage)) {
loadingPage = false;
}
@@ -744,6 +858,42 @@ function updateConvItems(mode,data) {
}
});
+
+ // take care of the notifications count updates
+ $('.thread-wrapper', data).each(function() {
+
+ var nmid = $(this).data('b64mid');
+
+ if($('.notification[data-b64mid=\'' + nmid + '\']').length) {
+ $('.notification[data-b64mid=\'' + nmid + '\']').each(function() {
+ var n = this.parentElement.id.split('-');
+
+ if(n[1] === 'pubs')
+ return true;
+
+ if(n[1] === 'notify' && (nmid !== bParam_mid || sse_type !== 'notify'))
+ return true;
+
+ var count = Number($('.' + n[1] + '-update').html());
+
+ if(count > 0)
+ count = count - 1;
+
+ if(count < 1) {
+ $('.' + n[1] + '-button').fadeOut();
+ $('.' + n[1] + '-update').html(count);
+ }
+ else
+ $('.' + n[1] + '-update').html(count);
+
+ $('#nav-' + n[1] + '-menu .notification[data-b64mid=\'' + nmid + '\']').fadeOut();
+
+ });
+ }
+
+ });
+
+
// reset rotators and cursors we may have set before reaching this place
$('.like-rotator').hide();
@@ -802,18 +952,15 @@ function updateConvItems(mode,data) {
function scrollToItem() {
// auto-scroll to a particular comment in a thread (designated by mid) when in single-thread mode
- // use the same method to generate the submid as we use in ThreadItem,
- // base64_encode + replace(['+','='],['','']);
if(justifiedGalleryActive)
return;
var submid = ((bParam_mid.length) ? bParam_mid : 'abcdefg');
var encoded = ((submid.substr(0,4) == 'b64.') ? true : false);
- var submid_encoded = ((encoded) ? submid.substr(4) : window.btoa(submid));
+ var submid_encoded = ((encoded) ? submid : window.btoa(submid));
- submid_encoded = submid_encoded.replace(/[\+\=]/g,'');
- if($('.item_' + submid_encoded).length && !$('.item_' + submid_encoded).hasClass('toplevel_item')) {
+ if($('.thread-wrapper[data-b64mid=\'' + submid_encoded + '\']').length && !$('.thread-wrapper[data-b64mid=\'' + submid_encoded + '\']').hasClass('toplevel_item')) {
if($('.collapsed-comments').length) {
var scrolltoid = $('.collapsed-comments').attr('id').substring(19);
$('#collapsed-comments-' + scrolltoid + ' .autotime').timeago();
@@ -821,8 +968,8 @@ function scrollToItem() {
$('#hide-comments-' + scrolltoid).html(aStr.showfewer);
$('#hide-comments-total-' + scrolltoid).hide();
}
- $('html, body').animate({ scrollTop: $('.item_' + submid_encoded).offset().top - $('nav').outerHeight(true) }, 'slow');
- $('.item_' + submid_encoded).addClass('item-highlight');
+ $('html, body').animate({ scrollTop: $('.thread-wrapper[data-b64mid=\'' + submid_encoded + '\']').offset().top - $('nav').outerHeight(true) }, 'slow');
+ $('.thread-wrapper[data-b64mid=\'' + submid_encoded + '\']').addClass('item-highlight');
}
}
@@ -885,6 +1032,7 @@ function updateInit() {
// if($('#live-cards').length) { src = 'cards'; }
// if($('#live-articles').length) { src = 'articles'; }
+/*
if (initialLoad && (sessionStorage.getItem('notifications_cache') !== null)) {
var cached_data = JSON.parse(sessionStorage.getItem('notifications_cache'));
notificationsUpdate(cached_data);
@@ -898,9 +1046,10 @@ function updateInit() {
}
}
+*/
if(! src) {
- notificationsUpdate();
+ // notificationsUpdate();
}
else {
liveUpdate();
@@ -1039,7 +1188,7 @@ function liveUpdate(notify_id) {
})
.done(function() {
- notificationsUpdate();
+ // notificationsUpdate();
});
}
@@ -1088,7 +1237,9 @@ function justifyPhotosAjax(id) {
$('#' + id).justifiedGallery('norewind').on('jg.complete', function(e){ justifiedGalleryActive = false; });
}
+/*
function loadNotificationItems(notifyType) {
+
var pingExCmd = 'ping/' + notifyType + ((localUser != 0) ? '?f=&uid=' + localUser : '');
var clicked = $('[data-type=\'' + notifyType + '\']').data('clicked');
@@ -1119,6 +1270,7 @@ function loadNotificationItems(notifyType) {
}
});
}
+*/
// Since our ajax calls are asynchronous, we will give a few
// seconds for the first ajax call (setting like/dislike), then
@@ -1169,7 +1321,7 @@ function doscroll(parent, hidden) {
}
}
back.remove();
- var id = $('[data-mid="' + parent + '"]');
+ var id = $('[data-b64mid="' + parent + '"]');
$('html, body').animate({scrollTop:(id.offset().top) - 50}, 'slow');
$('<a href="javascript:doscrollback(' + pos + ');" id="back-to-reply" class="float-right" title="' + aStr['to_reply'] + '"><i class="fa fa-angle-double-down">&nbsp;&nbsp;&nbsp;</i></a>').insertBefore('#wall-item-info-' + id.attr('id').replace(/\D/g,''));
}
@@ -1653,3 +1805,306 @@ function zid(s) {
return s;
}
+
+function sse_bs_init() {
+ if(sessionStorage.getItem('notification_open') !== null || typeof sse_type !== 'undefined' ) {
+ if(typeof sse_type === 'undefined')
+ sse_type = sessionStorage.getItem('notification_open');
+
+ $("#nav-" + sse_type + "-sub").addClass('show');
+ sse_bs_notifications(sse_type, true, false);
+ }
+ else {
+ $.get('/sse_bs',function(obj) {
+ console.log(obj);
+ sse_handleNotifications(obj, true, false);
+ });
+ }
+}
+
+function sse_bs_notifications(e, replace, followup) {
+
+ sse_bs_active = true;
+ var manual = false;
+
+ if(typeof replace === 'undefined')
+ replace = e.data.replace;
+
+ if(typeof followup === 'undefined')
+ followup = e.data.followup;
+
+ if(typeof e === 'string') {
+ sse_type = e;
+ }
+ else {
+ manual = true;
+ sse_offset = 0;
+ sse_type = e.target.dataset.sse_type;
+ }
+
+ if(typeof sse_type === 'undefined')
+ return;
+
+ if(followup || !manual || !($('#nav-' + sse_type + '-sub').hasClass('collapse') && $('#nav-' + sse_type + '-sub').hasClass('show'))) {
+
+ if(sse_offset >= 0) {
+ $("#nav-" + sse_type + "-loading").show();
+ }
+
+ sessionStorage.setItem('notification_open', sse_type);
+ if(sse_offset !== -1 || replace) {
+
+ var cn_val = (($('#cn-' + sse_type + '-input').length && sse_partial_result) ? $('#cn-' + sse_type + '-input').val().toString().toLowerCase() : '');
+
+ $.get('/sse_bs/' + sse_type + '/' + sse_offset + '?nquery=' + encodeURIComponent(cn_val), function(obj) {
+ console.log('sse: bootstraping ' + sse_type);
+ console.log(obj);
+ sse_handleNotifications(obj, replace, followup);
+ sse_bs_active = false;
+ sse_offset = obj[sse_type].offset;
+ if(sse_offset < 0)
+ $("#nav-" + sse_type + "-loading").hide();
+
+ });
+ }
+ else
+ $("#nav-" + sse_type + "-loading").hide();
+
+ }
+ else {
+ sessionStorage.removeItem('notification_open');
+ }
+}
+
+
+
+function sse_handleNotifications(obj, replace, followup) {
+
+ if(
+ (obj.network && obj.network.count) ||
+ (obj.home && obj.home.count) ||
+ (obj.intros && obj.intros.count) ||
+ (obj.register && obj.register.count) ||
+ (obj.mail && obj.mail.count) ||
+ (obj.all_events && obj.all_events.count) ||
+ (obj.notify && obj.notify.count) ||
+ (obj.files && obj.files.count) ||
+ (obj.pubs && obj.pubs.count) ||
+ (obj.forums && obj.forums.count)
+ ) {
+ $('.notifications-btn').css('opacity', 1);
+ $('#no_notifications').hide();
+ }
+ else {
+ $('.notifications-btn').css('opacity', 0.5);
+ $('#navbar-collapse-1').removeClass('show');
+ $('#no_notifications').show();
+ }
+
+ if(
+ (obj.home && obj.home.count) ||
+ (obj.intros && obj.intros.count) ||
+ (obj.register && obj.register.count) ||
+ (obj.mail && obj.mail.count) ||
+ (obj.notify && obj.notify.count) ||
+ (obj.files && obj.files.count)
+ ) {
+ $('.notifications-btn-icon').removeClass('fa-exclamation-circle');
+ $('.notifications-btn-icon').addClass('fa-exclamation-triangle');
+ }
+ if(
+ !(obj.home && obj.home.count) &&
+ !(obj.intros && obj.intros.count) &&
+ !(obj.register && obj.register.count) &&
+ !(obj.mail && obj.mail.count) &&
+ !(obj.notify && obj.notify.count) &&
+ !(obj.files && obj.files.count)
+ ) {
+ $('.notifications-btn-icon').removeClass('fa-exclamation-triangle');
+ $('.notifications-btn-icon').addClass('fa-exclamation-circle');
+ }
+ if(obj.all_events_today && obj.all_events_today.count) {
+ $('.all_events-update').removeClass('badge-secondary');
+ $('.all_events-update').addClass('badge-danger');
+ }
+ else {
+ $('.all_events-update').removeClass('badge-danger');
+ $('.all_events-update').addClass('badge-secondary');
+ }
+
+ // network
+ if(obj.network && obj.network.count) {
+ $('.network-button').fadeIn();
+ if(replace || followup)
+ $('.network-update').html(Number(obj.network.count));
+ else
+ $('.network-update').html(Number(obj.network.count) + Number($('.network-update').html()));
+ }
+ if(obj.network && obj.network.notifications.length)
+ sse_handleNotificationsItems('network', obj.network.notifications, replace, followup);
+
+ // home
+ if(obj.home && obj.home.count) {
+ $('.home-button').fadeIn();
+ if(replace || followup)
+ $('.home-update').html(Number(obj.home.count));
+ else
+ $('.home-update').html(Number(obj.home.count) + Number($('.home-update').html()));
+ }
+ if(obj.home && obj.home.notifications.length)
+ sse_handleNotificationsItems('home', obj.home.notifications, replace, followup);
+
+ // notify
+ if(obj.notify && obj.notify.count) {
+ $('.notify-button').fadeIn();
+ if(replace || followup)
+ $('.notify-update').html(Number(obj.notify.count));
+ else
+ $('.notify-update').html(Number(obj.notify.count) + Number($('.notify-update').html()));
+
+ }
+ if(obj.notify && obj.notify.notifications.length)
+ sse_handleNotificationsItems('notify', obj.notify.notifications, replace, false);
+
+ // intros
+ if(obj.intros && obj.intros.count) {
+ $('.intros-button').fadeIn();
+ if(replace || followup)
+ $('.intros-update').html(Number(obj.intros.count));
+ else
+ $('.intros-update').html(Number(obj.intros.count) + Number($('.intros-update').html()));
+ }
+ if(obj.intros && obj.intros.notifications.length)
+ sse_handleNotificationsItems('intros', obj.intros.notifications, replace, false);
+
+ // forums
+ if(obj.forums && obj.forums.count) {
+ $('.forums-button').fadeIn();
+ if(replace || followup)
+ $('.forums-update').html(Number(obj.forums.count));
+ else
+ $('.forums-update').html(Number(obj.forums.count) + Number($('.forums-update').html()));
+ }
+ if(obj.forums && obj.forums.notifications.length)
+ sse_handleNotificationsItems('forums', obj.forums.notifications, replace, false);
+
+ // pubs
+ if(obj.pubs && obj.pubs.count) {
+ $('.pubs-button').fadeIn();
+ if(replace || followup)
+ $('.pubs-update').html(Number(obj.pubs.count));
+ else
+ $('.pubs-update').html(Number(obj.pubs.count) + Number($('.pubs-update').html()));
+ }
+ if(obj.pubs && obj.pubs.notifications.length)
+ sse_handleNotificationsItems('pubs', obj.pubs.notifications, replace, followup);
+
+ // files
+ if(obj.files && obj.files.count) {
+ $('.files-button').fadeIn();
+ if(replace || followup)
+ $('.files-update').html(Number(obj.files.count));
+ else
+ $('.files-update').html(Number(obj.files.count) + Number($('.files-update').html()));
+ }
+ if(obj.files && obj.files.notifications.length)
+ sse_handleNotificationsItems('files', obj.files.notifications, replace, false);
+
+ // mail
+ if(obj.mail && obj.mail.count) {
+ $('.mail-button').fadeIn();
+ if(replace || followup)
+ $('.mail-update').html(Number(obj.mail.count));
+ else
+ $('.mail-update').html(Number(obj.mail.count) + Number($('.mail-update').html()));
+ }
+ if(obj.mail && obj.mail.notifications.length)
+ sse_handleNotificationsItems('mail', obj.mail.notifications, replace, false);
+
+ // all_events
+ if(obj.all_events && obj.all_events.count) {
+ $('.all_events-button').fadeIn();
+ if(replace || followup)
+ $('.all_events-update').html(Number(obj.all_events.count));
+ else
+ $('.all_events-update').html(Number(obj.all_events.count) + Number($('.all_events-update').html()));
+ }
+ if(obj.all_events && obj.all_events.notifications.length)
+ sse_handleNotificationsItems('all_events', obj.all_events.notifications, replace, false);
+
+ // register
+ if(obj.register && obj.register.count) {
+ $('.register-button').fadeIn();
+ if(replace || followup)
+ $('.register-update').html(Number(obj.register.count));
+ else
+ $('.register-update').html(Number(obj.register.count) + Number($('.register-update').html()));
+ }
+ if(obj.register && obj.register.notifications.length)
+ sse_handleNotificationsItems('register', obj.register.notifications, replace, false);
+
+ // notice and info
+ $.jGrowl.defaults.closerTemplate = '<div>[ ' + aStr.closeAll + ']</div>';
+ if(obj.notice) {
+ $(obj.notice.notifications).each(function() {
+ $.jGrowl(this, { sticky: true, theme: 'notice' });
+ });
+ }
+ if(obj.info) {
+ $(obj.info.notifications).each(function(){
+ $.jGrowl(this, { sticky: false, theme: 'info', life: 10000 });
+ });
+ }
+
+}
+
+function sse_handleNotificationsItems(notifyType, data, replace, followup) {
+ console.log('replace: ' + replace);
+ console.log('followup: ' + followup);
+ var notifications_tpl = ((notifyType == 'forums') ? unescape($("#nav-notifications-forums-template[rel=template]").html()) : unescape($("#nav-notifications-template[rel=template]").html()));
+ var notify_menu = $("#nav-" + notifyType + "-menu");
+ var notify_loading = $("#nav-" + notifyType + "-loading");
+ var notify_count = $("." + notifyType + "-update");
+
+ if(replace && !followup) {
+ notify_menu.html('');
+ notify_loading.hide();
+ }
+
+ $(data).each(function() {
+ html = notifications_tpl.format(this.notify_link,this.photo,this.name,this.addr,this.message,this.when,this.hclass,this.b64mid,this.notify_id,this.thread_top,this.unseen,this.private_forum);
+ notify_menu.append(html);
+ });
+
+ if(!replace && !followup) {
+ console.log('sorting');
+ $("#nav-" + notifyType + "-menu .notification").sort(function(a,b) {
+ a = new Date(a.dataset.when);
+ b = new Date(b.dataset.when);
+ return a > b ? -1 : a < b ? 1 : 0;
+ }).appendTo('#nav-' + notifyType + '-menu');
+ }
+
+ $(document.body).trigger("sticky_kit:recalc");
+ $("#nav-" + notifyType + "-menu .notifications-autotime").timeago();
+
+ if($('#tt-' + notifyType + '-only').hasClass('active'))
+ $('#nav-' + notifyType + '-menu [data-thread_top=false]').addClass('d-none');
+
+ if($('#cn-' + notifyType + '-input').length) {
+ var filter = $('#cn-' + notifyType + '-input').val().toString().toLowerCase();
+ if(filter) {
+ filter = filter.indexOf('%') == 0 ? filter.substring(1) : filter;
+
+ $('#nav-' + notifyType + '-menu .notification').each(function(i, el) {
+ var cn = $(el).data('contact_name').toString().toLowerCase();
+ var ca = $(el).data('contact_addr').toString().toLowerCase();
+ if(cn.indexOf(filter) === -1 && ca.indexOf(filter) === -1)
+ $(el).addClass('d-none');
+ else
+ $(el).removeClass('d-none');
+ });
+ }
+ }
+}
+
diff --git a/view/js/sse_worker.js b/view/js/sse_worker.js
new file mode 100755
index 000000000..78e4aa51b
--- /dev/null
+++ b/view/js/sse_worker.js
@@ -0,0 +1,14 @@
+var evtSource = new EventSource('/sse');
+
+onconnect = function(e) {
+
+ var port = e.ports[0];
+
+ port.start();
+
+ evtSource.addEventListener('notifications', function(e) {
+ var obj = JSON.parse(e.data);
+ port.postMessage(obj);
+ }, false);
+
+}
diff --git a/view/php/theme_init.php b/view/php/theme_init.php
index d683a3b58..d47325b77 100644
--- a/view/php/theme_init.php
+++ b/view/php/theme_init.php
@@ -37,6 +37,7 @@ head_add_js('/library/colorbox/jquery.colorbox-min.js');
head_add_js('/library/jquery.AreYouSure/jquery.are-you-sure.js');
head_add_js('/library/tableofcontents/jquery.toc.js');
head_add_js('/vendor/desandro/imagesloaded/imagesloaded.pkgd.min.js');
+
/**
* Those who require this feature will know what to do with it.
* Those who don't, won't.
diff --git a/view/tpl/activity_filter_widget.tpl b/view/tpl/activity_filter_widget.tpl
index 7d10100ba..779786828 100644
--- a/view/tpl/activity_filter_widget.tpl
+++ b/view/tpl/activity_filter_widget.tpl
@@ -9,7 +9,7 @@
</h3>
{{$content}}
{{if $name}}
- <div class="notifications-textinput">
+ <div id="cid-filter-wrapper" class="notifications-textinput">
<form method="get" action="{{$name.url}}" role="search">
<div class="text-muted notifications-textinput-filter"><i class="fa fa-fw fa-filter"></i></div>
<input id="cid" type="hidden" value="" name="cid" />
diff --git a/view/tpl/conv_item.tpl b/view/tpl/conv_item.tpl
index 186551e2d..f639683b8 100755
--- a/view/tpl/conv_item.tpl
+++ b/view/tpl/conv_item.tpl
@@ -4,9 +4,9 @@
</div>
<div id="collapsed-comments-{{$item.id}}" class="collapsed-comments" style="display: none;">
{{/if}}
- <div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry {{else}} u-comment h-cite {{/if}} item_{{$item.submid}}">
+ <div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry {{else}} u-comment h-cite{{/if}}" data-b64mid="{{$item.mid}}">
<a name="item_{{$item.id}}" ></a>
- <div class="wall-item-outside-wrapper{{if $item.is_comment}} comment{{/if}}{{if $item.previewing}} preview{{/if}}" data-mid="{{$item.mid}}" id="wall-item-outside-wrapper-{{$item.id}}" >
+ <div class="wall-item-outside-wrapper{{if $item.is_comment}} comment{{/if}}{{if $item.previewing}} preview{{/if}}" id="wall-item-outside-wrapper-{{$item.id}}" >
<div class="clearfix wall-item-content-wrapper{{if $item.is_comment}} comment{{/if}}" id="wall-item-content-wrapper-{{$item.id}}">
{{if $item.photo}}
<div class="wall-photo-item" id="wall-photo-item-{{$item.id}}">
diff --git a/view/tpl/conv_list.tpl b/view/tpl/conv_list.tpl
index 8c5b47bf3..b244311bb 100755
--- a/view/tpl/conv_list.tpl
+++ b/view/tpl/conv_list.tpl
@@ -4,7 +4,7 @@
</div>
<div id="collapsed-comments-{{$item.id}}" class="collapsed-comments" style="display: none;">
{{/if}}
- <div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry {{else}} u-comment h-cite {{/if}} item_{{$item.submid}}">
+ <div id="thread-wrapper-{{$item.id}}" class="thread-wrapper{{if $item.toplevel}} {{$item.toplevel}} generic-content-wrapper h-entry {{else}} u-comment h-cite {{/if}}" data-b64mid="{{$item.mid}}">
<a name="item_{{$item.id}}" ></a>
<div class="wall-item-outside-wrapper{{if $item.is_comment}} comment{{/if}}{{if $item.previewing}} preview{{/if}}" id="wall-item-outside-wrapper-{{$item.id}}" >
<div class="clearfix wall-item-content-wrapper{{if $item.is_comment}} comment{{/if}}" id="wall-item-content-wrapper-{{$item.id}}">
diff --git a/view/tpl/js_strings.tpl b/view/tpl/js_strings.tpl
index 0a9cf9519..440676dee 100755
--- a/view/tpl/js_strings.tpl
+++ b/view/tpl/js_strings.tpl
@@ -35,7 +35,7 @@
'name_ok2' : "{{$name_ok2}}",
'to_reply' : "{{$to_reply}}",
- 'plural_func' : "{{$plural_func}}",
+ 'plural_func' : "{{$plural_func}}",
't01' : "{{$t01}}",
't02' : "{{$t02}}",
diff --git a/view/tpl/notifications_widget.tpl b/view/tpl/notifications_widget.tpl
index bc7f80906..057d5b491 100644
--- a/view/tpl/notifications_widget.tpl
+++ b/view/tpl/notifications_widget.tpl
@@ -85,7 +85,7 @@
$('#nav-{{$notification.type}}-menu [data-thread_top=false]').toggle();
$(this).toggleClass('active sticky-top');
});
- $(document).on('click ', '#cn-{{$notification.type}}-input-clear', function(e) {
+ $(document).on('click', '#cn-{{$notification.type}}-input-clear', function(e) {
$('#cn-{{$notification.type}}-input').val('');
$('#cn-{{$notification.type}}-only').removeClass('active sticky-top');
$("#nav-{{$notification.type}}-menu .notification").removeClass('d-none');
@@ -93,8 +93,8 @@
});
$(document).on('input', '#cn-{{$notification.type}}-input', function(e) {
var val = $('#cn-{{$notification.type}}-input').val().toString().toLowerCase();
-
if(val) {
+ val = val.indexOf('%') == 0 ? val.substring(1) : val;
$('#cn-{{$notification.type}}-only').addClass('active sticky-top');
$('#cn-{{$notification.type}}-input-clear').removeClass('d-none');
}
@@ -135,16 +135,17 @@
{{$no_notifications}}<span class="jumping-dots"><span class="dot-1">.</span><span class="dot-2">.</span><span class="dot-3">.</span></span>
</div>
<div id="nav-notifications-template" rel="template">
- <a class="list-group-item clearfix notification {6}" href="{0}" title="{3}" data-b64mid="{7}" data-notify_id="{8}" data-thread_top="{9}" data-contact_name="{2}" data-contact_addr="{3}">
- <img class="menu-img-3" data-src="{1}">
+ <a class="list-group-item clearfix notification {6}" href="{0}" title="{3}" data-b64mid="{7}" data-notify_id="{8}" data-thread_top="{9}" data-contact_name="{2}" data-contact_addr="{3}" data-when="{5}">
+ <img class="menu-img-3" src="{1}">
<span class="contactname">{2}</span>
- <span class="dropdown-sub-text">{4}<br>{5}</span>
+ <span class="dropdown-sub-text">{4}</span><br>
+ <span class="dropdown-sub-text notifications-autotime" title="{5}">{5}</span>
</a>
</div>
<div id="nav-notifications-forums-template" rel="template">
<a class="list-group-item clearfix notification notification-forum" href="{0}" title="{4} - {3}" data-b64mid="{7}" data-notify_id="{8}" data-thread_top="{9}" data-contact_name="{2}" data-contact_addr="{3}">
- <span class="float-right badge badge-{{$notification.severity}}">{10}</span>
- <img class="menu-img-1" data-src="{1}">
+ <span class="float-right badge badge-secondary">{10}</span>
+ <img class="menu-img-1" src="{1}">
<span class="">{2}</span>
<i class="fa fa-{11} text-muted"></i>
</a>
@@ -152,11 +153,14 @@
<div id="notifications" class="navbar-nav">
{{foreach $notifications as $notification}}
<div class="collapse {{$notification.type}}-button">
- <a class="list-group-item notification-link" href="#" title="{{$notification.title}}" data-target="#nav-{{$notification.type}}-sub" data-toggle="collapse" data-type="{{$notification.type}}">
+ <a id="notification-link-{{$notification.type}}" class="collapsed list-group-item notification-link" href="#" title="{{$notification.title}}" data-target="#nav-{{$notification.type}}-sub" data-toggle="collapse" data-sse_type="{{$notification.type}}">
<i class="fa fa-fw fa-{{$notification.icon}}"></i> {{$notification.label}}
<span class="float-right badge badge-{{$notification.severity}} {{$notification.type}}-update"></span>
+ <div id="notifications-spinner-{{$notification.type}}" class="float-right spinner-wrapper">
+ <div class="spinner s"></div>
+ </div>
</a>
- <div id="nav-{{$notification.type}}-sub" class="collapse notification-content" data-parent="#notifications" data-type="{{$notification.type}}">
+ <div id="nav-{{$notification.type}}-sub" class="collapse notification-content" data-parent="#notifications" data-sse_type="{{$notification.type}}">
{{if $notification.viewall}}
<a class="list-group-item text-dark" id="nav-{{$notification.type}}-see-all" href="{{$notification.viewall.url}}">
<i class="fa fa-fw fa-external-link"></i> {{$notification.viewall.label}}
@@ -176,14 +180,16 @@
{{if $notification.filter.name_label}}
<div class="list-group-item clearfix notifications-textinput" id="cn-{{$notification.type}}-only">
<div class="text-muted notifications-textinput-filter"><i class="fa fa-fw fa-filter"></i></div>
- <input id="cn-{{$notification.type}}-input" type="text" class="form-control form-control-sm" placeholder="{{$notification.filter.name_label}}">
+ <input id="cn-{{$notification.type}}-input" type="text" class="notification-filter form-control form-control-sm" placeholder="{{$notification.filter.name_label}}">
<div id="cn-{{$notification.type}}-input-clear" class="text-muted notifications-textinput-clear d-none"><i class="fa fa-times"></i></div>
</div>
{{/if}}
{{/if}}
- <div id="nav-{{$notification.type}}-menu" class="">
+ <div id="nav-{{$notification.type}}-menu"></div>
+ <div id="nav-{{$notification.type}}-loading" style="display: none;">
{{$loading}}<span class="jumping-dots"><span class="dot-1">.</span><span class="dot-2">.</span><span class="dot-3">.</span></span>
</div>
+
</div>
</div>
{{/foreach}}