--- s080703/samizdat/lib/samizdat//helpers/application_helper.rb	2008-05-11 10:28:07.000000000 +0200
+++ /usr/lib/ruby/1.8/samizdat//helpers/application_helper.rb	2008-08-20 03:41:24.000000000 +0200
@@ -251,12 +264,37 @@
         [:select, 'focus', focuses, (focus ? (focus.id or focus.uriref) : nil)] ]
   end
 
+  # some calendar constants
+  thisyear=  Time.now.year 
+  d = [] and 0.upto(31){ |i| 
+    d += (i == 0 ? [[0, ("SELECT DAY")]] : [[i, i.to_s]]) } and Days_s = d
+  m = [] and 1.upto(12){ |i| 
+    m += [[i, i.to_s]] } and Months_s = m
+  y = [] and 0.upto(3){ |i| # default 4 years' calendar
+    y += [[thisyear+i, (thisyear+i).to_s]] }  and Years_s = y 
+  
   # form fields for vote on focus rating
   #
-  def focus_fields(focus)
+  def focus_fields(focus,calendar = nil)
     return [] unless @member.allowed_to?('vote')
 
+    if 'start' == calendar
+      fields =
+        [ [:hidden, 'id', @id],
+        [:label, 'calendar_date', _('Select the date at which this event is planned for')],
+        [:select, 'cal_year', Years_s, Time.now.year], 
+        [:select, 'cal_month', Months_s, Time.now.month], 
+        [:select, 'cal_day', Days_s, 0],  # default day is invalid day
+        [:hidden, 'rating', 1], 
+        [:hidden, 'calendar', 'send'],
+      ]
+    else
+
+      if @request.route =~ /vote$/
     fields = focus_select(focus)
+      else
+        fields = []
+      end
 
     if @request.advanced_ui?
       fields.push(
@@ -274,6 +312,19 @@
           [1, _('1 (Likely)')],
           [2, _('2 (Yes)')] ], 0]
     )
+
+    end  #if calendar else
+
+  end
+
+  # TODO: this should probably be moved to helpers.rb or calendar_helpers.rb
+  # or else a fast way to decide if a focus is a date message would be needed
+  def match_cal_focus_title(title) # Y10K unsafe
+     title =~ %r{\A[2-9][0-9]{3}-[01][0-9]-[0-3][0-9]\z}
+  end
+
+  def match_cal_focus_title_rdf
+    %{ ?title ~ '^[2-9][0-9]{3}-[01][0-9]-[0-3][0-9]$' }
   end
 
   # render link to resource with a tooltip
--- s080703/samizdat/lib/samizdat//helpers/resource_helper.rb	2008-05-10 18:35:32.000000000 +0200
+++ /usr/lib/ruby/1.8/samizdat//helpers/resource_helper.rb	2008-08-19 23:29:06.000000000 +0200
@@ -23,7 +23,7 @@
     info = resource_href(id, info) if id
     info << ': ' << focus.print_rating
     info << ' (<a title="' << _('Click to vote on how this resource is related to this focus') <<
-      '" href="resource/' << related.to_s << '/vote' <<
+      '" class="action" href="resource/' << related.to_s << '/vote' <<
       '?focus=' << CGI.escape((focus.id or focus.uriref).to_s) <<
       '">' << _('vote') <<
       '</a>)' if @member.allowed_to?('vote')
@@ -49,6 +49,16 @@
         %{" href="resource/#{related}/vote">} <<
         _('Add another focus') <<
         '</a></p>')
+
+      if config['calendar']  
+        fbox << box(nil,
+                    %{<p><a class="action" title="} << 
+                    _('Click to put this resource in the calendar') <<
+                    %{" href="resource/#{related}/calendar_add">} <<
+                    _('Put this in the calendar') << '</a></p>'
+                    )
+      end # if config['calendar']
+
     end
     fbox
   end
--- s080703/samizdat/lib/samizdat//controllers/resource_controller.rb	2008-05-10 18:35:32.000000000 +0200
+++ /usr/lib/ruby/1.8/samizdat//controllers/resource_controller.rb	2008-08-20 04:18:20.000000000 +0200
@@ -60,6 +60,101 @@
     end
   end
 
+  def calendar_add
+    @member.assert_allowed_to('vote')
+
+    focus, focus_id, rating, cal_year, cal_month, cal_day, creator, format, description = 
+      @request.values_at %w[focus focus_id rating cal_year cal_month cal_day creator format description]
+
+    # julian date if date is valid, otherwise set to nil
+    jd= Date::valid_civil?(cal_year.to_i, cal_month.to_i, cal_day.to_i) 
+
+    if config['calendar'].kind_of? Integer
+      if jd
+        dd = cal_day.to_s
+        dd = "0" + dd  if cal_day.to_i < 10
+        mm = cal_month.to_s 
+        mm = "0" + mm if cal_month.to_i < 10 
+
+        focus_title= cal_year.to_s + "-" + mm + "-" + dd
+
+        # find out if a message for this date already exists
+        db.transaction do |db|
+          focus_id, = db.select_one 'SELECT id FROM Message
+          WHERE title = ? AND version_of is NULL', focus_title
+        end
+        if not focus_id # create a date message
+          @message = Message.new
+          @message.creator = @member
+          @message.content = Content.new(nil, @session.login, 
+                                         focus_title, 'text/textile', ' ')
+          @message.lang = @message.validate_lang((@request['lang'] or @request.language))
+          @message.desc = description  
+          @message.open = true  # date message is open for editing by members
+          @message.insert!
+          focus_id= @message.id
+        end #     if focus_id
+
+        # add link; editing is open, so links can be added/removed by logged-in members
+        # TODO: can this be done without direct transactions in the database?
+        db.transaction do |db|
+          f_content, = db.select_one 'select content from message
+            where id = ? ', focus_id
+          m_title, = db.select_one 'select title from message
+            where id = ? ', @id
+          db.do 'update message set content = ? where id = ? ', 
+            f_content + %{\n} + '"' + m_title.gsub(/"/,'&quot;') + '":/' +  
+            @id.to_s + %{\n}, focus_id 
+          db.do 'update message set format = ? where id = ? ', 
+            "text/textile", focus_id 
+          db.do 'update message set html_full = null where id = ? ',
+            focus_id
+          db.do 'update message set html_short = null where id = ? ',
+            focus_id
+          Content.regenerate_html(focus_id)
+        end #  db.transaction do |db|
+
+        calendar_superfocus = config['calendar']
+      else # invalid date - try again
+        calendar = 'start'  
+      end
+    end  # if 'send' == calendar
+
+    if focus_id = Resource.validate_id(focus_id)
+      # manual entry overrides selection
+      focus = focus_id
+    end
+    focus_id = focus  # needed for resource.new 
+    focus = Focus.new(@request, focus, @id) if focus  # focus becomes an object
+
+    if focus.kind_of?(Focus) and rating and action_confirmed?   # commit vote
+      # rating is validated by focus#rating=
+      db.transaction {|db| focus.rating = rating }
+      if Resource.validate_id(calendar_superfocus)
+        #        focus_r = Resource.new(session, focus_id) # focus as resource object
+        focus_r = Resource.new(@request, focus_id) # focus as resource object
+        calendar_superfocus = Focus.new(@request, calendar_superfocus, focus_r.id) 
+        if calendar_superfocus.kind_of?(Focus)
+          db.transaction {|db| calendar_superfocus.rating = rating }
+        end
+        @request.redirect(@id)
+      else
+        @request.redirect(@id)
+      end
+
+    else   # display vote form
+      calendar = 'start'  # this is the calendar_add method, not the vote method
+      vote_form = secure_form( 'resource/' + @id.to_s + '/calendar_add',
+        *focus_fields(focus, calendar) +  
+        [ [:br], [:submit, nil, _('Submit')] ]
+      )
+      @title = _('Vote') + ': ' + @title
+      @content_for_layout = box(_('Vote'), vote_form) + box(@title, @resource.short)
+    end
+  end
+
+
+
   # RSS feed of related resources
   #
   def rss
@@ -87,11 +182,12 @@
     @layout = nil
   end
 
-  private
+  #  private # shifted lower down for calendar_patch-0.7
 
   # messages that are related to this focus
   #
-  def related_dataset
+  def related_dataset(calendar_days = nil)
+    if not (config['calendar'] and @id.to_i == config['calendar'])
     RdfDataSet.new(%{
 SELECT ?msg
 WHERE (rdf::subject ?stmt ?msg)
@@ -104,7 +200,38 @@
       (s::hidden ?msg ?hidden)
 LITERAL ?rating > 0 AND ?parent IS NULL AND ?current IS NULL #{exclude_hidden}
 ORDER BY ?date DESC})
+    else # calendar superfocus
+      # TODO rewrite this using .collect instead of .each
+      if calendar_days
+        check_near_future = ' AND ?title <= :soon' 
+        order = 'DESC'
+        n_days = (calendar_days.kind_of? Integer) ? calendar_days : 2
+        near_future = (Time.now + n_days * 86400).strftime("%Y-%m-%d")
+      else
+        check_near_future = ''
+        order = 'ASC'
+      end
+      today=Time.now.strftime("%Y-%m-%d")
+      RdfDataSet.new(%{
+SELECT ?msg
+WHERE (rdf::subject ?stmt ?msg)
+      (rdf::predicate ?stmt dc::relation)
+      (rdf::object ?stmt #{@id})
+      (s::inReplyTo ?msg ?parent)
+      (dct::isVersionOf ?msg ?current)
+      (s::rating ?stmt ?rating)
+      (dc::date ?msg ?date)
+      (dc::title ?msg ?title)
+      (s::hidden ?msg ?hidden)
+LITERAL ?rating > 0 AND ?parent IS NULL AND ?current IS NULL #{exclude_hidden} AND
+      ?title >= :now #{check_near_future}
+ORDER BY ?title #{order}}, limit_page, 
+                     { :now  => today.to_s,
+                       :soon => near_future.to_s })
   end
+  end
+
+  private
 
   # related resources
   #

--- s080703_monolanguage-0.3_frontpage_controller.rb	2008-08-20 04:43:08.000000000 +0200
+++ /usr/lib/ruby/1.8/samizdat/controllers/frontpage_controller.rb	2008-08-20 03:40:44.000000000 +0200
@@ -10,6 +10,8 @@
 
 require 'samizdat/helpers/syndication_helper'
 
+require 'samizdat/controllers/resource_controller'
+
 class FrontpageController < Controller
   include SyndicationHelper
 
@@ -39,15 +41,55 @@
       return @content_for_layout = body
     end
 
+    @calendar_id = (config['calendar'].kind_of? Integer) ? config['calendar'] : nil
+    tag_parameters
+
     if full_front_page   # render focuses
       focuses_title = _('Top Focuses')
+      Focus.collect_focuses {|focus, usage|
+        f_title = Resource.new(@request, focus).title
+        if not @calendar_id or 
+            (not match_cal_focus_title(f_title) and
+            focus != @calendar_id)
+          @tag_min = usage if ((usage < @tag_min) and (usage > 0))
+          @tag_max = usage if usage > @tag_max
+        end
+      }
+      d_tag = Math.log(@tag_max/@tag_min)/@n_sizes.to_f
+      d_tag = 0.001 if d_tag < 0.001
+
+      calendar_resource_href =  (@calendar_id)?
+         resource_href(@calendar_id, Resource.new(@request, @calendar_id).title) : ''
+
+      if @calendar_id
+        cal_controller = ResourceController.new(@request, @calendar_id)
+        # calendar in left column
+        calendar_days = (config['calendar_days'].kind_of? Integer) ?
+           config['calendar_days'] : 30
+        dataset = cal_controller.related_dataset(calendar_days)
+        calendar_upcoming = dataset[0].collect { |r,| 
+               m = Message.new(r)
+               # TODO: class="calendar" -> *.css ?
+               %{<div class="focus"> <div class="title">} + 
+                       %{<a href="/} + r.to_i.to_s + %{">} + m.content.title + %{:</a></div>\n} +
+                        (m.content.html_short or m.content.html_full or '') + 
+                         %{</div>\n} }.join 
+        calendar_upcoming_title = calendar_resource_href 
+      end
+
       focuses =
         Focus.collect_focuses {|focus, usage|
-          %{<div class="focus">} + resource_href(
-            focus,
-            Resource.new(@request, focus).title + %{ (#{usage})}
-          ) + %{</div>\n}
-        }.join
+          f_title = Resource.new(@request, focus).title
+          if not @calendar_id or 
+              (not match_cal_focus_title(f_title) and
+              focus != @calendar_id)
+            t = usage
+	    t = 1 if t < 1
+            t_size = (@size_min * Math.exp( @vary_log * Math.log(t/@tag_min)/d_tag )).to_i
+            %{<span style="font-size: #{t_size}%">} +
+              resource_href( focus, f_title + %{ (#{usage})} ) + %{</span><br />\n}
+          end
+        }.join + %{</div>\n}
 
       focuses <<
         '<div class="foot"><div class="nav"><a href="foci">' <<
@@ -70,6 +112,13 @@
         Resource.new(@request, msg).short }
       end
 
+      if @calendar_id 
+        cal_controller = ResourceController.new(@request, @calendar_id)
+        cal_dataset = cal_controller.related_dataset(2)
+        features = cal_dataset[0].collect {|r,| 
+          Resource.new(@request, r).short } << features
+      end
+
       features_title = _('Features') + page_number(features_page)
       features = features.join <<
         %{<div class="foot">} <<
@@ -79,11 +128,11 @@
     end
 
     if render_updates
-      dataset = updates_dataset
+      dataset = updates_dataset(@calendar_id)
       if @request.monolanguage?
         updates = dataset[page - 1].collect {|msg,date|
           [Resource.new(@request, msg).list_item, date] }
-        dataset_t = translated_updates_dataset
+        dataset_t = translated_updates_dataset(@calendar_id)
         updates += dataset_t[page - 1].collect {|msg,date|
           [Resource.new(@request, msg).list_item, date] }
         updates.sort!{|a,b| b[1].to_time <=> a[1].to_time}
@@ -99,17 +148,31 @@
         nav(@request.monolanguage? ? [dataset, dataset_t] : dataset) )
     end
 
+    if @calendar_id 
+      left_top = calendar_upcoming
+      left_top_title = calendar_upcoming_title
+      left_mid = focuses
+      left_mid_title = focuses_title
+    else
+      left_top = focuses
+      left_top_title = focuses_title
+      left_mid = ''
+      left_mid_title = ''
+    end
+
     body =
       if full_front_page
 %{<table>
   <thead>
-    <tr><th>#{focuses_title}</th><th>#{features_title}</th><th>#{updates_title}</th></tr>
+    <tr><th>#{left_top_title}</th><th>#{features_title}</th><th>#{updates_title}</th></tr>
   </thead>
   <tr>
-    <td class="focuses">#{focuses}</td>
-    <td class="features" rowspan="3">#{features}</td>
-    <td class="updates" rowspan="3">#{updates}</td>
+    <td class="focuses">#{left_top}</td>
+    <td class="features" rowspan="5">#{features}</td>
+    <td class="updates" rowspan="5">#{updates}</td>
   </tr>
+  <tr><td class="links-head">#{left_mid_title}</td></tr>
+  <tr><td class="focuses">#{left_mid}</td></tr>
   <tr><td class="links-head">} << _('Links') << '</td></tr>
   <tr><td class="links">
     <p><a href="query/run?q=' << CGI.escape('SELECT ?resource WHERE (dc::date ?resource ?date) (s::inReplyTo ?resource ?parent) LITERAL ?parent IS NOT NULL ORDER BY ?date DESC') << '">' << _('All Replies') << '</a></p>
@@ -172,7 +236,7 @@
       resources =
         case feed
         when 'updates'
-          updates_dataset[0]
+          updates_dataset(@calendar_id)[0]
         else
           features_dataset(limit_page)[0]
         end
@@ -240,21 +304,25 @@
 
   # messages that are not comments or older version, sorted chronologically
   #
-  def updates_dataset
+  def updates_dataset(exclude_calendar = nil) 
+    exclude_calendar = exclude_calendar ? ' AND NOT ' + match_cal_focus_title_rdf : ''
     RdfDataSet.new(%{
 SELECT ?msg, ?date
 WHERE (dc::date ?msg ?date)
       (s::inReplyTo ?msg ?parent)
       (dc::language ?msg ?language)
+      (dc::title ?msg ?title)
       (dct::isVersionOf ?msg ?version_of)
       (s::hidden ?msg ?hidden #{filter_hidden})
 LITERAL ?parent IS NULL AND ?version_of IS NULL #{@check_monolang}
+      #{exclude_calendar}
 ORDER BY ?date DESC}, 
                    limit_page,
                    { :accept_lang => @monolang})
   end
 
-  def translated_updates_dataset
+  def translated_updates_dataset(exclude_calendar = nil) 
+    exclude_calendar = exclude_calendar ? ' AND NOT ' + match_cal_focus_title_rdf : ''
     RdfDataSet.new(%{
 SELECT ?parent, ?date
 WHERE (dc::date ?msg ?date)
@@ -263,9 +331,11 @@
       (rdf::subject ?m_stmt ?msg)
       (rdf::object ?m_stmt focus::Translation)
       (dc::language ?msg ?language)
+      (dc::title ?msg ?title)
       (dct::isVersionOf ?msg ?version_of)
       (s::hidden ?msg ?hidden #{filter_hidden})
 LITERAL NOT ?parent IS NULL AND ?version_of IS NULL #{@check_monolang}
+      #{exclude_calendar}
 ORDER BY ?date DESC}, 
                    limit_page,
                    { :accept_lang => @monolang})
@@ -333,4 +407,13 @@
       @check_monolang = ''
     end
   end
+
+  def tag_parameters
+    @tag_min = 9.9e9
+    @tag_max = 1.0
+    @n_sizes = 5
+    @vary_log = 0.2
+    @size_min = Math.exp(-@vary_log * (@n_sizes/2).to_f) *100 # as percentage
+  end
+
 end

--- s080703/samizdat/lib/samizdat//controllers/foci_controller.rb	2008-05-10 18:35:32.000000000 +0200
+++ /usr/lib/ruby/1.8/samizdat//controllers/foci_controller.rb	2008-08-20 04:17:52.000000000 +0200
@@ -11,17 +11,22 @@
 class FociController < Controller
 
   def index
-    dataset = RdfDataSet.new('
+    exclude_calendar =  (config['calendar'].kind_of? Integer) ? 
+      ' AND NOT ' + match_cal_focus_title_rdf + 
+      %{ AND NOT ?focus = '#{config['calendar']}' } : ''
+    dataset = RdfDataSet.new(%{
       SELECT ?focus, count(?resource)
       WHERE (rdf::predicate ?stmt dc::relation)
             (rdf::subject ?stmt ?resource)
             (rdf::object ?stmt ?focus)
             (s::inReplyTo ?resource ?parent)
+            (dc::title ?focus ?title)
             (dct::isVersionOf ?resource ?current)
             (s::rating ?stmt ?rating FILTER ?rating > 0)
       LITERAL ?parent IS NULL AND ?current IS NULL
+            #{exclude_calendar}
       GROUP BY ?focus
-      ORDER BY count(?resource) DESC')
+      ORDER BY count(?resource) DESC })
 
     page = (@request['page'] or 1).to_i
 
--- s080703/samizdat/lib/samizdat//storage.rb	2008-06-20 15:56:02.000000000 +0200
+++ /usr/lib/ruby/1.8/samizdat//storage.rb	2008-08-20 03:08:33.000000000 +0200
@@ -265,7 +265,7 @@
   NUMBER = Regexp.new(/\A-?[[:digit:]]+(\.[[:digit:]]+)?\z/).freeze
 
   # regexp for operator
-  OPERATOR = Regexp.new(/\A(\+|-|\*|\/|<|<=|>|>=|=|I?LIKE|NOT|AND|OR|IS|NULL)\z/i).freeze
+  OPERATOR = Regexp.new(/\A(\+|-|\*|\/|<|<=|>|>=|=|~|I?LIKE|NOT|AND|OR|IS|NULL)\z/i).freeze
 
   # regexp for aggregate function
   AGGREGATE = Regexp.new(/\A(avg|count|max|min|sum)\z/i).freeze
