Commit a491c25e authored by Malcolm Blaney's avatar Malcolm Blaney

Initial commit which adds channels to Reader module. Channels can

currently be set per feed, although most of the work is done to
also set channels based on the author as well. Control module
updates for channel UI as page-select is switched to display
channels on reader pages.
parent 001ce457
Pipeline #39733145 passed with stage
in 1 minute and 26 seconds
......@@ -31,6 +31,9 @@ class Reader extends Base {
if ($us_action === 'feed') {
return $this->GetFeed($id, $us_update);
}
if ($us_action === 'changeChannel') {
return $this->ChangeChannel($id);
}
if ($us_action === 'reset') {
$this->ResetSession();
if (isset($_SESSION['indieauth-feed']) &&
......@@ -82,6 +85,14 @@ class Reader extends Base {
return ['error' => 'Permission denied removing feed'];
}
}
if ($us_action === 'setChannel') {
if ($this->user->canViewPage) {
return $this->SetChannel($id);
}
else {
return ['error' => 'Permission denied setting channel'];
}
}
if ($us_action === 'import') {
if ($this->user->canViewPage) {
return $this->ImportFile();
......@@ -125,7 +136,9 @@ class Reader extends Base {
'</button><div class="reader-settings' . $show_settings . '">' .
$this->FeedSettings($id, $us_follow, $show_value) . '</div>';
}
$content .= '<div class="reader-discovered"></div>';
$content .= '<div class="reader-discovered"></div>' .
'<div class="reader-channel-settings">' . $this->ChannelSettings($id) .
'</div>';
}
$content .= '<div class="reader-show-hidden-wrapper">' .
'<button id="reader-show-hidden">Display new content</button></div>' .
......@@ -275,17 +288,20 @@ class Reader extends Base {
}
return;
}
if ($fn === 'Channels') {
return $this->Channels();
}
if ($fn === 'Following') {
return $this->Following($p);
}
if ($fn === 'UpdateFeed') {
return $this->UpdateFeed($p);
if ($fn === 'Microsub') {
return $this->Microsub();
}
if ($fn === 'RegisteredFeed') {
return $this->RegisteredFeed($p);
}
if ($fn === 'Microsub') {
return $this->Microsub();
if ($fn === 'UpdateFeed') {
return $this->UpdateFeed($p);
}
}
......@@ -384,6 +400,18 @@ class Reader extends Base {
$this->Log('Reader->Install 7: ' . $mysqli->error);
}
$query = 'CREATE TABLE IF NOT EXISTS reader_channels (' .
'user VARCHAR(50) NOT NULL,' .
'box_id INT UNSIGNED NOT NULL,' .
'xml_url VARCHAR(300),' .
'author VARCHAR(200),' .
'channel VARCHAR(100),' .
'PRIMARY KEY(user, box_id, xml_url(50), author(50))' .
') ENGINE=MyISAM';
if (!$mysqli->query($query)) {
$this->Log('Reader->Install 8: ' . $mysqli->error);
}
$format_with_title = '<h5 class="author">!date !author</h5>' .
'<h4 class="title">!title</h4>' .
'<div class="content">!content</div>' .
......@@ -412,9 +440,10 @@ class Reader extends Base {
$this->AddTemplateDescription($description);
$site_style = ['"",".reader .feed-item","padding","5px"',
'"",".reader .feed-item","margin","5px"',
'"",".reader .feed-text.added","background-color","#ff5500"',
'"",".reader .feed-url","font-size","0.8em"',
'"",".reader .feed-url","color","#aaaaaa"',
'"",".reader .feed-url-wrapper","font-size","0.8em"',
'"",".reader .feed-url-wrapper","color","#aaaaaa"',
'"",".reader .feed-url a","color","#aaaaaa"',
'"",".reader .content .h-card img","width","20px"',
'"",".reader .content .h-card img","border-radius","2px"',
......@@ -430,6 +459,17 @@ class Reader extends Base {
'"",".reader-settings","margin","5px"',
'"",".reader-feed-list","max-height","250px"',
'"",".reader-feed-list","overflow","auto"',
'"",".reader-feed-list","clear","both"',
'"",".reader-feed-list > div:nth-child(2n)",' .
'"background-color","#ffffff"',
'"","#reader-options-button","width","150px"',
'"","#reader-options-button","float","right"',
'"","#reader-options-button","margin-top","4px"',
'"",".reader-add-feed","margin-left","5px"',
'"","#reader-feed-input-wrapper","clear","none"',
'"","#reader-file-input-wrapper","clear","none"',
'"","#reader-channel-wrapper","clear","none"',
'"",".reader-channel-button","float","right"',
'"",".reader-hidden","display","none"',
'"","#reader-show-hidden","display","block"',
'"","#reader-show-hidden","margin-left","auto"',
......@@ -466,18 +506,26 @@ class Reader extends Base {
'"",".reader-actions","margin","10px"',
'"",".reader-actions","clear","both"',
'"",".reader-actions a","margin","5px"',
'"","label[for=reader-toggle-import]","float","right"',
'"","label[for=reader-toggle-import]","margin-top","8px"',
'"",".reader-discovered label","float","none"',
'"",".reader-discovered button","margin-top","20px"',
'"",".reader-discovered-add","float","right"',
'"",".reader-discovered-feed-url","color","#aaaaaa"',
'"",".reader-discovered-feed-url","font-size","0.8em"',
'"","#reader-channel-title","font-weight","bold"',
'"","#reader-channel-title","font-size","1.2em"',
'"",".reader-channel-settings label","width","7em"',
'"","#reader-channel-submit","margin-left","5px"',
'"",".reader-reposted-by","font-size","0.8em"',
'"",".reader-reposted-by","margin-left","5px"',
'"",".h-card .ui-icon.ui-icon-person","display",' .
'"inline-block"'];
$this->AddSiteStyle($site_style);
$this->AddSettingTypes(['"reader","defaultChannel","","text","Enter the ' .
'name of the channel you would like to see ' .
'when you first log in."',
'"reader","showChannels","yes,no","radio","If ' .
'you would prefer to not use channels you can ' .
'set that here."']);
}
public function Placement() {
......@@ -538,8 +586,14 @@ class Reader extends Base {
public function Update() {
$mysqli = connect_db();
$query = 'ALTER TABLE reader_items ADD COLUMN enclosure TEXT AFTER ' .
'category';
$query = 'CREATE TABLE IF NOT EXISTS reader_channels (' .
'user VARCHAR(50) NOT NULL,' .
'box_id INT UNSIGNED NOT NULL,' .
'xml_url VARCHAR(300),' .
'author VARCHAR(200),' .
'channel VARCHAR(100),' .
'PRIMARY KEY(user, box_id, xml_url(50), author(50))' .
') ENGINE=MyISAM';
if (!$mysqli->query($query)) {
$this->Log('Reader->Update: ' . $mysqli->error);
}
......@@ -762,6 +816,155 @@ class Reader extends Base {
return $result;
}
private function ChangeChannel($id) {
$found = false;
$us_channel = $_POST['channel'];
if ($us_channel === 'all') {
$_SESSION['reader-channel'] = $us_channel;
return ['channel' => true];
}
$mysqli = connect_db();
$owner = $this->SwitchOwner();
$channel = $mysqli->escape_string($us_channel);
$query = 'SELECT DISTINCT channel FROM reader_channels WHERE ' .
'user = "' . $owner . '" AND box_id = ' . $id . ' AND ' .
'channel = "' . $channel . '"';
if ($result = $mysqli->query($query)) {
if ($result->num_rows === 1) {
$_SESSION['reader-channel'] = $us_channel;
$found = true;
}
$result->close();
}
else {
$this->Log('Reader->ChangeChannel: ' . $mysqli->error);
}
$mysqli->close();
return ['channel' => $found];
}
private function Channels($id = 0) {
$all_feeds = false;
$channel_list = [];
$mysqli = connect_db();
// This function is called from the Control module, which doesn't have
// the Reader module box_id so look it up for the current page. Can add
// some extra options used by page-select input here too.
if ($id === 0) {
$query = 'SELECT box_id FROM modules WHERE label = "reader" AND ' .
'user = "' . $this->owner . '" AND ' .
'page = "' . $this->user->page . '" AND deleted = 0';
if ($result = $mysqli->query($query)) {
if ($modules = $result->fetch_assoc()) {
$id = (int)$modules['box_id'];
}
$result->close();
}
else {
$this->Log('Reader->Channels 1: ' . $mysqli->error);
}
$all_feeds = true;
}
$owner = $this->SwitchOwner();
$query = 'SELECT DISTINCT channel FROM reader_channels WHERE ' .
'user = "' . $owner . '" AND box_id = ' . $id . ' ORDER BY channel';
if ($result = $mysqli->query($query)) {
while ($reader_channels = $result->fetch_assoc()) {
$channel_list[] = $reader_channels['channel'];
}
$result->close();
}
else {
$this->Log('Reader->Channels 2: ' . $mysqli->error);
}
$mysqli->close();
// Display the currently selected channel in the session, otherwise look for
// a default setting if not set, otherwise use the first channel listed.
$current_channel = '';
$default_channel =
isset($this->user->settings['reader']['defaultChannel']) ?
$this->user->settings['reader']['defaultChannel'] : '';
if (isset($_SESSION['reader-channel'])) {
$current_channel = $_SESSION['reader-channel'];
}
else if ($default_channel !== '') {
$current_channel = $default_channel;
$_SESSION['reader-channel'] = $current_channel;
}
else if (isset($channel_list[0])) {
$current_channel = $channel_list[0];
$_SESSION['reader-channel'] = $current_channel;
}
$options = '';
foreach ($channel_list as $channel) {
$options .= $channel === $current_channel ?
'<option selected="selected">' . $channel . '</option>' :
'<option>' . $channel . '</option>';
}
if ($all_feeds && $options !== '') {
$selected = $current_channel === 'all' ? ' selected="selected"' : '';
$options .= '<option value="all"' . $selected . '>all feeds</option>' .
'<option value="">change page...</option>';
}
return $options;
}
private function ChannelQuery($id) {
$us_channel = isset($_SESSION['reader-channel']) ?
$_SESSION['reader-channel'] : '';
if ($us_channel === '' || $us_channel === 'all') return '';
$channel_query = '';
$mysqli = connect_db();
$owner = $this->SwitchOwner();
$query = 'SELECT xml_url, author FROM reader_channels WHERE ' .
'user = "' . $owner . '" AND box_id = ' . $id . ' AND ' .
'channel = "' . $mysqli->escape_string($us_channel) . '"';
if ($result = $mysqli->query($query)) {
while ($reader_channels = $result->fetch_assoc()) {
$xml_url = $mysqli->escape_string($reader_channels['xml_url']);
$author = $mysqli->escape_string($reader_channels['author']);
if ($channel_query !== '') $channel_query .= ' OR ';
// The feed can be added to a channel in which case author is not
// set, otherwise a specific author can be added to a channel.
if ($author === '') {
$channel_query .= 'reader_items.xml_url = "' . $xml_url . '"';
}
else {
$channel_query .= '(reader_items.xml_url = "' . $xml_url . '" AND ' .
'reader_items.author = "' . $author . '")';
}
}
$result->close();
}
else {
$this->Log('Reader->ChannelQuery: ' . $mysqli->error);
}
$mysqli->close();
return $channel_query;
}
private function ChannelSettings($id) {
$options = $this->Channels($id);
$hide_channel_options = $options === '' ? ' hidden' : '';
// Channel options will be displayed once a channel has been added.
return '<div id="reader-channel-info"></div>' .
'<div class="form-spacing' . $hide_channel_options . '">' .
'<label for="reader-channel-select">Set channel:</label>' .
'<select id="reader-channel-select">' . $options .
'</select>' .
'</div>' .
'<div class="form-spacing">' .
'<label for="reader-channel-add">Add channel:</label>' .
'<input id="reader-channel-add" type="text">' .
'<button id="reader-channel-submit">submit</button>' .
'</div>';
}
private function CheckFeed($feed, $us_xml_url, $daily = false) {
if ($feed->error()) {
$this->RemoveFeed($us_xml_url);
......@@ -992,41 +1195,79 @@ class Reader extends Base {
}
private function FeedSettings($id, $us_xml_url = '', $show_value = false) {
$show_channels = isset($this->user->settings['reader']['showChannels']) ?
$this->user->settings['reader']['showChannels'] === 'yes' : false;
$value = $show_value ? ' value="' . $us_xml_url . '"' : '';
$content = '<label for="reader-toggle-import">Import</label>' .
'<input id="reader-toggle-import" type="checkbox">' .
'<div class="form-spacing hidden">' .
'<label for="reader-file-import">Select a file:</label>' .
'<input id="reader-file-import" type="file">' .
'</div>' .
'<div class="form-spacing">' .
$content = '<select id="reader-options">' .
'<option value="add">All Feeds</option>';
if ($show_channels) {
$content .= '<option value="channels">Channel Feeds</option>';
}
$content .= '<option value="import">Import Feeds</option></select>' .
'<div id="reader-feed-input-wrapper" class="form-spacing">' .
'<label for="reader-feed-input">Add feed:</label>' .
'<input id="reader-feed-input" type="text" maxlength="300"' .$value.'>'.
'<button class="reader-add-feed">add</button>' .
'</div>' .
'<div class="reader-feed-list">';
'<div id="reader-file-import-wrapper" class="form-spacing hidden">' .
'<label for="reader-file-import">Select a file:</label>' .
'<input id="reader-file-import" type="file">' .
'</div>';
if ($show_channels) {
$options = $this->Channels($id);
if ($options === '') {
$content .= '<div id="reader-channel-wrapper" class="hidden">' .
'Add a channel by clicking a channel button next to a feed.</div>';
}
else {
$options = '<option selected="selected" value="">Displaying all feeds' .
'</option>' . $options;
$content .= '<div id="reader-channel-wrapper" ' .
'class="form-spacing hidden">' .
'<label for="reader-channel-show">Show channel:</label>' .
'<select id="reader-channel-show">' . $options .
'</select>' .
'</div>';
}
}
$content .= '<div class="reader-feed-list">';
$owner = $this->SwitchOwner();
$mysqli = connect_db();
$query = 'SELECT reader.xml_url, feed_title FROM reader LEFT JOIN ' .
'reader_feeds ON reader.xml_url = reader_feeds.xml_url WHERE ' .
'user = "' . $owner . '" AND box_id = ' . $id . ' ORDER BY feed_title';
$owner = $this->SwitchOwner();
$query = 'SELECT reader.xml_url, feed_title, channel FROM reader ' .
'LEFT JOIN reader_feeds ON reader.xml_url = reader_feeds.xml_url ' .
'LEFT JOIN reader_channels ON reader.user = reader_channels.user AND ' .
'reader.box_id = reader_channels.box_id AND ' .
'reader.xml_url = reader_channels.xml_url WHERE ' .
'reader.user = "' . $owner . '" AND reader.box_id = ' . $id .
' ORDER BY feed_title';
if ($result = $mysqli->query($query)) {
while ($reader = $result->fetch_assoc()) {
$url = $reader['xml_url'];
$short_url = strlen($url) <= 50 ? $url : substr($url, 0, 47) . '...';
$title = $reader['feed_title'];
$added = $us_xml_url === $url ? ' added' : '';
$link = '<a href="' . $url . '">' . urldecode($short_url) . '</a>';
$link = '<a class="feed-url" href="' . $url . '">' .
urldecode($short_url) . '</a>';
$channel_button = '';
if ($show_channels) {
$channel = htmlspecialchars($reader['channel']);
if ($channel === '') $channel = 'not set';
$channel_class = preg_replace('/[[:^alnum:]]/', '', $channel);
$channel_button = '<button class="reader-channel-button ' .
'reader-channel-' . $channel_class . '">' . $channel . '</button>';
}
$content .= '<div class="feed-item">' .
'<button class="remove-feed-item">remove</button> ' .
'<span class="feed-text' . $added . '">';
if ($title === '') {
$content .= $link . '</div>';
$content .= $link . '</span>' . $channel_button . '</div>';
}
else {
$content .= '<span class="feed-title">' . $title . '</span> ' .
'<span class="feed-url">[' . $link . ']</span></div>';
'<span class="feed-url-wrapper">[' . $link . ']</span></span>' .
$channel_button . '</div>';
}
}
$result->close();
......@@ -1070,9 +1311,9 @@ class Reader extends Base {
private function Following($target) {
$following = false;
$mysqli = connect_db();
$target = trim($target, ' /');
$owner = $this->SwitchOwner();
$mysqli = connect_db();
$query = 'SELECT user FROM reader LEFT JOIN reader_feeds ON ' .
'reader.xml_url = reader_feeds.xml_url WHERE user = "' . $owner . '" ' .
'AND (reader.xml_url = "' . $target . '" OR html_url = "' . $target .'")';
......@@ -1181,14 +1422,14 @@ class Reader extends Base {
$item_count = 10;
}
$mysqli = connect_db();
// Create an xml_url list for this id.
$xml_url_query = '';
foreach ($this->FeedList($id) as $us_xml_url) {
if ($xml_url_query !== '') {
$xml_url_query .= ' OR ';
// Create a query for this id, if channels are not used check feed list.
$xml_url_query = $this->ChannelQuery($id);
if ($xml_url_query === '') {
foreach ($this->FeedList($id) as $us_xml_url) {
if ($xml_url_query !== '') $xml_url_query .= ' OR ';
$xml_url_query .= 'reader_items.xml_url = ' .
'"' . $mysqli->escape_string($us_xml_url) . '"';
}
$xml_url_query .=
'reader_items.xml_url = "' . $mysqli->escape_string($us_xml_url) . '"';
}
$mysqli->close();
if ($xml_url_query === '') {
......@@ -1327,13 +1568,20 @@ class Reader extends Base {
}
}
if (isset($enclosure_list['audio'][0])) {
// Don't embed media if secure is true but content is over http.
$secure = $this->user->config->Secure();
foreach ($enclosure_list['audio'] as $audio) {
$media .= '<audio controls src="' . $audio . '"></audio>';
$media .= $secure && strpos($audio, 'http://') === 0 ?
'<a href="' . $audio . '">' . $audio . '</a>' :
'<audio controls src="' . $audio . '"></audio>';
}
}
if (isset($enclosure_list['video'][0])) {
$secure = $this->user->config->Secure();
foreach ($enclosure_list['video'] as $video) {
$media .= '<video controls src="' . $video . '"></video>';
$media .= $secure && strpos($video, 'http://') === 0 ?
'<a href="' . $video . '">' . $video . '</a>' :
'<video controls src="' . $video . '"></video>';
}
}
......@@ -1347,8 +1595,8 @@ class Reader extends Base {
if ($repost_author !== '') {
list($repost_name, $repost_url, $repost_photo) =
$this->LookupNickname($repost_author, $permalink);
$author .= '<span class="reader-reposted-by">reposted by ';
$author .= '<a href="' . $repost_url . '">' . $repost_name .'</a></span>';
$author .= '<span class="reader-reposted-by">reposted by ' .
'<a href="' . $repost_url . '">' . $repost_name . '</a></span>';
}
$category = '';
......@@ -1383,6 +1631,14 @@ class Reader extends Base {
}
$group_header = '';
$group_link = $item['html_url'];
// If the group link doesn't match the author url add it to the author, as
// it's possible the group header is not shown.
if (strpos($group_link, $author_url) !== 0 ||
strpos($author_url, $group_link) !== 0) {
$author .= ' <span class="reader-via-url">via ' .
'<a href="' . $group_link . '">' . $item['feed_title'] . '</a></span>';
}
// Allow feed items to be grouped by their url by setting a matching
// class name per item.
$class = 'no-url-match';
......@@ -1818,8 +2074,8 @@ class Reader extends Base {
}
private function RemoveFeedForUser($id) {
$owner = $this->SwitchOwner();
$mysqli = connect_db();
$owner = $this->SwitchOwner();
// Remove a final slash that may have been added by javascript.
$xml_url = $mysqli->escape_string(trim($_POST['xmlUrl'], ' /'));
$query = 'DELETE FROM reader WHERE user = "' . $owner . '" AND ' .
......@@ -2225,6 +2481,27 @@ class Reader extends Base {
return $updated;
}
private function SetChannel($id, $author = '') {
$us_channel = htmlspecialchars($_POST['channel']);
if ($us_channel === 'all') {
return ['error' => '\'all\' is a reserved channel.'];
}
$mysqli = connect_db();
$channel = $us_channel === 'not set' ? '' :
$mysqli->escape_string($us_channel);
$xml_url = $mysqli->escape_string($_POST['feed']);
$owner = $this->SwitchOwner();
$query = 'INSERT INTO reader_channels VALUES ("' . $owner . '", ' .
$id . ', "' . $xml_url . '", "' . $author . '", "' . $channel . '") ' .
'ON DUPLICATE KEY UPDATE channel = "' . $channel . '"';
if (!$mysqli->query($query)) {
$this->Log('Reader->SetChannel: ' . $mysqli->error);
}
$mysqli->close();
return ['name' => $us_channel];
}
private function SwitchOwner() {
// This is a special case for when a user has logged in using indieauth.
// They are given access to the reader module on a specific admin page
......
......@@ -186,7 +186,9 @@ class Settings extends Base {
$mysqli->close();
$this->AppendScript($path, 'dobrado.settings.js', false);
$this->AddTemplate(['"settings-display","","control"']);
// Add all modules that call AddSettingTypes here as a default.
$this->AddTemplate(['"settings-display","","account,control,purchase,' .
'reader"']);
$site_style = ['"",".settings-section","background-color","#eeeeee"',
'"",".settings-section","border","1px solid #aaaaaa"',
'"",".settings-section","margin","5px"',
......
......@@ -241,7 +241,9 @@ class Start extends Base {
}
if ($profile === 'unicyclic') {
return ['control' => ['displayMessageButton' => 'hidden',
'displayToolsButton' => 'display']];
'displayToolsButton' => 'display'],
'reader' => ['defaultChannel' => 'all',
'showChannels' => 'yes']];
}
return [];
}
......
......@@ -1609,7 +1609,7 @@ class Stock extends Base {
}
$mysqli->close();
if (count($us_sort_options) > 0) {
if (is_array($us_sort_options) && count($us_sort_options) > 0) {
$args = [];
foreach ($us_sort_options as $sort) {
$id = $sort['columnId'];
......
......@@ -283,6 +283,7 @@ class Writer extends Base {
'"",".writer .designate","padding","5px"',
'"",".writer .designate","margin-bottom","10px"',
'"",".writer .designate label","width","15em"',
'"",".writer .submit-designate","margin-left","5px"',
'"","#writer-feed-title-input","width","324px"',
'"","#writer-designate-input","width","250px"',
'"","#writer-webaction-info","padding-bottom","2px"',
......
......@@ -26,12 +26,32 @@
if(!this.dobrado.reader){dobrado.reader={};}
(function(){'use strict';var importList=[];var importCount=0;$(function(){if($('.reader').length===0){return;}
var tenMinutes=600000;var elapsed=tenMinutes+1;var pageChanged=true;$('.reader-edit-settings').button({icon:'ui-icon-signal-diag',showLabel:false}).click(function(){$('.reader-settings').toggle();});$('.reader-more').button().click(function(){$('.reader-more').button('option','label','Loading...');request('older','feed');});$('.reader-reset').button().click(reset);$('#reader-show-hidden').button().click(function(){$('.reader-hidden').show();$('.reader-show-hidden-wrapper').hide();});$('#reader-feed-input').val('');$('.reader-discovered').dialog({show:true,autoOpen:false,width:600,height:300,position:{my:'top',at:'top+50',of:window},title:'Discovered Feeds',close:function(){importList=[];importCount=0;},create:dobrado.fixedDialog});feedSettings();if(dobrado.localStorage&&localStorage.reader){$('.reader-content').html(localStorage.reader);$('.reader-writer').show();$('.reader-more').show();$('.reader-reset').show();if(localStorage.readerUpdate){elapsed=new Date().getTime()-localStorage.readerUpdate;}
var tenMinutes=600000;var elapsed=tenMinutes+1;var pageChanged=true;$('.reader-edit-settings').button({icon:'ui-icon-signal-diag',showLabel:false}).click(function(){$('.reader-settings').toggle();});$('.reader-more').button().click(function(){$('.reader-more').button('option','label','Loading...');request('older','feed');});$('.reader-reset').button().click(reset);$('#reader-show-hidden').button().click(function(){$('.reader-hidden').show();$('.reader-show-hidden-wrapper').hide();});$('#reader-feed-input').val('');$('.reader-discovered').dialog({show:true,autoOpen:false,width:600,height:300,position:{my:'top',at:'top+50',of:window},title:'Discovered Feeds',close:function(){importList=[];importCount=0;},create:dobrado.fixedDialog});$('.reader-channel-button').button().click(channelSettings);$('.reader-channel-settings').dialog({show:true,autoOpen:false,width:600,height:300,position:{my:'top',at:'top+50',of:window},title:'Channel List',create:dobrado.fixedDialog});feedSettings();if(dobrado.localStorage&&localStorage.reader){$('.reader-content').html(localStorage.reader);$('.reader-writer').show();$('.reader-more').show();$('.reader-reset').show();if(localStorage.readerUpdate){elapsed=new Date().getTime()-localStorage.readerUpdate;}
if(localStorage.readerPage){pageChanged=location.href!==localStorage.readerPage;}}
if(pageChanged||($(window).scrollTop()<200&&elapsed>tenMinutes)){reset(true);}
else{if($('.reader-settings .feed-item').length<=5){$('.writer .designate').show();$('.reader-settings').show();}
newFeedEvents();dobrado.notify('feed',dobrado.reader.update);}});function feedSettings(){$('#reader-feed-input').keypress(function(event){if(event.keyCode!==13){return;}
event.preventDefault();dobrado.reader.addFeed();});$('.reader-add-feed').button().click(dobrado.reader.addFeed);$('.remove-feed-item').button({icon:'ui-icon-closethick',showLabel:false}).click(removeFeed);$('#reader-toggle-import').checkboxradio({icon:false}).click(function(){$('#reader-file-import').parent().toggle();});$('#reader-file-import').change(importFile);if($('.feed-text.added').length!==0){$('.feed-text.added').get(0).scrollIntoView();setTimeout(function(){$('.feed-text.added').removeClass('added');},5000);}}
else{if($('.reader-settings .feed-item').length<=5){$('.reader-settings').show();}
newFeedEvents();dobrado.notify('feed',dobrado.reader.update);}});function channelSettings(){function addChannel(){var option=$('#reader-channel-add').val();if(option===''){return;}
$('#reader-channel-add').val('');setChannel(option,true);}
function setChannel(option,add){var feed=$('#reader-channel-url').html();$.post('/php/request.php',{id:'#'+$('.reader').attr('id'),request:'reader',action:'setChannel',channel:option,feed:feed,url:location.href,token:dobrado.token},function(response){if(dobrado.checkResponseError(response,'reader setChannel')){return;}
var channel=JSON.parse(response);var text=channel.name==='not set'?'The channel for this feed is no longer set.':'The channel for this feed has been updated to <b>'+
channel.name+'</b>';$('#reader-channel-status').html(text);$('.reader-feed-list .feed-url').each(function(){if($(this).html()===feed){let channelButton=$(this).parents('.feed-item').find('.reader-channel-button');channelButton.button('option','label',channel.name);}});if(add){$('#reader-channel-select').append('<option selected="selected">'+
channel.name+'</option>');$('#reader-channel-select').selectmenu('refresh');$('#reader-channel-select').parent().show();}});}
var title=$(this).parent().find('.feed-title');var link=$(this).parent().find('.feed-url');var channel=$(this).html();var text='';if(title&&link){text='<span id="reader-channel-title">'+title.html()+'</span> [<a href="'+link.html()+'" '+'id="reader-channel-url">'+link.html()+'</a>]';}
else if(link){text+='<a href="'+link.html()+'" id="reader-channel-url">'+
link.html()+'</a>';}
$('#reader-channel-select').selectmenu({change:function(event,ui){setChannel(ui.item.value,false);}});$('#reader-channel-submit').button().click(addChannel);if(channel==='not set'){text+='<p id="reader-channel-status">'+'This feed does not have a channel set. ';if($('#reader-channel-select').children().length===0){text+='You can add one below to set the channel for this feed.</p>';}
else{let firstOption=$('#reader-channel-select').children().first().html();if(firstOption!=='not set'){$('#reader-channel-select').prepend('<option selected="selected">'+'not set</option>');}
text+='You can select an existing channel from the list or add a '+'new channel below.</p>';}}
else{text+='<p id="reader-channel-status">'+'The channel for this feed is <b>'+channel+'</b></p>';}
$('#reader-channel-select').val(channel);$('#reader-channel-select').selectmenu('refresh');$('#reader-channel-info').html(text);$('.reader-channel-settings').dialog('open');}
function feedSettings(){function readerOption(event,ui){if(ui.item.value==='add'){$('#reader-channel-show').val('').selectmenu('refresh');$('.reader-feed-list .feed-item').show();$('#reader-feed-input-wrapper').show();$('#reader-channel-wrapper').hide();$('#reader-file-import-wrapper').hide();$('.writer .designate').hide();}
else if(ui.item.value==='channels'){$('#reader-channel-wrapper').show();$('#reader-feed-input-wrapper').hide();$('#reader-file-import-wrapper').hide();$('.writer .designate').hide();}
else if(ui.item.value==='import'){$('#reader-file-import-wrapper').show();$('#reader-feed-input-wrapper').hide();$('#reader-channel-wrapper').hide();$('.writer .designate').hide();}
else if(ui.item.value==='writer'){$('.writer .designate').show();$('#reader-feed-input-wrapper').hide();$('#reader-channel-wrapper').hide();$('#reader-file-import-wrapper').hide();}}
function channelFeeds(event,ui){$('.reader-feed-list .feed-item').show();if(ui.item.value!==''){$('.reader-feed-list .reader-channel-button').each(function(){if($(this).html()!==ui.item.value){$(this).parents('.feed-item').hide();}});}}
$('#reader-feed-input').keypress(function(event){if(event.keyCode!==13){return;}
event.preventDefault();dobrado.reader.addFeed();});$('.reader-add-feed').button().click(dobrado.reader.addFeed);$('.remove-feed-item').button({icon:'ui-icon-closethick',showLabel:false}).click(removeFeed);if($('.writer .designate').length!==0){$('#reader-options').append('<option value="writer">Writer Settings</option>');}
$('#reader-options').selectmenu({change:readerOption});$('#reader-file-import').change(importFile);$('#reader-channel-show').selectmenu({change:channelFeeds});if($('.feed-text.added').length!==0){$('.feed-text.added').get(0).scrollIntoView();setTimeout(function(){$('.feed-text.added').removeClass('added');},5000);}}
function importFile(){function addImportedFeed(){var xmlUrl=importList[importCount].xmlUrl;var last=importCount===importList.length-1;var text='<br><span class="reader-imported-feed-'+importCount+'">Adding: <b>'+importList[importCount].title+'</b> <span class="reader-discovered-feed-url">[<a href="'+
importList[importCount].htmlUrl+'">'+
importList[importCount].htmlUrl+'</a>]</span> ... </span>';$('.reader-discovered').append(text);$.post('/php/request.php',{id:'#'+$('.reader').attr('id'),request:'reader',action:'addImport',last:last,force:true,xmlUrl:xmlUrl,url:location.href,token:dobrado.token},function(response){if(!response){dobrado.log('No response to reader addImport call.','error');return;}
......@@ -63,7 +83,7 @@ else if($(this).hasClass('reply')){if(dobrado.editor){dobrado.editor.setData('')
else{$('#writer-content').val('');}
dobrado.writer.action('reply',permalink,sendTo);}
window.scroll(0,0);return false;}
function removeFeed(){dobrado.log('Removing feed...','info');$.post('/php/request.php',{id:'#'+$('.reader').attr('id'),request:'reader',action:'remove',xmlUrl:$(this).siblings().find('a').prop('href'),url:location.href,token:dobrado.token},function(response){if(dobrado.checkResponseError(response,'reader remove')){return;}
function removeFeed(){dobrado.log('Removing feed...','info');$.post('/php/request.php',{id:'#'+$('.reader').attr('id'),request:'reader',action:'remove',xmlUrl:$(this).siblings().find('a.feed-url').prop('href'),url:location.href,token:dobrado.token},function(response){if(dobrado.checkResponseError(response,'reader remove')){return;}
var reader=JSON.parse(response);if(reader.content){$('.reader-content').html(reader.content);}
else{$('.reader-content').html('');}
if(reader.more){$('.reader-more').show();}
......@@ -84,7 +104,7 @@ var reader=JSON.parse(response);var update=0;if(reader.content){$('.reader-write
if(reader.more){$('.reader-more').show();}
else{$('.reader-more').hide();}
if(reader.settings){$('.reader-settings').html(reader.settings);feedSettings();}
if($('.reader-settings .feed-item').length<=5){$('.writer .designate').show();$('.reader-settings').show();}
if($('.reader-settings .feed-item').length<=5){$('.reader-settings').show();}
newFeedEvents();if(dobrado.localStorage){localStorage.reader=reader.content;localStorage.readerUpdate=update;localStorage.readerPage=location.href;}
if(notify){dobrado.notify('feed',dobrado.reader.update);}});}
dobrado.reader.addFeed=function(xmlUrl,force,multiple){function addDiscoveredFeed(){var discovered=[];$('.reader-discovered input:checked').each(function(index){discovered.push($(this).val());});if(discovered.length===1){dobrado.reader.addFeed(discovered[0],true);}
......@@ -96,4 +116,6 @@ if($('.reader-discovered').dialog('isOpen')){$('.reader-discovered').dialog('clo
if(reader.content){$('.reader-content').html(reader.content);}
if(reader.more){$('.reader-more').show();}
else{$('.reader-more').hide();}
$('.reader-settings').html(reader.settings);feedSettings();newFeedEvents();if(dobrado.localStorage){localStorage.reader=reader.content;localStorage.readerUpdate=new Date().getTime();localStorage.readerPage=location.href;}});};dobrado.reader.update=function(action){request('newer',action);};dobrado.reader.scrollback=function(url,cancelled){if($('.reader-item a[href="'+url+'"]').length!==0){$('.reader-item a[href="'+url+'"]').get(0).scrollIntoView();if(!cancelled){var item=$('.reader-item a[href="'+url+'"]').parents('.reader-item');item.find('indie-action').data('checked',false);dobrado.indieConfig();}}};}());
\ No newline at end of file
$('.reader-settings').html(reader.settings);feedSettings();newFeedEvents();if(dobrado.localStorage){localStorage.reader=reader.content;localStorage.readerUpdate=new Date().getTime();localStorage.readerPage=location.href;}});};dobrado.reader.channelSelect=function(channel){$.post('php/request.php',{id:'#'+$('.reader').attr('id'),request:'reader',action:'changeChannel',channel:channel,url:location.href,token:dobrado.token},function(response){if(dobrado.checkResponseError(response,'reader change channel')){return;}
var change=JSON.parse(response);if(change.channel){reset(false);}
else{dobrado.changePage(channel);}});};dobrado.reader.update=function(action){request('newer',action);};dobrado.reader.scrollback=function(url,cancelled){if($('.reader-item a[href="'+url+'"]').length!==0){$('.reader-item a[href="'+url+'"]').get(0).scrollIntoView();if(!cancelled){let item=$('.reader-item a[href="'+url+'"]').parents('.reader-item');item.find('indie-action').data('checked',false);dobrado.indieConfig();}}};}());
\ No newline at end of file
......@@ -69,6 +69,15 @@ if (!this.dobrado.reader) {
title: 'Discovered Feeds',
close: function() { importList = []; importCount = 0; },
create: dobrado.fixedDialog });
$('.reader-channel-button').button().click(channelSettings);
$('.reader-channel-settings').dialog({
show: true,
autoOpen: false,
width: 600,
height: 300,
position: { my: 'top', at: 'top+50', of: window },
title: 'Channel List',
create: dobrado.fixedDialog });
feedSettings();
// Retrieve existing feed content from localStorage if available.
......@@ -93,10 +102,8 @@ if (!this.dobrado.reader) {
}
else {
// If the user hasn't subscribed to any feeds yet, automatically open
// the reader settings. The writer module also toggles designate, so
// show that too so they don't get out of sync.
// the reader settings.
if ($('.reader-settings .feed-item').length <= 5) {
$('.writer .designate').show();
$('.reader-settings').show();
}
newFeedEvents();
......@@ -104,7 +111,140 @@ if (!this.dobrado.reader) {
}
});
function channelSettings() {
function addChannel() {
var option = $('#reader-channel-add').val();
if (option === '') {
return;
}
$('#reader-channel-add').val('');
setChannel(option, true);
}
function setChannel(option, add) {
var feed = $('#reader-channel-url').html();
$.post('/php/request.php',
{ id: '#' + $('.reader').attr('id'), request : 'reader',
action: 'setChannel', channel: option, feed: feed,
url: location.href, token: dobrado.token },
function(response) {
if (dobrado.checkResponseError(response, 'reader setChannel')) {
return;