add new feature that let user can add hot and top or hidden status themselves

This commit is contained in:
chiu 2020-04-05 22:36:09 +08:00
parent f91f598df7
commit 3b214e3519
15 changed files with 22365 additions and 54 deletions

File diff suppressed because it is too large Load Diff

4521
app/assets/javascripts/feeds/bootstrap.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -22,17 +22,23 @@
dataType : "json",
type : "get"
}).done(function(channels){
$.each(channels.channels,function(index,channel){
var ch = $("<div id='" + channel.key + "-channel' class='module cursor-pointer'><div class='lead muted'><i class='" + channel.app_icon + "'></i><br/><span style='font-size:14px;'>" + channel.title + "</span></div></div>");
ch.on("click",function(){
getFeedList(channel);
})
$("#channels").append(ch);
})
setTimeout(function(){
displayLoading(false);
setTimeout(function(){$("#channels").fadeIn();},500);
},1000);
if (channels != null){
$.each(channels.channels,function(index,channel){
var ch = $("<div id='" + channel.key + "-channel' class='module cursor-pointer'><div class='lead muted'><i class='" + channel.app_icon + "'></i><br/><span style='font-size:14px;'>" + channel.title + "</span></div></div>");
ch.on("click",function(){
getFeedList(channel);
})
$("#channels").append(ch);
})
setTimeout(function(){
displayLoading(false);
setTimeout(function(){$("#channels").fadeIn();},500);
},1000);
}
else{
alert('Feed not found')
window.location.href = window.location.href
}
})
}

10224
app/assets/stylesheets/feeds/bootstrap.css vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,12 @@
class Admin::FeedsController < OrbitAdminController
layout :compute_layout
def compute_layout
if action_name== 'annc_content'
false
else
'back_end'
end
end
def index
@site_feeds = SiteFeed.all.group_by(&:remote_site_url)
@school_urls = @site_feeds.keys
@ -9,7 +16,53 @@ class Admin::FeedsController < OrbitAdminController
def new
end
def annc_content
site_feed_annc = SiteFeedAnnc.where(id: params['feed_annc_id']).first
@annc = site_feed_annc.get_annc(params['annc_uid'].to_s) rescue {}
end
def process_annc
now_process = params['process']
annc_uid = params['annc_uid'].to_s
site_feed_annc = SiteFeedAnnc.where(id: params['feed_annc_id']).first
if !site_feed_annc.nil?
case now_process
when /is_top|is_hot/
cmd = now_process.split(':')
f = cmd[0]=='is_top' ? 'top_list' : 'hot_list'
if cmd[1] == 'enable'
if site_feed_annc[f].exclude?(annc_uid)
tmp = site_feed_annc.send(f)
tmp << annc_uid
site_feed_annc.update_attributes(f.to_sym => tmp)
end
else
if site_feed_annc[f].include?(annc_uid)
tmp = site_feed_annc.send(f)
tmp -= [annc_uid]
site_feed_annc.update_attributes(f.to_sym => tmp)
end
end
when /hidden|display/
if now_process == 'hidden'
if site_feed_annc[:hidden_annc].exclude?(annc_uid)
tmp = site_feed_annc.hidden_annc
tmp << annc_uid
site_feed_annc.update_attributes(:hidden_annc =>tmp)
end
else
if site_feed_annc[:hidden_annc].include?(annc_uid)
tmp = site_feed_annc.hidden_annc
tmp -= [annc_uid]
site_feed_annc.update_attributes(:hidden_annc =>tmp)
end
end
end
end
render :text => 'success'
end
def announcements
@all_feed_annc = SiteFeedAnnc.all.order(created_at: 1).to_a
end
def get_category_list
app_key = params[:channel]
ma = ModuleApp.find_by_key(app_key) rescue nil
@ -28,34 +81,34 @@ class Admin::FeedsController < OrbitAdminController
def get_channel_list
url = params['url'].chomp("/") + "/feeds/channel_lists"
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
data = response.body
data = JSON.parse(data)
render :json => data.to_json
res = Net::HTTP.start(uri.host, uri.port,:use_ssl => uri.scheme == 'https',open_timeout: 60,read_timeout: 60) do |http|
req = Net::HTTP::Get.new(uri)
http.request(req).body rescue nil
end
data = JSON.parse(res) rescue {}
render :json => data.to_json
end
def get_feed_list
url = params['url'].chomp("/") + params[:feed_list_url]
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request)
data = response.body
data = JSON.parse(data)
data_to_send = {}
data_to_send["feeds"] = []
data["feeds"].each do |feed|
sf = SiteFeed.find_by(:feed_uid => feed["uid"]) rescue nil
if !sf.nil?
feed["subscribed"] = true
else
feed["subscribed"] = false
end
data_to_send["feeds"] << feed
end
render :json => data_to_send.to_json
res = Net::HTTP.start(uri.host, uri.port,:use_ssl => uri.scheme == 'https',open_timeout: 60,read_timeout: 60) do |http|
req = Net::HTTP::Get.new(uri)
http.request(req).body rescue nil
end
data = JSON.parse(res) rescue {}
data_to_send = {}
data_to_send["feeds"] = []
Array(data["feeds"]).each do |feed|
sf = SiteFeed.find_by(:feed_uid => feed["uid"]) rescue nil
if !sf.nil?
feed["subscribed"] = true
else
feed["subscribed"] = false
end
data_to_send["feeds"] << feed
end
render :json => data_to_send.to_json
end
def channel_title

View File

@ -1,7 +1,6 @@
class SiteFeed
include Mongoid::Document
include Mongoid::Timestamps
field :remote_site_url
field :merge_with_category
field :channel_name
@ -11,12 +10,34 @@ class SiteFeed
field :disabled, type: Boolean, default: false
field :feed_url
field :feed_uid
require 'feed_model/cache'
include FeedModel::Cache
Category.send(:include,FeedModel::Cache)
scope :enabled, ->{where(:disabled => false)}
def get_annc
main_directory = File.join("#{Rails.root}","public","site_feeds")
feed_directory = File.join(main_directory.to_s, self.id.to_s)
if File.exists?(feed_directory)
anns = JSON.parse(File.read(File.join(feed_directory.to_s, self.feed_uid + ".json")))['announcements'] rescue []
else
uri = URI(self.feed_url)
res = Net::HTTP.start(uri.host, uri.port,:use_ssl => uri.scheme == 'https',open_timeout: 20,read_timeout: 20) do |http|
req = Net::HTTP::Get.new(uri)
http.request(req).body rescue ''
end
FileUtils.mkdir_p(feed_directory) if !File.exists?(feed_directory)
File.open(File.join(feed_directory.to_s,self.feed_uid + ".json"),"w") do |file|
res.force_encoding("utf-8")
file.write(res)
end
anns = JSON.parse(res)['announcements'] rescue []
end
anns
end
def category
Category.find(self.merge_with_category) rescue nil
end
def channel_title_for_cache
!self[:channel_title].to_s.empty? ? self[:channel_title] : I18n.t("feed.source")
end
end

View File

@ -0,0 +1,81 @@
class SiteFeedAnnc
include Mongoid::Document
include Mongoid::Timestamps
field :top_list,type: Array,default: []
field :hot_list,type: Array,default: []
field :all_contents_for_feed
field :channel_key
field :feed_id
field :feed_name
field :category_title
field :hidden_annc,type: Array,default: []
field :merge_with_category
field :remote_site_url
field :channel_title
def get_annc(annc_uid)
Array(self[:all_contents_for_feed]).select{|v| v['id']==annc_uid}[0] rescue {}
end
def all_contents_for_feed(site_source=nil,locale=I18n.locale.to_s)
cat = self.category_title
Array(self[:all_contents_for_feed]).collect do |v|
tmp = v
if hidden_annc.exclude?(v['id'])
tmp['statuses'] = []
if self[:top_list].count == 0 || self[:top_list].exclude?(tmp['id'])
tmp[:is_top] = false
else
tmp[:is_top] = true
tmp['statuses'] << {
"status" => I18n.t(:top),
"status-class" => "status-top"
}
end
if self[:hot_list].count == 0 || self[:top_list].exclude?(tmp['id'])
tmp[:is_hot] = false
else
tmp[:is_hot] = true
tmp['statuses'] << {
"status" => I18n.t(:hot),
"status-class" => "status-hot"
}
end
tmp["category"] = cat
tmp["source-site"] = self.remote_site_url
tmp["source-site-title"] = self[:channel_title][locale]
tmp["params"] = tmp["params"].to_s + "_" + self.feed_id.to_s + "h"
next if !site_source.nil? && site_source != fa["source-site-title"]
tmp['statuses'] << {
"status" => "<a href='#{tmp["source-site"]}' target='_blank' class='feed-source'>#{tmp["source-site-title"]}</a>",
"status-class" => "status-source"
}
files = tmp["bulletin_files"].collect{|bf| { "file_url" => bf["url"], "file_title" => (fa["title_translations"][locale].blank? ? File.basename(fa["url"]) : fa["title_translations"][locale] rescue '') }} rescue []
links = tmp["bulletin_links"].map{|link| { "link_url" => link["url"], "link_title" => (link["title_translations"][locale].blank? ? link["url"] : link["title_translations"][locale]) } } rescue []
tmp["bulletin_links"] = links
tmp["bulletin_files"] = files
tmp["title"] = tmp["title_translations"][locale]
tmp["subtitle"] = tmp["subtitle_translations"][locale]
tmp["source-site-link"] = tmp["source-site"]
tmp["source-site"] = "<a href='#{tmp["source-site"]}' target='_blank' class='feed-source'>#{tmp["source-site-title"]}</a>"
tmp["link_to_show"] = OrbitHelper.url_to_show(tmp["params"]) rescue ''
tmp["target"] = "_self"
tmp["img_src"] = tmp["image"]["thumb"] || "/assets/announcement-default.jpg"
tmp["img_description"] = tmp["image_description_translations"][locale]
tmp["more"] = I18n.t(:more_plus)
tmp["view_count"] = ""
else
tmp = nil
end
tmp
end.compact
end
def channel_title(locale=I18n.locale)
self[:channel_title][locale]
end
def category
Category.find(self.merge_with_category) rescue nil
end
def category_title
self[:category_title][I18n.locale]
end
end

View File

@ -0,0 +1,20 @@
<style type="text/css">
.subtitle,.content{
display: flex;
}
.subtitle > .block,.content > .block{
display: inline-flex;
flex-wrap: wrap;
width: 50%;
}
</style>
<% available_locales = Site.first.in_use_locales rescue I18n.available_locales %>
<div class="subtitle">
<%= "#{t('subtitle')}(#{available_locales.collect{|v| t(v)}.join('/')})" %>:
<%= available_locales.collect{|v| "<div class=\"block\">#{@annc['subtitle_translations'][v]}</div>"}.join(' / ').html_safe %>
</div>
<br>
<div class="content">
<%= "#{t('content')}(#{available_locales.collect{|v| t(v)}.join('/')})" %>:
<%= available_locales.collect{|v| "<div class=\"block\">#{@annc['text_translations'][v]}</div>"}.join(' / ').html_safe %>
</div>

View File

@ -0,0 +1,168 @@
<% available_locales = Site.first.in_use_locales rescue I18n.available_locales %>
<style type="text/css">
.margin_right_1em{
margin-right: 1em;
}
li{
list-style: none;
}
a{
cursor: pointer;
}
</style>
<link href="https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css" rel="stylesheet">
<script src="https://gitcdn.github.io/bootstrap-toggle/2.2.2/js/bootstrap-toggle.min.js"></script>
<div>
<h3><% t('feed.all_feeds_announcments') %></h3>
</div>
<div class='content_show_div'>
</div>
<div class="accordion channel-accordion" id="feedAccordion">
<table class="table main-list footable-loaded tablet">
<thead>
<tr class="table-info">
<th scope="row"><%= t('feed.feed_name') %></th>
<th><%= "#{t('title')}(#{available_locales.collect{|v| t(v)}.join('/')})" %></th>
<th><%= "#{t('tags')}(#{available_locales.collect{|v| t(v)}.join('/')})" %></th>
<th></th>
</tr>
</thead>
<tbody>
<% @all_feed_annc.each do |feed_annc| %>
<% Array(feed_annc[:all_contents_for_feed]).each do |annc| %>
<tr data-annc-feed-id='<%= feed_annc.id.to_s %>' data-annc-uid='<%= annc['id'].to_s %>'>
<td>
<% if !feed_annc.channel_title.to_s.empty? %>
<span class="channel-source-name label"><%= feed_annc.channel_title %></span>
<br>
<% end %>
<%= available_locales.collect{|v| feed_annc[:feed_name][v]}.join(' / ') %>
</td>
<td>
<ul>
<li>
<a class='annc_title'><%= available_locales.collect{|v| annc['title_translations'][v]}.join('/').html_safe %></a>
</li>
<li style="display: flex;width: 100%;flex-wrap: wrap;">
<%= check_box_tag nil,nil,feed_annc[:top_list].include?(annc['id']),'class' => 'is_top' %>
<%= check_box_tag nil,nil,feed_annc[:hot_list].include?(annc['id']),'class' => 'is_hot' %>
</li>
</ul>
</td>
<td>
<%= annc['tags'].collect{|tmp| available_locales.collect{|v| tmp['name_translations'][v]}.join(' / ')}.join('<br>').html_safe %>
</td>
<td>
<% display_flag = feed_annc[:hidden_annc].count==0 || feed_annc[:hidden_annc].exclude?(annc['id']) ? true : false %>
<% if display_flag %>
<button class='btn-info hidden_btn'><%= t('is_hidden') %></button>
<% else %>
<button class='btn-primary display_btn'><%= t('feed.display') %></button>
<% end %>
</td>
</tr>
<% end %>
<% end %>
</tbody>
</table>
</div>
<script type="text/javascript">
$(function(){
<% if I18n.locale.to_s == 'zh_tw' %>
var off_top_text = '<%= "#{t('disable')}#{t('is_top')}" %>'
var off_hot_text = '<%= "#{t('disable')}#{t('is_hot')}" %>'
<% else %>
var off_top_text = '<%= "#{t('disable')} #{t('is_top')}" %>'
var off_hot_text = '<%= "#{t('disable')} #{t('is_hot')}" %>'
<% end %>
$('.is_top').bootstrapToggle({
on: '<%= t('is_top') %>',
off: off_top_text,
size: 'mini',
onstyle: 'danger',
width: '4em',
height: '2em',
style: 'margin_right_1em'
});
$('.is_hot').bootstrapToggle({
on: '<%= t('is_hot') %>',
off: off_hot_text,
size: 'mini',
width: '4em',
height: '2em',
onstyle: 'warning'
});
})
function process_annc(ele){
var parent_tr = $(ele).parents('tr').eq(-1)
var process_
if ($(ele).attr('class').match(/is_top|is_hot/) != null){
process_ = $(ele).hasClass('is_top') ? 'is_top' : 'is_hot'
if ($(ele).prop('checked')){
process_ += ':enable'
}
else{
process_ += ':disable'
}
}else{
process_ = $(ele).hasClass('hidden_btn') ? 'hidden' : 'display'
}
$.ajax({
url : "/<%= I18n.locale.to_s %>/admin/feeds/process_annc",
data : {"feed_annc_id": parent_tr.data("annc-feed-id"),
"annc_uid": parent_tr.data("annc-uid"),
'process': process_},
dataType : "text",
type : "post",
error : function(data){
alert('something went wrong')
},
success: function(){
if (process_=='hidden'){
$(ele).attr('class','btn-primary display_btn')
$(ele).text('<%= t('feed.display') %>')
}
else if (process_=='display'){
$(ele).attr('class','btn-info hidden_btn')
$(ele).text('<%= t('is_hidden') %>')
}
}
})
}
$('.annc_title').click(function(){
var parent_tr = $(this).parents('tr').eq(-1)
$.ajax({
url : "/<%= I18n.locale.to_s %>/admin/feeds/annc_content",
data : {"feed_annc_id": parent_tr.data("annc-feed-id"),
"annc_uid": parent_tr.data("annc-uid")},
dataType : "text",
type : "get",
error : function(data){
alert('something went wrong')
},
success: function(data){
$('.content_show_div').eq(-1).html(data)
$('.content_show_div').eq(-1).dialog('open')
}
})
})
$(document).ready(function(){
var window_width = window.innerWidth
var window_height = window.innerHeight
$(".content_show_div").eq(-1).dialog({
autoOpen: false,
show: "blind",
model: true,
hide: "explode",
width: window_width*0.85,
height: window_height*0.8
});
$('.is_top,.is_hot').change(function(){
process_annc(this)
})
$('.hidden_btn,.display_btn').click(function(){
process_annc(this)
})
})
</script>

View File

@ -4,3 +4,7 @@ en:
feed: Feed
all_feeds: All Feeds
source : Source
all_announcements: All Announcements
display: Display
all_feeds_announcments: All feeds announcments
feed_name: Channel name

View File

@ -4,3 +4,7 @@ zh_tw:
feed: Feed
all_feeds: 'Feeds 列表'
source: 來源
all_announcements: '公告列表'
display: 顯示
all_feeds_announcments: 全部訂閱公告
feed_name: 訂閱名稱

View File

@ -11,7 +11,10 @@ Rails.application.routes.draw do
post "/feeds/unsubscribe", to: 'feeds#unsubscribe'
post "/feeds/disable", to: 'feeds#disable'
post "/feeds/channel_title", to: 'feeds#channel_title'
resources :feeds
resources :feeds,only: ['new','index']
get '/feeds/announcements' => 'feeds#announcements'
post '/feeds/process_annc' => 'feeds#process_annc'
get '/feeds/annc_content' => 'feeds#annc_content'
end
end

39
lib/feed_model/cache.rb Normal file
View File

@ -0,0 +1,39 @@
module FeedModel
module Cache
require 'active_support/concern'
extend ActiveSupport::Concern
included do
before_save :do_before_save
end
def recreate_annc_cache(feed)
tmp = SiteFeedAnnc.where(feed_id: feed.id).first
if tmp.nil?
tmp = SiteFeedAnnc.new(feed_id: feed.id)
end
tmp[:feed_name] = feed[:feed_name]
tmp.all_contents_for_feed = feed.get_annc
tmp.merge_with_category = feed.merge_with_category
tmp.category_title = feed.category[:title] rescue {}
tmp.remote_site_url = feed.remote_site_url
tmp.channel_title = feed.channel_title_for_cache
feed.remote_site_url
tmp.save
end
def do_before_save
if self.class == Category
SiteFeedAnnc.where(merge_with_category: self.id.to_s).each do |site_feed_annc|
recreate_annc_cache(site_feed_annc)
end
elsif self.class == SiteFeed
if self.disabled != true
recreate_annc_cache(self)
else
tmp = SiteFeedAnnc.where(feed_id: self.id).first
if !tmp.nil?
tmp.destroy
end
end
end
end
end
end

View File

@ -1,6 +1,29 @@
module Feeds
class Engine < ::Rails::Engine
initializer "feeds" do
initializer "feeds" do
Thread.new do
begin
require File.expand_path('../../../app/models/site_feed', __FILE__)
require File.expand_path('../../../app/models/site_feed_annc', __FILE__)
if defined?(SiteFeed) && defined?(SiteFeedAnnc)
SiteFeed.where(:disabled.ne => true).each do |site_feed|
tmp = SiteFeedAnnc.where(feed_id: site_feed.id).first
if tmp.nil?
tmp = SiteFeedAnnc.new(feed_id: site_feed.id)
end
tmp.all_contents_for_feed = site_feed.get_annc
tmp[:feed_name] = site_feed[:feed_name]
tmp.merge_with_category = site_feed.merge_with_category
tmp.channel_key = site_feed.channel_key
tmp.category_title = site_feed.category[:title] rescue {}
tmp.channel_title = site_feed.channel_title_for_cache
tmp.save
end
end
rescue => e
puts ['feed_engine',e]
end
end
OrbitApp.registration "Feeds", :type => "ModuleApp" do
module_label "feed.feed"
base_url File.expand_path File.dirname(__FILE__)
@ -23,6 +46,11 @@ module Feeds
:priority=>2,
:active_for_action=>{'admin/feeds'=>'new'},
:available_for => 'managers'
context_link 'feed.all_announcements',
:link_path=>"admin_feeds_announcements_path" ,
:priority=>3,
:active_for_action=>{'admin/feeds'=>'announcements'},
:available_for => 'managers'
end
end

View File

@ -1,16 +1,21 @@
namespace :feeds_module do
desc "Downloading feeds"
task :make_cache,[:url] => :environment do |task,args|
main_directory = File.join("#{Rails.root}","public","site_feeds")
FileUtils.mkdir_p(main_directory) if !File.exists?(main_directory)
SiteFeed.enabled.each do |site_feed|
feed_directory = File.join(main_directory.to_s, site_feed.id.to_s)
FileUtils.mkdir_p(feed_directory) if !File.exists?(feed_directory)
uri = URI(site_feed.feed_url)
res = Net::HTTP.get(uri)
file = File.open(File.join(feed_directory.to_s,site_feed.feed_uid + ".json"),"w")
res.force_encoding("utf-8")
file.write(res)
end
main_directory = File.join("#{Rails.root}","public","site_feeds")
FileUtils.mkdir_p(main_directory) if !File.exists?(main_directory)
SiteFeed.enabled.each do |site_feed|
feed_directory = File.join(main_directory.to_s, site_feed.id.to_s)
FileUtils.mkdir_p(feed_directory) if !File.exists?(feed_directory)
uri = URI(site_feed.feed_url)
res = Net::HTTP.start(uri.host, uri.port,:use_ssl => uri.scheme == 'https',open_timeout: 60,read_timeout: 60) do |http|
req = Net::HTTP::Get.new(uri)
http.request(req).body rescue ''
end
File.open(File.join(feed_directory.to_s,self.feed_uid + ".json"),"w") do |file|
res.force_encoding("utf-8")
file.write(res)
end
site_feed.save
end
end
end