# category.rb # # Copyright (c) 2003 Junichiro KITA # Distributed under the GPL2 or any later version. # # # initialize # def category_init @conf['category.header1'] ||= %Q[
\n

\n<%= category_navi %>\n

\n
\n] @conf['category.header2'] ||= %Q[

Categories |\n<%= category_list %>\n

\n] @conf['category.edit_support'] = @conf['category.edit_support'].to_i rescue 1 end category_init def category_icon_location_init @category_icon_dir = (@conf['category.icon_dir'] || './icons/').sub(%r|/*$|, '/') @category_icon_url = (@conf['category.icon_url'] || './icons/').sub(%r|/*$|, '/') end def category_icon_init category_icon_location_init @conf['category.icon'] ||= '' @category_icon = {} @conf['category.icon'].split(/\n/).each do |l| c, i = l.split next if c.nil? or i.nil? @category_icon[c] = i if File.exist?("#{@category_icon_dir}#{i}".untaint) end end category_icon_init # # plugin methods # def category_form # don't you need this method any more? end def category_anchor(category) period = @conf['category.period'] || 'quarter' period_string = case period when "month" "year=#{@date.year};month=#{'%02d' % @date.month};" when "quarter" "year=#{@date.year};month=#{(@date.month - 1) / 3 + 1}Q;" when "half" "year=#{@date.year};month=#{(@date.month - 1) / 6 + 1}H;" when "year" "year=#{@date.year};" else "" end if @category_icon[category] and !@cgi.mobile_agent? %Q|#{h category}| else %Q|[#{h category}]| end end def category_navi_anchor(info, label, mobile = false) if mobile then "|#{info.make_anchor(label)}" else ((!label.nil?) && label.empty?) ? '' : %Q[#{info.make_anchor(label)}\n] end end def category_navi mobile = @cgi.mobile_agent? info = Category::Info.new(@cgi, @years, @conf) mode = info.mode result = '' case mode when :year, :half, :quarter, :month all_diary = Category::Info.new(@cgi, @years, @conf, year: -1, month: -1) all = Category::Info.new(@cgi, @years, @conf, category: ['ALL'], year: -1, month: -1) result << category_navi_anchor(info.prev, @conf['category.prev_' + mode.to_s], mobile) result << category_navi_anchor(info.next, @conf['category.next_' + mode.to_s], mobile) unless mobile then result << category_navi_anchor(all_diary, @conf['category.all_diary']) result << category_navi_anchor(all, @conf['category.all']) end when :all year = Category::Info.new(@cgi, @years, @conf, year: Time.now.year.to_s) half = Category::Info.new(@cgi, @years, @conf, year: Time.now.year.to_s, month: "#{((Time.now.month - 1) / 6 + 1)}H") quarter = Category::Info.new(@cgi, @years, @conf, year: Time.now.year.to_s, month: "#{((Time.now.month - 1) / 3 + 1)}Q") month = Category::Info.new(@cgi, @years, @conf, year: Time.now.year.to_s, month: '%02d' % Time.now.month) result << category_navi_anchor(year, @conf['category.this_year'], mobile) result << category_navi_anchor(half, @conf['category.this_half'], mobile) result << category_navi_anchor(quarter, @conf['category.this_quarter'], mobile) result << category_navi_anchor(month, @conf['category.this_month'], mobile) end if !info.category.include?('ALL') and !mobile then all_category = Category::Info.new(@cgi, @years, @conf, category: ['ALL']) result << category_navi_anchor(all_category, @conf['category.all_category']) end result end def category_list_sections info = Category::Info.new(@cgi, @years, @conf) r = '' raise ::TDiary::NotFound if @categorized.empty? and bot? @categorized.to_a.sort_by{|e| e[0].downcase}.each do |c, v| info.category = [c] if @category_icon[c] img = %Q|#{h c}| else img = '' end r << <

#{img}#{info.make_anchor}

HTML end r end def category_list_sections_mobile info = Category::Info.new(@cgi, @years, @conf) r = '' raise ::TDiary::NotFound if @categorized.empty? and bot? @categorized.to_a.sort_by{|e| e[0].downcase}.each do |c, v| info.category = [c] r << "

#{info.make_anchor}

" r << "" end r end def category_list info = Category::Info.new(@cgi, @years, @conf) @categories.map do |c| info.category = [c] info.make_anchor end.join(" | \n") end def category_dropdown_list(label = nil, multiple = nil) label ||= 'Categorize!' multiple ||= false info = Category::Info.new(@cgi, @years, @conf) category = info.category if category.empty? return '' if @cgi.mobile_agent? category = ['ALL'] end options = '' (['ALL'] + @categories.sort_by{|e| e.downcase}).each do |c| options << %Q|\t\t\n| end params = '' params << %Q[] if info.year params << %Q[] if info.month <
#{params}
HTML end # # misc # def category_icon_save @conf['category.icon'] = @category_icon.map {|c, i| "#{c} #{i}"}.join("\n") end module Category # # CGI (mock-up CGI class for Cache::recreate) # class CGI attr_reader :params def initialize @params = Hash.new([]) end def referer; nil; end def user_agent; nil; end def mobile_agent?; nil; end def request_method; 'GET'; end end # # Info # class Info include ERB::Util def initialize(cgi, years, conf, args = {}) @cgi = cgi @years = years @conf = conf @category = args[:category] || @cgi.params['category'].map do |c| @conf.to_native(c, @conf.encoding_old) end @year = args[:year] || @cgi.params['year'][0] @month = args[:month] || @cgi.params['month'][0] @mode = :all set_mode end protected attr_writer :year attr_writer :month public attr :category, true attr_reader :year attr_reader :month attr_reader :mode def prev pp = self.dup case mode when :half h = @month.to_i if h == 1 pp.month = "2H" pp.year = (@year.to_i - 1).to_s if @year else pp.month = "1H" end when :quarter q = @month.to_i if q == 1 pp.month = "4Q" pp.year = (@year.to_i - 1).to_s if @year else pp.month = "#{q - 1}Q" end when :month m = @month.to_i if m == 1 pp.month = "12" pp.year = (@year.to_i - 1).to_s if @year else pp.month = '%02d' % (m - 1) end when :year pp.year = (@year.to_i - 1).to_s end pp end def next pp = self.dup case mode when :half h = @month.to_i if h == 2 pp.month = "1H" pp.year = (@year.to_i + 1).to_s if @year else pp.month = "2H" end when :quarter q = @month.to_i if q == 4 pp.month = "1Q" pp.year = (@year.to_i + 1).to_s if @year else pp.month = "#{q + 1}Q" end when :month m = @month.to_i if m == 12 pp.month = "01" pp.year = (@year.to_i + 1).to_s if @year else pp.month = '%02d' % (m + 1) end when :year pp.year = (@year.to_i + 1).to_s end pp end def make_anchor(label = nil) a = @category.map {|c| "category=#{u c}"}.join(';') a << ";year=#{@year}" if @year a << ";month=#{@month}" if @month if label case mode when :year label = label.gsub(/\$1/, @year) when :month, :quarter, :half label = label.gsub(/\$2/, @month) label = label.gsub(/\$1/, @year || '*') end else label = @category.to_a.join(':') end %Q|#{h label}| end # # return ym_spec # # {"yyyy" => ["mm", ...], ...} # # date spec: # (1) none -> all diary # (2) month=xH -> all diary in xH of all year # (3) year=YYYY;month=xH -> all diary in YYYY/xH # (4) month=xQ -> all diary in xQ of all year # (5) year=YYYY;month=xQ -> all diary in YYYY/xQ # (6) month=MM -> all diary in MM of all year # (7) year=YYYY;month=MM -> all diary in YYYY/MM # (8) year=YYYY -> all diary in YYYY # def years if @mode == :all return @years end months = case @mode when :half [('01'..'06'), ('07'..'12')][@month.to_i - 1].to_a when :quarter [['01', '02', '03'], ['04', '05', '06'], ['07', '08', '09'], ['10', '11', '12']][@month.to_i - 1] when :month [@month] else ('01'..'12').to_a end r = {} (@year ? [@year] : @years.keys).each do |y| r[y] = months end r end # # date spec: # (1) none -> all # (2) month=xH -> half # (3) year=YYYY;month=xH -> half # (4) month=xQ -> quarter # (5) year=YYYY;month=xQ -> quarter # (6) month=MM -> month # (7) year=YYYY;month=MM -> month # (8) year=YYYY -> year # def set_mode if @year.nil? and @month.nil? @mode = :all end if /\d{4}/ === @year.to_s @mode = :year else @year = nil end if /[12]H/ === @month.to_s @mode = :half elsif /[1-4]Q/ === @month.to_s @mode = :quarter elsif (1..12).include?(@month.to_i) @mode = :month else @month = nil end end end # # Cache # class Cache include ERB::Util def initialize(conf, bind) @conf = conf @binding = bind # ...... very ugly @plugin = @binding.eval('self') @categories = nil end def get(db, cat) JSON.load(db.get(cat) || '{}') end def set(db, cat, data) db.set(cat, data.to_json) end def add_categories(list) return if list.nil? or list.empty? replace_categories(restore_categories + list) end def replace_categories(list) @categories = list end def restore_categories return @categories if @categories @plugin.__send__(:transaction, 'category') do |db| @categories = db.keys end return @categories end # # cache each section of diary # used in update_proc # def replace_sections(diary) return if diary.nil? or !diary.categorizable? categorized = categorize_diary(diary) categories = restore_categories deleted = [] ymd = diary.date.strftime('%Y%m%d') @plugin.__send__(:transaction, 'category') do |db| categories.each do |c| cat = get(db, c) || {} if diary.visible? and categorized[c] cat.update(categorized[c]) set(db, c, cat) else # diary is invisible or sections of this category is deleted cat.delete(ymd) if cat.empty? db.delete(c) deleted << c else set(db, c, cat) end end end if !deleted.empty? replace_categories(categories - deleted) end end end # # (re)create category cache # def recreate(years) cgi = Category::CGI::new list = [] @plugin.__send__(:transaction, 'category') do |db| db.keys.each {|key|db.delete(key)} years.each do |y, ms| ms.each do |m| ym = "#{y}#{m}" cgi.params['date'] = [ym] m = TDiaryMonthWithoutFilter.new(cgi, '', @conf) m.diaries.each do |ymd, diary| next if !diary.visible? or !diary.categorizable? categorized = categorize_diary(diary) categorized.keys.each do |c| cat = get(db, c) || {} set(db, c, cat.update(categorized[c])) end diary.each_section do |s| list |= s.categories unless s.categories.empty? end end end end end replace_categories(list) end # # categorize sections of category of years # # {"category" => {"yyyymmdd" => [[idx, title, excerpt], ...], ...}, ...} # def categorize(category, years) categories = category - ['ALL'] if categories.empty? categories = restore_categories else categories &= restore_categories end categorized = {} begin categorized.clear categories.each do |c| @plugin.__send__(:transaction, 'category') do |db| categorized[c] = get(db, c) end categorized[c].keys.each do |ymd| y, m = ymd[0,4], ymd[4,2] if years[y].nil? or !years[y].include?(m) categorized[c].delete(ymd) end end categorized.delete(c) if categorized[c].empty? end rescue NoMethodError # when categorized[c] is nil recreate(years) retry end categorized end private def cache_file(category = nil) if category "#{@dir}/#{u( category ).gsub(/%20/,'+')}".untaint else "#{@dir}/category_list" end end # # categorize sections of diary # # {"category" => {"yyyymmdd" => [[idx, title, excerpt], ...]}} # def categorize_diary(diary) categorized = {} ymd = diary.date.strftime('%Y%m%d') idx = 1 diary.each_section do |s| s.categories.each do |c| categorized[c] = {} if categorized[c].nil? categorized[c][ymd] = [] if categorized[c][ymd].nil? body = <' ret << "#{@category_conf_label}:\n" @categories.sort_by{|e| e.downcase}.each do |c| ret << %Q!| #{h c}\n! end ret << "|\n\n
\n" ret end def category_edit_support_dropdown ret = '' ret << '
' ret << %Q|#{@category_conf_label}: \n
\n
\n" ret end if @conf['category.edit_support'] != 0 then enable_js( 'category.js' ) add_edit_proc do |date| ret = '' unless @categories.size == 0 then ret << if @conf['category.edit_support'] == 2 then category_edit_support_dropdown else category_edit_support_flatlist end end end end # # when update diary, update cache # add_update_proc do if /^(append|replace)$/ =~ @mode cache = @category_cache list = [] diary = @diaries[@date.strftime('%Y%m%d')] diary.each_section do |s| list |= s.categories end cache.add_categories(list) cache.replace_sections(diary) end end # # configuration # def category_icon_find_icons return if @category_all_icon @category_all_icon = [] %w(png jpg gif bmp).each do |e| @category_all_icon += Dir.glob("#{@category_icon_dir}*.#{e}".untaint).map {|i| File.basename(i)} end @category_all_icon.sort! end def category_icon_select(category) options = %Q|<\t\n| @category_all_icon.each do |i| options << %Q|\t\n| end < #{options} HTML end def category_icon_sample @category_all_icon.map do |i| %Q|#{h i}\n| end.join("/\n") end if @mode == 'conf' || @mode == 'saveconf' add_conf_proc( 'category', @category_conf_label, 'basic' ) do if @mode == 'saveconf' if @cgi.valid?( 'category_initialize' ) @category_cache.recreate(@years) end [ 'category.header1', 'category.header2', ].each do |name| @conf[name] = @conf.to_native( @cgi.params[name][0] ) end [ 'category.prev_year', 'category.next_year', 'category.prev_half', 'category.next_half', 'category.prev_quarter', 'category.next_quarter', 'category.prev_month', 'category.next_month', 'category.this_year', 'category.this_half', 'category.this_quarter', 'category.this_month', 'category.all_diary', 'category.all_category', 'category.all', ].each do |name| @conf[name] = @conf.to_native( @cgi.params[name][0] ) end if ["month", "quarter", "half", "year", "all"].index(@cgi.params["category.period"][0]) @conf["category.period"] = @cgi.params["category.period"][0] end @conf['category.edit_support'] = (@cgi.params['category.edit_support'][0] || '1').to_i end category_conf_html end category_icon_find_icons if @cgi.params['conf'][0] == 'category_icon' add_conf_proc( 'category_icon', @category_icon_conf_label, 'basic' ) do if @mode == 'saveconf' unless @conf.secure [ 'category.icon_dir', 'category.icon_url', ].each do |name| @conf[name] = @cgi.params[name][0].sub(%r|/*$|, '/') end category_icon_location_init end @cgi.params.keys.each do |key| next unless /\Acategory\.icon\..*\z/ === key category = key.sub(/\Acategory\.icon\./, '') if @cgi.params[key][0] == 'none' @category_icon.delete(category) else @category_icon[category] = @cgi.params[key][0] end end category_icon_save end category_icon_conf_html end end @categories = @category_cache.restore_categories if @mode == 'categoryview' info = Category::Info.new(@cgi, @years, @conf) @categorized = @category_cache.categorize(info.category, info.years) end # Local Variables: # mode: ruby # indent-tabs-mode: t # tab-width: 3 # ruby-indent-level: 3 # End: # vim: ts=3