liquid_feedback_frontend

changeset 11:77d58efe99fd beta7

Version beta7

Important security fixes:
- Added missing HTML encoding to postal address of member
- Link to discussion URL only if it starts with http(s)://

Other bugfixes:
- Fixed wrong display of 2nd level delegating voters for an initiative
- Do not display invited initiators as initiators while voting
- Added missing translation

New features:
- Public message of the day
- Both direct and indirect supporter count is shown in tab heads
- Support shown in initiative lists

Language chooser at the login page has been added (again)
author bsw
date Fri Jan 22 12:00:00 2010 +0100 (2010-01-22)
parents 72c5e0ee7c98
children 60bed92b7434
files app/main/_filter/21_auth.lua app/main/_filter_view/30_navigation.lua app/main/_layout/default.html app/main/index/login.lua app/main/initiative/_list.lua app/main/initiative/show.lua app/main/issue/_list.lua app/main/member/_show.lua app/main/timeline/_action/delete_filter.lua app/main/timeline/_action/save.lua app/main/timeline/_action/update.lua app/main/timeline/_constants.lua app/main/timeline/_list.lua app/main/timeline/index.lua app/main/timeline/list_filter.lua app/main/timeline/save_filter.lua app/main/vote/list.lua app/main/vote/show_incoming.lua config/default.lua env/util/gregor.lua locale/help/initiative.revoke.de.txt locale/help/timeline.index.de.txt locale/motd/de_public.txt locale/translations.de.lua model/member.lua model/setting_map.lua model/timeline.lua static/gregor.js/gregor.css static/gregor.js/gregor.js static/icons/16/clock.png static/icons/16/magnifier.png static/icons/16/text_list_bullets.png static/icons/16/user_edit.png static/style.css
line diff
     1.1 --- a/app/main/_filter/21_auth.lua	Sun Jan 10 12:00:00 2010 +0100
     1.2 +++ b/app/main/_filter/21_auth.lua	Fri Jan 22 12:00:00 2010 +0100
     1.3 @@ -10,6 +10,7 @@
     1.4      or request.get_action() == "reset_password"
     1.5      or request.get_view()   == "confirm_notify_email"
     1.6      or request.get_action() == "confirm_notify_email"
     1.7 +    or request.get_action() == "set_lang"
     1.8    )
     1.9  )
    1.10  
     2.1 --- a/app/main/_filter_view/30_navigation.lua	Sun Jan 10 12:00:00 2010 +0100
     2.2 +++ b/app/main/_filter_view/30_navigation.lua	Fri Jan 22 12:00:00 2010 +0100
     2.3 @@ -42,50 +42,77 @@
     2.4  
     2.5  slot.select('navigation', function()
     2.6  
     2.7 -    ui.link{
     2.8 -      content = function()
     2.9 -        ui.image{ static = "icons/16/house.png" }
    2.10 -        slot.put(_"Home")
    2.11 -      end,
    2.12 -      module = 'index',
    2.13 -      view = 'index'
    2.14 -    }
    2.15 +  ui.link{
    2.16 +    content = function()
    2.17 +      ui.image{ static = "icons/16/house.png" }
    2.18 +      slot.put(_"Home")
    2.19 +    end,
    2.20 +    module = 'index',
    2.21 +    view = 'index'
    2.22 +  }
    2.23 +
    2.24 +  local setting_key = "liquidfeedback_frontend_timeline_current_options"
    2.25 +  local setting = Setting:by_pk(app.session.member.id, setting_key)
    2.26  
    2.27 -    ui.link{
    2.28 -      content = function()
    2.29 -        ui.image{ static = "icons/16/package.png" }
    2.30 -        slot.put(_"Areas")
    2.31 -      end,
    2.32 -      module = 'area',
    2.33 -      view = 'list'
    2.34 -    }
    2.35 +  timeline_params = {}
    2.36 +  if setting then
    2.37 +    for event_ident, filter_idents in setting.value:gmatch("(%S+):(%S+)") do
    2.38 +      timeline_params["option_" .. event_ident] = true
    2.39 +      if filter_idents ~= "*" then
    2.40 +        for filter_ident in filter_idents:gmatch("([^\|]+)") do
    2.41 +          timeline_params["option_" .. event_ident .. "_" .. filter_ident] = true
    2.42 +        end
    2.43 +      end
    2.44 +    end
    2.45 +  end
    2.46 +
    2.47 +  timeline_params.date = param.get("date") or today
    2.48 +
    2.49 +  ui.link{
    2.50 +    content = function()
    2.51 +      ui.image{ static = "icons/16/time.png" }
    2.52 +      slot.put(_"Timeline")
    2.53 +    end,
    2.54 +    module = "timeline",
    2.55 +    action = "update"
    2.56 +--    params = timeline_params
    2.57 +  }
    2.58  
    2.59 -    ui.link{
    2.60 -      content = function()
    2.61 -        ui.image{ static = "icons/16/group.png" }
    2.62 -        slot.put(_"Members")
    2.63 -      end,
    2.64 -      module = 'member',
    2.65 -      view = 'list'
    2.66 -    }
    2.67 +  ui.link{
    2.68 +    content = function()
    2.69 +      ui.image{ static = "icons/16/package.png" }
    2.70 +      slot.put(_"Areas")
    2.71 +    end,
    2.72 +    module = 'area',
    2.73 +    view = 'list'
    2.74 +  }
    2.75 +
    2.76 +  ui.link{
    2.77 +    content = function()
    2.78 +      ui.image{ static = "icons/16/group.png" }
    2.79 +      slot.put(_"Members")
    2.80 +    end,
    2.81 +    module = 'member',
    2.82 +    view = 'list'
    2.83 +  }
    2.84  
    2.85 -    ui.link{
    2.86 -      content = function()
    2.87 -        ui.image{ static = "icons/16/book_edit.png" }
    2.88 -        slot.put(_"Contacts")
    2.89 -      end,
    2.90 -      module = 'contact',
    2.91 -      view = 'list'
    2.92 -    }
    2.93 +  ui.link{
    2.94 +    content = function()
    2.95 +      ui.image{ static = "icons/16/book_edit.png" }
    2.96 +      slot.put(_"Contacts")
    2.97 +    end,
    2.98 +    module = 'contact',
    2.99 +    view = 'list'
   2.100 +  }
   2.101  
   2.102 -    ui.link{
   2.103 -      content = function()
   2.104 -        ui.image{ static = "icons/16/information.png" }
   2.105 -        slot.put(_"About")
   2.106 -      end,
   2.107 -      module = 'index',
   2.108 -      view = 'about'
   2.109 -    }
   2.110 +  ui.link{
   2.111 +    content = function()
   2.112 +      ui.image{ static = "icons/16/information.png" }
   2.113 +      slot.put(_"About")
   2.114 +    end,
   2.115 +    module = 'index',
   2.116 +    view = 'about'
   2.117 +  }
   2.118  
   2.119    if app.session.member.admin then
   2.120  
     3.1 --- a/app/main/_layout/default.html	Sun Jan 10 12:00:00 2010 +0100
     3.2 +++ b/app/main/_layout/default.html	Fri Jan 22 12:00:00 2010 +0100
     3.3 @@ -3,6 +3,7 @@
     3.4      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     3.5      <title><!-- WEBMCP SLOTNODIV app_name --></title>
     3.6      <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/static/trace.css" />
     3.7 +    <link rel="stylesheet" type="text/css" media="screen" href="__BASEURL__/static/gregor.js/gregor.css" />
     3.8      <link rel="stylesheet" type="text/css" media="screen" href="<!-- WEBMCP SLOTNODIV stylesheet_url -->" />
     3.9      <!-- WEBMCP SLOTNODIV html_head -->
    3.10    </head>
     4.1 --- a/app/main/index/login.lua	Sun Jan 10 12:00:00 2010 +0100
     4.2 +++ b/app/main/index/login.lua	Fri Jan 22 12:00:00 2010 +0100
     4.3 @@ -12,6 +12,55 @@
     4.4  
     4.5  slot.put_into("title", encode.html(config.app_title))
     4.6  
     4.7 +slot.select("title", function()
     4.8 +  ui.container{
     4.9 +    attr = { class = "lang_chooser" },
    4.10 +    content = function()
    4.11 +      for i, lang in ipairs{"en", "de"} do
    4.12 +        ui.link{
    4.13 +          content = function()
    4.14 +            ui.image{
    4.15 +              static = "lang/" .. lang .. ".png",
    4.16 +              attr = { style = "margin-left: 0.5em;", alt = lang }
    4.17 +            }
    4.18 +          end,
    4.19 +          module = "index",
    4.20 +          action = "set_lang",
    4.21 +          params = { lang = lang },
    4.22 +          routing = {
    4.23 +            default = {
    4.24 +              mode = "redirect",
    4.25 +              module = request.get_module(),
    4.26 +              view = request.get_view(),
    4.27 +              id = param.get_id_cgi(),
    4.28 +              params = param.get_all_cgi()
    4.29 +            }
    4.30 +          }
    4.31 +        }
    4.32 +      end
    4.33 +    end
    4.34 +  }
    4.35 +end)
    4.36 +
    4.37 +
    4.38 +local lang = locale.get("lang")
    4.39 +local basepath = request.get_app_basepath() 
    4.40 +local file_name = basepath .. "/locale/motd/" .. lang .. "_public.txt"
    4.41 +local file = io.open(file_name)
    4.42 +if file ~= nil then
    4.43 +  local help_text = file:read("*a")
    4.44 +  if #help_text > 0 then
    4.45 +    ui.container{
    4.46 +      attr = { class = "motd wiki" },
    4.47 +      content = function()
    4.48 +        slot.put(format.wiki_text(help_text))
    4.49 +      end
    4.50 +    }
    4.51 +  end
    4.52 +end
    4.53 +
    4.54 +
    4.55 +
    4.56  ui.tag{
    4.57    tag = 'p',
    4.58    content = _'You need to be logged in, to use this system.'
    4.59 @@ -51,3 +100,4 @@
    4.60      }
    4.61    end
    4.62  }
    4.63 +
     5.1 --- a/app/main/initiative/_list.lua	Sun Jan 10 12:00:00 2010 +0100
     5.2 +++ b/app/main/initiative/_list.lua	Fri Jan 22 12:00:00 2010 +0100
     5.3 @@ -1,6 +1,18 @@
     5.4  local initiatives_selector = param.get("initiatives_selector", "table")
     5.5  initiatives_selector:join("issue", nil, "issue.id = initiative.issue_id")
     5.6  
     5.7 +local limit = param.get("limit", atom.number)
     5.8 +
     5.9 +local more_initiatives_count
    5.10 +if limit then
    5.11 +  local initiatives_count = initiatives_selector:count()
    5.12 +  if initiatives_count > limit then
    5.13 +    more_initiatives_count = initiatives_count - limit
    5.14 +  end
    5.15 +  initiatives_selector:limit(limit)
    5.16 +end
    5.17 +
    5.18 +
    5.19  local issue = param.get("issue", "table")
    5.20  
    5.21  local order_options = {}
    5.22 @@ -53,12 +65,22 @@
    5.23    end
    5.24  end
    5.25  
    5.26 +initiatives_selector
    5.27 +  :left_join("initiator", "_initiator", { "_initiator.initiative_id = initiative.id AND _initiator.member_id = ?", app.session.member.id} )
    5.28 +  :left_join("supporter", "_supporter", { "_supporter.initiative_id = initiative.id AND _supporter.member_id = ?", app.session.member.id} )
    5.29 +
    5.30 +  :add_field("(_initiator.member_id NOTNULL)", "is_initiator")
    5.31 +  :add_field({"(_supporter.member_id NOTNULL) AND NOT EXISTS(SELECT 1 FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)))", app.session.member.id }, "is_supporter")
    5.32 +  :add_field({"EXISTS(SELECT 1 FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)))", app.session.member.id }, "is_potential_supporter")
    5.33 +
    5.34 +
    5.35  ui_order{
    5.36    name = name,
    5.37    selector = initiatives_selector,
    5.38    options = order_options,
    5.39    content = function()
    5.40      ui.paginate{
    5.41 +      name = issue and "issue_" .. tostring(issue.id) .. "_page" or nil,
    5.42        selector = initiatives_selector,
    5.43        per_page = param.get("per_page", atom.number),
    5.44        content = function()
    5.45 @@ -130,6 +152,30 @@
    5.46                  static = "icons/16/new.png"
    5.47                }
    5.48              end
    5.49 +            if record.is_supporter then
    5.50 +              slot.put("&nbsp;")
    5.51 +              local label = _"You are supporting this initiative"
    5.52 +              ui.image{
    5.53 +                attr = { alt = label, title = label },
    5.54 +                static = "icons/16/thumb_up_green.png"
    5.55 +              }
    5.56 +            end
    5.57 +            if record.is_potential_supporter then
    5.58 +              slot.put("&nbsp;")
    5.59 +              local label = _"You are potential supporter of this initiative"
    5.60 +              ui.image{
    5.61 +                attr = { alt = label, title = label },
    5.62 +                static = "icons/16/thumb_up.png"
    5.63 +              }
    5.64 +            end
    5.65 +            if record.is_initiator then
    5.66 +              slot.put("&nbsp;")
    5.67 +              local label = _"You are iniator of this initiative"
    5.68 +              ui.image{
    5.69 +                attr = { alt = label, title = label },
    5.70 +                static = "icons/16/user_edit.png"
    5.71 +              }
    5.72 +            end
    5.73            end
    5.74          }
    5.75  
    5.76 @@ -141,4 +187,14 @@
    5.77        end
    5.78      }
    5.79    end
    5.80 -}
    5.81 \ No newline at end of file
    5.82 +}
    5.83 +
    5.84 +if more_initiatives_count then
    5.85 +  ui.link{
    5.86 +    attr = { style = "font-size: 75%; font-style: italic;" },
    5.87 +    content = _("#{count} more initiatives", { count = more_initiatives_count }),
    5.88 +    module = "issue",
    5.89 +    view = "show",
    5.90 +    id = issue.id,
    5.91 +  }
    5.92 +end
     6.1 --- a/app/main/initiative/show.lua	Sun Jan 10 12:00:00 2010 +0100
     6.2 +++ b/app/main/initiative/show.lua	Fri Jan 22 12:00:00 2010 +0100
     6.3 @@ -151,18 +151,22 @@
     6.4        ui.tag{
     6.5          tag = "span",
     6.6          content = function()
     6.7 -          if initiative.discussion_url and #initiative.discussion_url > 0 then
     6.8 -            ui.link{
     6.9 -              attr = {
    6.10 -                class = "actions",
    6.11 -                target = "_blank",
    6.12 -                title = initiative.discussion_url
    6.13 -              },
    6.14 -              content = function()
    6.15 -                slot.put(encode.html(initiative.discussion_url))
    6.16 -              end,
    6.17 -              external = initiative.discussion_url
    6.18 -            }
    6.19 +          if initiative.discussion_url:find("^https?://") then
    6.20 +            if initiative.discussion_url and #initiative.discussion_url > 0 then
    6.21 +              ui.link{
    6.22 +                attr = {
    6.23 +                  class = "actions",
    6.24 +                  target = "_blank",
    6.25 +                  title = initiative.discussion_url
    6.26 +                },
    6.27 +                content = function()
    6.28 +                  slot.put(encode.html(initiative.discussion_url))
    6.29 +                end,
    6.30 +                external = initiative.discussion_url
    6.31 +              }
    6.32 +            end
    6.33 +          else
    6.34 +            slot.put(encode.html(initiative.discussion_url))
    6.35            end
    6.36            slot.put(" ")
    6.37            if initiator and initiator.accepted and not initiative.issue.half_frozen and not initiative.issue.closed and not initiative.revoked then
    6.38 @@ -248,7 +252,7 @@
    6.39      ui.container{
    6.40        attr = { class = "draft_updated_info" },
    6.41        content = function()
    6.42 -        slot.put("The draft of this initiative has been updated!")
    6.43 +        slot.put(_"The draft of this initiative has been updated!")
    6.44          slot.put(" ")
    6.45          ui.link{
    6.46            content = _"Show diff",
    6.47 @@ -358,18 +362,27 @@
    6.48    end
    6.49  }
    6.50  
    6.51 -local members_selector =  initiative:get_reference_selector("supporting_members_snapshot")
    6.52 +local members_selector = initiative:get_reference_selector("supporting_members_snapshot")
    6.53            :join("issue", nil, "issue.id = direct_supporter_snapshot.issue_id")
    6.54            :join("direct_interest_snapshot", nil, "direct_interest_snapshot.event = issue.latest_snapshot_event AND direct_interest_snapshot.issue_id = issue.id AND direct_interest_snapshot.member_id = member.id")
    6.55            :add_field("direct_interest_snapshot.weight")
    6.56            :add_where("direct_supporter_snapshot.event = issue.latest_snapshot_event")
    6.57            :add_where("direct_supporter_snapshot.satisfied")
    6.58  
    6.59 -local satisfied_supporter_count = members_selector:count()
    6.60 +local tmp = db:query("SELECT count(1) AS count, sum(weight) AS weight FROM (" .. tostring(members_selector) .. ") as subquery", "object")
    6.61 +local direct_satisfied_supporter_count = tmp.count
    6.62 +local indirect_satisfied_supporter_count = (tmp.weight or 0) - tmp.count
    6.63 +
    6.64 +local count_string
    6.65 +if indirect_satisfied_supporter_count > 0 then
    6.66 +  count_string = "(" .. tostring(direct_satisfied_supporter_count) .. "+" .. tostring(indirect_satisfied_supporter_count) .. ")"
    6.67 +else
    6.68 +  count_string = "(" .. tostring(direct_satisfied_supporter_count) .. ")"
    6.69 +end
    6.70  
    6.71  tabs[#tabs+1] = {
    6.72    name = "satisfied_supporter",
    6.73 -  label = _"Supporter" .. " (" .. tostring(satisfied_supporter_count) .. ")",
    6.74 +  label = _"Supporter" .. " " .. count_string,
    6.75    content = function()
    6.76      execute.view{
    6.77        module = "member",
    6.78 @@ -389,11 +402,20 @@
    6.79            :add_where("direct_supporter_snapshot.event = issue.latest_snapshot_event")
    6.80            :add_where("NOT direct_supporter_snapshot.satisfied")
    6.81  
    6.82 -local potential_supporter_count = members_selector:count()
    6.83 +local tmp = db:query("SELECT count(1) AS count, sum(weight) AS weight FROM (" .. tostring(members_selector) .. ") as subquery", "object")
    6.84 +local direct_potential_supporter_count = tmp.count
    6.85 +local indirect_potential_supporter_count = (tmp.weight or 0) - tmp.count
    6.86 +
    6.87 +local count_string
    6.88 +if indirect_potential_supporter_count > 0 then
    6.89 +  count_string = "(" .. tostring(direct_potential_supporter_count) .. "+" .. tostring(indirect_potential_supporter_count) .. ")"
    6.90 +else
    6.91 +  count_string = "(" .. tostring(direct_potential_supporter_count) .. ")"
    6.92 +end
    6.93  
    6.94  tabs[#tabs+1] = {
    6.95    name = "supporter",
    6.96 -  label = _"Potential supporter" .. " (" .. tostring(potential_supporter_count) .. ")",
    6.97 +  label = _"Potential supporter" .. " " .. count_string,
    6.98    content = function()
    6.99      execute.view{
   6.100        module = "member",
     7.1 --- a/app/main/issue/_list.lua	Sun Jan 10 12:00:00 2010 +0100
     7.2 +++ b/app/main/issue/_list.lua	Fri Jan 22 12:00:00 2010 +0100
     7.3 @@ -1,5 +1,9 @@
     7.4  local issues_selector = param.get("issues_selector", "table")
     7.5  
     7.6 +issues_selector
     7.7 +  :left_join("interest", "_interest", { "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id} )
     7.8 +  :add_field("(_interest.member_id NOTNULL)", "is_interested")
     7.9 +
    7.10  local ui_filter = ui.filter
    7.11  if param.get("filter", atom.boolean) == false then
    7.12    ui_filter = function(args) args.content() end
    7.13 @@ -150,6 +154,36 @@
    7.14                  end
    7.15                end
    7.16              },
    7.17 +            {
    7.18 +              type = "boolean",
    7.19 +              name = "supported",
    7.20 +              label = _"Supported",
    7.21 +              selector_modifier = function(selector, value)
    7.22 +                if value then
    7.23 +                  selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = ? LEFT JOIN opinion ON opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) WHERE initiative.issue_id = issue.id AND opinion.member_id ISNULL LIMIT 1)", app.session.member.id, app.session.member.id })
    7.24 +                end
    7.25 +              end
    7.26 +            },
    7.27 +            {
    7.28 +              type = "boolean",
    7.29 +              name = "potentially_supported",
    7.30 +              label = _"Potential supported",
    7.31 +              selector_modifier = function(selector, value)
    7.32 +                if value then
    7.33 +                  selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN supporter ON supporter.initiative_id = initiative.id AND supporter.member_id = ? JOIN opinion ON opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) WHERE initiative.issue_id = issue.id LIMIT 1)", app.session.member.id, app.session.member.id })
    7.34 +                end
    7.35 +              end
    7.36 +            },
    7.37 +            {
    7.38 +              type = "boolean",
    7.39 +              name = "initiated",
    7.40 +              label = _"Initiated",
    7.41 +              selector_modifier = function(selector, value)
    7.42 +                if value then
    7.43 +                  selector:add_where({ "EXISTS (SELECT 1 FROM initiative JOIN initiator ON initiator.initiative_id = initiative.id AND initiator.member_id = ? WHERE initiative.issue_id = issue.id)", app.session.member.id })
    7.44 +                end
    7.45 +              end
    7.46 +            },
    7.47            },
    7.48            content = function()
    7.49              local ui_order = ui.order
    7.50 @@ -215,6 +249,14 @@
    7.51                                }
    7.52                                slot.put("<br />")
    7.53                              end
    7.54 +                            if record.is_interested then
    7.55 +                              local label = _"You are interested in this issue",
    7.56 +                              ui.image{
    7.57 +                                attr = { alt = label, title = label },
    7.58 +                                static = "icons/16/eye.png"
    7.59 +                              }
    7.60 +                              slot.put("&nbsp;")
    7.61 +                            end
    7.62                              ui.link{
    7.63                                text = _("Issue ##{id}", { id = tostring(record.id) }),
    7.64                                module = "issue",
    7.65 @@ -265,7 +307,6 @@
    7.66                                  issue = record,
    7.67                                  initiatives_selector = initiatives_selector,
    7.68                                  highlight_string = highlight_string,
    7.69 -                                limit = 3,
    7.70                                  per_page = param.get("initiatives_per_page", atom.number),
    7.71                                  no_sort = param.get("initiatives_no_sort", atom.boolean)
    7.72                                }
     8.1 --- a/app/main/member/_show.lua	Sun Jan 10 12:00:00 2010 +0100
     8.2 +++ b/app/main/member/_show.lua	Fri Jan 22 12:00:00 2010 +0100
     8.3 @@ -73,7 +73,7 @@
     8.4                  ui.tag{
     8.5                    tag = "span",
     8.6                    content = function()
     8.7 -                    slot.put(encode.html_newlines(member.address))
     8.8 +                    slot.put(encode.html_newlines(html.encode(member.address)))
     8.9                    end
    8.10                  }
    8.11                end
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/app/main/timeline/_action/delete_filter.lua	Fri Jan 22 12:00:00 2010 +0100
     9.3 @@ -0,0 +1,3 @@
     9.4 +local timeline_filter = app.session.member:get_setting_map_by_key_and_subkey("timeline_filters", param.get("name"))
     9.5 +
     9.6 +timeline_filter:destroy()
     9.7 \ No newline at end of file
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/app/main/timeline/_action/save.lua	Fri Jan 22 12:00:00 2010 +0100
    10.3 @@ -0,0 +1,41 @@
    10.4 +local id = param.get("id", atom.number)
    10.5 +
    10.6 +local setting_key = "liquidfeedback_frontend_timeline_current_options"
    10.7 +local setting = Setting:by_pk(app.session.member.id, setting_key)
    10.8 +local options_string = setting.value
    10.9 +
   10.10 +local timeline_filter
   10.11 +
   10.12 +local subkey = param.get("name")
   10.13 +
   10.14 +setting_map = SettingMap:new()
   10.15 +setting_map.member_id = app.session.member.id
   10.16 +setting_map.key = "timeline_filters"
   10.17 +setting_map.subkey = subkey
   10.18 +setting_map.value = options_string
   10.19 +setting_map:save()
   10.20 +
   10.21 +local timeline_params = {}
   10.22 +if options_string then
   10.23 +  for event_ident, filter_idents in setting.value:gmatch("(%S+):(%S+)") do
   10.24 +    timeline_params["option_" .. event_ident] = true
   10.25 +    if filter_idents ~= "*" then
   10.26 +      for filter_ident in filter_idents:gmatch("([^\|]+)") do
   10.27 +        timeline_params["option_" .. event_ident .. "_" .. filter_ident] = true
   10.28 +      end
   10.29 +    end
   10.30 +  end
   10.31 +end
   10.32 +
   10.33 +local setting_key = "liquidfeedback_frontend_timeline_current_date"
   10.34 +local setting = Setting:by_pk(app.session.member.id, setting_key)
   10.35 +
   10.36 +if setting then
   10.37 +  timeline_params.date = setting.value
   10.38 +end
   10.39 +
   10.40 +request.redirect{
   10.41 +  module = "timeline",
   10.42 +  view = "index",
   10.43 +  params = timeline_params
   10.44 +}
   10.45 \ No newline at end of file
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/app/main/timeline/_action/update.lua	Fri Jan 22 12:00:00 2010 +0100
    11.3 @@ -0,0 +1,101 @@
    11.4 +execute.view{
    11.5 +  module = "timeline",
    11.6 +  view = "_constants"
    11.7 +}
    11.8 +
    11.9 +local options_string = param.get("options_string")
   11.10 +
   11.11 +if not options_string then
   11.12 +  local active_options = ""
   11.13 +  for event_ident, event_name in pairs(event_names) do
   11.14 +    if param.get("option_" .. event_ident, atom.boolean) then
   11.15 +      active_options = active_options .. event_ident .. ":"
   11.16 +      local filter_idents = {}
   11.17 +      for filter_ident, filter_name in pairs(filter_names) do
   11.18 +        if param.get("option_" .. event_ident .. "_" .. filter_ident, atom.boolean) then
   11.19 +          filter_idents[#filter_idents+1] = filter_ident
   11.20 +        end
   11.21 +      end
   11.22 +      if #filter_idents > 0 then
   11.23 +        active_options = active_options .. table.concat(filter_idents, "|") .. " "
   11.24 +      else
   11.25 +        active_options = active_options .. "* "
   11.26 +      end
   11.27 +    end
   11.28 +  end
   11.29 +  if #active_options > 0 then
   11.30 +    options_string = active_options
   11.31 +  end
   11.32 +end
   11.33 +
   11.34 +if not options_string then
   11.35 +  options_string = "issue_created:* issue_finished_after_voting:* issue_accepted:* issue_voting_started:* suggestion_created:* issue_canceled:* initiative_created:* issue_finished_without_voting:* draft_created:* initiative_revoked:* issue_half_frozen:* "
   11.36 +end
   11.37 +
   11.38 +local setting_key = "liquidfeedback_frontend_timeline_current_options"
   11.39 +local setting = Setting:by_pk(app.session.member.id, setting_key)
   11.40 +
   11.41 +if not setting or setting.value ~= options_string then
   11.42 +  if not setting then
   11.43 +    setting = Setting:new()
   11.44 +    setting.member_id = app.session.member_id
   11.45 +    setting.key = setting_key
   11.46 +  end
   11.47 +  if options_string then
   11.48 +    setting.value = options_string
   11.49 +    setting:save()
   11.50 +  end
   11.51 +end
   11.52 +
   11.53 +local date = param.get("date")
   11.54 +
   11.55 +if date and #date > 0 then
   11.56 +  local setting_key = "liquidfeedback_frontend_timeline_current_date"
   11.57 +  local setting = Setting:by_pk(app.session.member.id, setting_key)
   11.58 +  if not setting or setting.value ~= date then
   11.59 +    if not setting then
   11.60 +      setting = Setting:new()
   11.61 +      setting.member_id = app.session.member.id
   11.62 +      setting.key = setting_key
   11.63 +    end
   11.64 +    setting.value = date
   11.65 +    setting:save()
   11.66 +  end
   11.67 +end
   11.68 +
   11.69 +local setting_key = "liquidfeedback_frontend_timeline_current_options"
   11.70 +local setting = Setting:by_pk(app.session.member.id, setting_key)
   11.71 +
   11.72 +local timeline_params = {}
   11.73 +if setting and setting.value then
   11.74 +  for event_ident, filter_idents in setting.value:gmatch("(%S+):(%S+)") do
   11.75 +    timeline_params["option_" .. event_ident] = true
   11.76 +    if filter_idents ~= "*" then
   11.77 +      for filter_ident in filter_idents:gmatch("([^\|]+)") do
   11.78 +        timeline_params["option_" .. event_ident .. "_" .. filter_ident] = true
   11.79 +      end
   11.80 +    end
   11.81 +  end
   11.82 +end
   11.83 +
   11.84 +local setting_key = "liquidfeedback_frontend_timeline_current_date"
   11.85 +local setting = Setting:by_pk(app.session.member.id, setting_key)
   11.86 +
   11.87 +if setting then
   11.88 +  timeline_params.date = setting.value
   11.89 +end
   11.90 +
   11.91 +timeline_params.show_options = param.get("show_options", atom.boolean)
   11.92 +
   11.93 +if param.get("save", atom.boolean) then
   11.94 +  request.redirect{
   11.95 +    module = "timeline",
   11.96 +    view = "save_filter"
   11.97 +  }
   11.98 +else
   11.99 +  request.redirect{
  11.100 +    module = "timeline",
  11.101 +    view = "index",
  11.102 +    params = timeline_params
  11.103 +  }
  11.104 +end
  11.105 \ No newline at end of file
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/app/main/timeline/_constants.lua	Fri Jan 22 12:00:00 2010 +0100
    12.3 @@ -0,0 +1,31 @@
    12.4 +event_names = {
    12.5 +  issue_created                 = _"New issue",
    12.6 +  issue_canceled                = _"Issue canceled",
    12.7 +  issue_accepted                = _"Issue accepted",
    12.8 +  issue_half_frozen             = _"Issue frozen",
    12.9 +  issue_finished_without_voting = _"Issue finished without voting",
   12.10 +  issue_voting_started          = _"Voting started",
   12.11 +  issue_finished_after_voting   = _"Issue finished",
   12.12 +  initiative_created            = _"New initiative",
   12.13 +  initiative_revoked            = _"Initiative revoked",
   12.14 +  draft_created                 = _"New draft",
   12.15 +  suggestion_created            = _"New suggestion"
   12.16 +}
   12.17 +
   12.18 +filter_names = {
   12.19 +  contact = _"Saved as contact",
   12.20 +  interested = _"Interested",
   12.21 +  supporter = _"Supported",
   12.22 +  potential_supporter = _"Potential supported",
   12.23 +  initiator = _"Initiated",
   12.24 +  membership = _"Member of area"
   12.25 +}
   12.26 +
   12.27 +option_names = {}
   12.28 +for key, val in pairs(event_names) do
   12.29 +  option_names[key] = val
   12.30 +end
   12.31 +for key, val in pairs(filter_names) do
   12.32 +  option_names[key] = val
   12.33 +end
   12.34 +
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/app/main/timeline/_list.lua	Fri Jan 22 12:00:00 2010 +0100
    13.3 @@ -0,0 +1,124 @@
    13.4 +local timeline_selector = param.get("timeline_selector", "table")
    13.5 +local event_names = param.get("event_names", "table")
    13.6 +local initiatives_per_page = param.get("initiatives_per_page", atom.number) or 3
    13.7 +
    13.8 +ui.paginate{
    13.9 +  per_page = param.get("per_page", atom.number) or 25,
   13.10 +  selector = timeline_selector,
   13.11 +  content = function()
   13.12 +    local timelines = timeline_selector:exec()
   13.13 +    timelines:load("issue")
   13.14 +    timelines:load("initiative")
   13.15 +    timelines:load("member")
   13.16 +    ui.list{
   13.17 +      attr = { class = "nohover" },
   13.18 +      records = timelines,
   13.19 +      columns = {
   13.20 +        {
   13.21 +          field_attr = { style = "width: 10em;" },
   13.22 +          content = function(timeline)
   13.23 +            ui.field.text{
   13.24 +              attr = { style = "font-size: 75%; font-weight: bold; background-color: #ccc; display: block; margin-bottom: 1ex;"},
   13.25 +              value = format.time(timeline.occurrence)
   13.26 +            }
   13.27 +            ui.field.text{
   13.28 +              attr = { style = "font-size: 75%; font-weight: bold;"},
   13.29 +              value = event_names[timeline.event] or timeline.event
   13.30 +            }
   13.31 +          end
   13.32 +        },
   13.33 +        {
   13.34 +          content = function(timeline)
   13.35 +            local issue
   13.36 +            local initiative
   13.37 +            if timeline.issue then
   13.38 +              issue = timeline.issue
   13.39 +            elseif timeline.initiative then
   13.40 +              initiative = timeline.initiative
   13.41 +              issue = initiative.issue
   13.42 +            elseif timeline.draft then
   13.43 +              initiative = timeline.draft.initiative
   13.44 +              issue = initiative.issue
   13.45 +            elseif timeline.suggestion then
   13.46 +              initiative = timeline.suggestion.initiative
   13.47 +              issue = initiative.issue
   13.48 +            end
   13.49 +            if issue then
   13.50 +              if timeline.is_interested then
   13.51 +                local label = _"You are interested in this issue",
   13.52 +                ui.image{
   13.53 +                  attr = { alt = label, title = label, style = "float: left; margin-right: 0.5em;" },
   13.54 +                  static = "icons/16/eye.png"
   13.55 +                }
   13.56 +              end
   13.57 +              slot.put(" ")
   13.58 +              ui.tag{
   13.59 +                tag = "span",
   13.60 +                attr = { style = "font-size: 75%; font-weight: bold; background-color: #ccc; display: block; margin-bottom: 1ex;"},
   13.61 +                content = issue.area.name .. ", " .. _("Issue ##{id}", { id = issue.id })
   13.62 +              }
   13.63 +            else
   13.64 +              ui.tag{
   13.65 +                tag = "span",
   13.66 +                attr = { style = "font-size: 75%; background-color: #ccc; display: block; margin-bottom: 1ex;"},
   13.67 +                content = function() slot.put("&nbsp;") end
   13.68 +              }
   13.69 +            end
   13.70 +
   13.71 +            if timeline.member then
   13.72 +              execute.view{
   13.73 +                module = "member_image",
   13.74 +                view = "_show",
   13.75 +                params = {
   13.76 +                  member = timeline.member,
   13.77 +                  image_type = "avatar",
   13.78 +                  show_dummy = true
   13.79 +                }
   13.80 +              }
   13.81 +              ui.link{
   13.82 +                content = timeline.member.name,
   13.83 +                module = "member",
   13.84 +                view = "show",
   13.85 +                id = timeline.member.id
   13.86 +              }
   13.87 +            end
   13.88 +            if timeline.issue then
   13.89 +              local initiatives_selector = timeline.issue
   13.90 +                :get_reference_selector("initiatives")
   13.91 +              execute.view{
   13.92 +                module = "initiative",
   13.93 +                view = "_list",
   13.94 +                params = {
   13.95 +                  issue = timeline.issue,
   13.96 +                  initiatives_selector = initiatives_selector,
   13.97 +                  per_page = initiatives_per_page,
   13.98 +                  no_sort = true,
   13.99 +                  limit = initiatives_per_page
  13.100 +                }
  13.101 +              }
  13.102 +            elseif initiative then
  13.103 +              execute.view{
  13.104 +                module = "initiative",
  13.105 +                view = "_list",
  13.106 +                params = {
  13.107 +                  issue = initiative.issue,
  13.108 +                  initiatives_selector = Initiative:new_selector():add_where{ "initiative.id = ?", initiative.id },
  13.109 +                  per_page = initiatives_per_page,
  13.110 +                  no_sort = true
  13.111 +                }
  13.112 +              }
  13.113 +            end
  13.114 +            if timeline.suggestion then
  13.115 +              ui.link{
  13.116 +                module = "suggestion",
  13.117 +                view = "show",
  13.118 +                id = timeline.suggestion.id,
  13.119 +                content = timeline.suggestion.name
  13.120 +              }
  13.121 +            end
  13.122 +          end
  13.123 +        },
  13.124 +      }
  13.125 +    }
  13.126 +  end
  13.127 +}
  13.128 \ No newline at end of file
    14.1 --- a/app/main/timeline/index.lua	Sun Jan 10 12:00:00 2010 +0100
    14.2 +++ b/app/main/timeline/index.lua	Fri Jan 22 12:00:00 2010 +0100
    14.3 @@ -1,3 +1,12 @@
    14.4 +execute.view{
    14.5 +  module = "timeline",
    14.6 +  view = "_constants"
    14.7 +}
    14.8 +
    14.9 +local options_box_count = param.get("options_box_count", atom.number) or 1
   14.10 +if options_box_count > 10 then
   14.11 +  options_box_count = 10
   14.12 +end
   14.13  
   14.14  local function format_dow(dow)
   14.15    local dows = {
   14.16 @@ -11,141 +20,331 @@
   14.17    }
   14.18    return dows[dow+1]
   14.19  end
   14.20 +slot.put_into("title", _"Timeline")
   14.21  
   14.22 -slot.put_into("title", _"Global timeline")
   14.23 +slot.select("actions", function()
   14.24 +  local setting_key = "liquidfeedback_frontend_timeline_current_options"
   14.25 +  local setting = Setting:by_pk(app.session.member.id, setting_key)
   14.26 +  local current_options = ""
   14.27 +  if setting then
   14.28 +    current_options = setting.value
   14.29 +  end
   14.30 +  local setting_maps = app.session.member:get_setting_maps_by_key("timeline_filters")
   14.31 +  for i, setting_map in ipairs(setting_maps) do
   14.32 +    local active
   14.33 +    local options_string = setting_map.value
   14.34 +    local name = setting_map.subkey
   14.35 +    if options_string == current_options then
   14.36 +      active = true
   14.37 +    end
   14.38 +    timeline_params.date = param.get("date")
   14.39 +    ui.link{
   14.40 +      attr = { class = active and "action_active" or nil },
   14.41 +      content = function()
   14.42 +        ui.image{ static = "icons/16/time.png" }
   14.43 +        slot.put(encode.html(name))
   14.44 +      end,
   14.45 +      module = 'timeline',
   14.46 +      action = 'update',
   14.47 +      params = {
   14.48 +        options_string = options_string
   14.49 +      },
   14.50 +    }
   14.51 +  end
   14.52 +  if #setting_maps > 0 then
   14.53 +    ui.link{
   14.54 +      content = function()
   14.55 +        ui.image{ static = "icons/16/wrench.png" }
   14.56 +        slot.put(_"Manage filter")
   14.57 +      end,
   14.58 +      module = "timeline",
   14.59 +      view = "list_filter",
   14.60 +    }
   14.61 +  end
   14.62 +  ui.link{
   14.63 +    content = function()
   14.64 +      ui.image{ static = "icons/16/bullet_disk.png" }
   14.65 +      slot.put(_"Save current filter")
   14.66 +    end,
   14.67 +    module = "timeline",
   14.68 +    view = "save_filter",
   14.69 +    attr = { 
   14.70 +      onclick = "el=document.getElementById('timeline_save');el.checked=true;el.form.submit();return(false);"
   14.71 +    }
   14.72 +  }
   14.73 +end)
   14.74  
   14.75 +util.help("timeline.index", _"Timeline")
   14.76  
   14.77  ui.form{
   14.78 -  attr = { class = "vertical" },
   14.79    module = "timeline",
   14.80 -  view = "index",
   14.81 -  method = "get",
   14.82 +  action = "update",
   14.83    content = function()
   14.84 -    local tmp = db:query("select EXTRACT(DOW FROM date) as dow, date FROM (SELECT (now() - (to_char(days_before, '0') || ' days')::interval)::date as date from (select generate_series(0,7) as days_before) as series) as date; ")
   14.85 -    local today = tmp[1].date
   14.86 -    for i, record in ipairs(tmp) do
   14.87 -      local content
   14.88 -      if i == 1 then
   14.89 -        content = _"Today"
   14.90 -      elseif i == 2 then
   14.91 -        content = _"Yesterday"
   14.92 -      else
   14.93 -        content = format_dow(record.dow)
   14.94 -      end
   14.95 -      ui.link{
   14.96 -        content = content,
   14.97 -        attr = { onclick = "el = document.getElementById('timeline_search_date'); el.value = '" .. tostring(record.date) .. "'; el.form.submit(); return(false);" },
   14.98 -        module = "timeline",
   14.99 -        view = "index",
  14.100 -        params = { date = record.date }
  14.101 -      }
  14.102 -      slot.put(" ")
  14.103 +
  14.104 +
  14.105 +    ui.tag{
  14.106 +      tag = "label",
  14.107 +      attr = { style = "font-size: 130%;" },
  14.108 +      content = _"Date" .. ":"
  14.109 +    }
  14.110 +    slot.put(" ")
  14.111 +    local date = param.get("date")
  14.112 +    if not date or #date == 0 then
  14.113 +      date = tostring(db:query("select now()::date as date")[1].date)
  14.114      end
  14.115 -    ui.field.hidden{
  14.116 -      attr = { id = "timeline_search_date" },
  14.117 -      name = "date",
  14.118 -      value = param.get("date") or today
  14.119 +    ui.tag{
  14.120 +      tag = "input",
  14.121 +      attr = {
  14.122 +        type = "text",
  14.123 +        id = "timeline_search_date",
  14.124 +        style = "width: 10em;",
  14.125 +        onchange = "this.form.submit();",
  14.126 +        name = "date",
  14.127 +        value = date
  14.128 +      },
  14.129 +      content = function() end
  14.130 +    }
  14.131 +
  14.132 +    ui.script{ static = "gregor.js/gregor.js" }
  14.133 +    util.gregor("timeline_search_date", "document.getElementById('timeline_search_date').form.submit();")
  14.134 +
  14.135 +
  14.136 +    ui.link{
  14.137 +      attr = { style = "margin-left: 1em; font-size: 130%; font-weight: bold;", onclick = "document.getElementById('timeline_search_date').form.submit();return(false);" },
  14.138 +      content = function()
  14.139 +        ui.image{
  14.140 +          attr = { style = "margin-right: 0.25em;" },
  14.141 +          static = "icons/16/magnifier.png"
  14.142 +        }
  14.143 +        slot.put(_"Search")
  14.144 +      end,
  14.145 +      external = "#",
  14.146 +    }
  14.147 +    local show_options = param.get("show_options", atom.boolean)
  14.148 +    ui.link{
  14.149 +      attr = { style = "margin-left: 1em; font-size: 130%;", onclick = "el=document.getElementById('timeline_show_options');el.checked=" .. tostring(not show_options) .. ";el.form.submit();return(false);" },
  14.150 +      content = function()
  14.151 +        ui.image{
  14.152 +          attr = { style = "margin-right: 0.25em;" },
  14.153 +          static = "icons/16/text_list_bullets.png"
  14.154 +        }
  14.155 +        slot.put(not show_options and _"Show filter details" or _"Hide filter details")
  14.156 +      end,
  14.157 +      external = "#",
  14.158 +    }
  14.159 +
  14.160 +    ui.field.boolean{
  14.161 +      attr = { id = "timeline_show_options", style = "display: none;", onchange="this.form.submit();" },
  14.162 +      name = "show_options",
  14.163 +      value = param.get("show_options", atom.boolean)
  14.164 +    }
  14.165 +
  14.166 +    ui.field.boolean{
  14.167 +      attr = { id = "timeline_save", style = "display: none;", onchange="this.form.submit();" },
  14.168 +      name = "save",
  14.169 +      value = false
  14.170      }
  14.171 -    ui.field.select{
  14.172 -      attr = { onchange = "this.form.submit();" },
  14.173 -      name = "per_page",
  14.174 -      label = _"Issues per page",
  14.175 -      foreign_records = {
  14.176 -        { id = "10",  name = "10"   },
  14.177 -        { id = "25",  name = "25"   },
  14.178 -        { id = "50",  name = "50"   },
  14.179 -        { id = "100", name = "100"  },
  14.180 -        { id = "250", name = "250"  },
  14.181 -        { id = "all", name = _"All" },
  14.182 +
  14.183 +    ui.container{
  14.184 +      attr = { 
  14.185 +        id = "timeline_options_boxes",
  14.186 +        class = "vertical",
  14.187 +        style = not param.get("show_options", atom.boolean) and "display: none;" or nil
  14.188        },
  14.189 -      foreign_id = "id",
  14.190 -      foreign_name = "name",
  14.191 -      value = param.get("per_page")
  14.192 -    }
  14.193 -    local initiatives_per_page = param.get("initiatives_per_page", atom.integer) or 3
  14.194 +      content = function()
  14.195 +
  14.196 +        local function option_field(event_ident, filter_ident)
  14.197 +          local param_name
  14.198 +          if not filter_ident then
  14.199 +            param_name = "option_" .. event_ident
  14.200 +          else
  14.201 +            param_name = "option_" .. event_ident .. "_" .. filter_ident
  14.202 +          end
  14.203 +          local value = param.get(param_name, atom.boolean)
  14.204 +          ui.field.boolean{
  14.205 +            attr = { id = param_name },
  14.206 +            name = param_name,
  14.207 +            value = value,
  14.208 +          }
  14.209 +        end
  14.210 +
  14.211 +        local function filter_option_fields(event_ident, filter_idents)
  14.212 +
  14.213 +          for i, filter_ident in ipairs(filter_idents) do
  14.214 +              slot.put("<td>")
  14.215 +              option_field(event_ident, filter_ident)
  14.216 +              slot.put("</td><td><div class='ui_field_label label_right'>")
  14.217 +              ui.tag{
  14.218 +                attr = { ["for"] = "option_" .. event_ident .. "_" .. filter_ident },
  14.219 +                tag = "label",
  14.220 +                content = filter_names[filter_ident]
  14.221 +              }
  14.222 +              slot.put("</div></td>")
  14.223 +          end
  14.224 +
  14.225 +        end
  14.226  
  14.227 -    ui.field.select{
  14.228 -      attr = { onchange = "this.form.submit();" },
  14.229 -      name = "initiatives_per_page",
  14.230 -      label = _"Initiatives per page",
  14.231 -      foreign_records = {
  14.232 -        { id = 1,   name = "1"  },
  14.233 -        { id = 3,   name = "3"  },
  14.234 -        { id = 5,   name = "5"  },
  14.235 -        { id = 10,  name = "10" },
  14.236 -        { id = 25,  name = "25" },
  14.237 -        { id = 50,  name = "50" },
  14.238 -      },
  14.239 -      foreign_id = "id",
  14.240 -      foreign_name = "name",
  14.241 -      value = initiatives_per_page
  14.242 +        local event_groups = {
  14.243 +          {
  14.244 +            title = _"Issue events",
  14.245 +            event_idents = {
  14.246 +              "issue_created",
  14.247 +              "issue_canceled",
  14.248 +              "issue_accepted",
  14.249 +              "issue_half_frozen",
  14.250 +              "issue_finished_without_voting",
  14.251 +              "issue_voting_started",
  14.252 +              "issue_finished_after_voting",
  14.253 +            },
  14.254 +            filter_idents = {
  14.255 +              "membership",
  14.256 +              "interested"
  14.257 +            }
  14.258 +          },
  14.259 +          {
  14.260 +            title = _"Initiative events",
  14.261 +            event_idents = {
  14.262 +              "initiative_created",
  14.263 +              "initiative_revoked",
  14.264 +              "draft_created",
  14.265 +              "suggestion_created",
  14.266 +            },
  14.267 +            filter_idents = {
  14.268 +              "membership",
  14.269 +              "interested",
  14.270 +              "supporter",
  14.271 +              "potential_supporter",
  14.272 +              "initiator"
  14.273 +            }
  14.274 +          }
  14.275 +        }
  14.276 +
  14.277 +        slot.put("<br />")
  14.278 +
  14.279 +        slot.put("<table>")
  14.280 +
  14.281 +        for i_event_group, event_group in ipairs(event_groups) do
  14.282 +          slot.put("<tr>")
  14.283 +          slot.put("<th colspan='2'>")
  14.284 +          slot.put(event_group.title)
  14.285 +          slot.put("</th><th colspan='10'>")
  14.286 +          slot.put(_"Show only events which match... (or associtated)")
  14.287 +          slot.put("</th>")
  14.288 +          slot.put("</tr>")
  14.289 +          local event_idents = event_group.event_idents
  14.290 +          for i, event_ident in ipairs(event_idents) do
  14.291 +            slot.put("<tr><td>")
  14.292 +            option_field(event_ident)
  14.293 +            slot.put("</td><td><div class='ui_field_label label_right'>")
  14.294 +            ui.tag{
  14.295 +              attr = { ["for"] = "option_" .. event_ident },
  14.296 +              tag = "label",
  14.297 +              content = event_names[event_ident]
  14.298 +            }
  14.299 +            slot.put("</div></td>")
  14.300 +            filter_option_fields(event_ident, event_group.filter_idents)
  14.301 +            slot.put("</tr>")
  14.302 +          end
  14.303 +        end
  14.304 +
  14.305 +        slot.put("</table>")
  14.306 +
  14.307 +      end
  14.308      }
  14.309    end
  14.310  }
  14.311  
  14.312  local date = param.get("date")
  14.313 -if not date then
  14.314 +if not date or #date == 0 then
  14.315    date = "today"
  14.316  end
  14.317 -local issues_selector = db:new_selector()
  14.318 -issues_selector._class = Issue
  14.319 +
  14.320 +local timeline_selector
  14.321 +
  14.322 +for event, event_name in pairs(event_names) do
  14.323 +
  14.324 +  if param.get("option_" .. event, atom.boolean) then
  14.325 +
  14.326 +    local tmp = Timeline:new_selector()
  14.327 +      :add_where{ "occurrence::date = ?", date }
  14.328 +
  14.329 +      :left_join("draft", nil, "draft.id = timeline.draft_id")
  14.330 +      :left_join("suggestion", nil, "suggestion.id = timeline.suggestion_id")
  14.331 +      :left_join("initiative", nil, "initiative.id = timeline.initiative_id or initiative.id = draft.initiative_id or initiative.id = suggestion.initiative_id")
  14.332 +      :left_join("issue", nil, "issue.id = timeline.issue_id or issue.id = initiative.issue_id")
  14.333 +      :left_join("area", nil, "area.id = issue.area_id")
  14.334 +
  14.335 +      :left_join("interest", "_interest", { "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id} )
  14.336 +      :left_join("membership", "_membership", { "_membership.area_id = area.id AND _membership.member_id = ?", app.session.member.id} )
  14.337 +      :left_join("initiator", "_initiator", { "_initiator.initiative_id = initiative.id AND _initiator.member_id = ?", app.session.member.id} )
  14.338 +      :left_join("supporter", "_supporter", { "_supporter.initiative_id = initiative.id AND _supporter.member_id = ?", app.session.member.id} )
  14.339 +
  14.340 +      :add_field("(_interest.member_id NOTNULL)", "is_interested")
  14.341 +      :add_field("(_initiator.member_id NOTNULL)", "is_initiator")
  14.342 +      :add_field({"(_supporter.member_id NOTNULL) AND NOT EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1)", app.session.member.id }, "is_supporter")
  14.343 +      :add_field({"EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1)", app.session.member.id }, "is_potential_supporter")
  14.344 +  --    :left_join("member", nil, "member.id = timeline.member_id")
  14.345 +
  14.346 +    tmp:add_where{ "event = ?", event }
  14.347 +
  14.348 +    local filters = {}
  14.349 +    if param.get("option_" .. event .. "_membership", atom.boolean) then
  14.350 +      filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR _membership.member_id NOTNULL"
  14.351 +    end
  14.352 +
  14.353 +    if param.get("option_" .. event .. "_supporter", atom.boolean) then
  14.354 +      filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR ((_supporter.member_id NOTNULL) AND NOT EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1))"
  14.355 +    end
  14.356 +
  14.357 +    if param.get("option_" .. event .. "_potential_supporter", atom.boolean) then
  14.358 +      filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR ((_supporter.member_id NOTNULL) AND EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1))"
  14.359 +    end
  14.360  
  14.361 -issues_selector
  14.362 -  :add_field("*")
  14.363 -  :add_where{ "sort::date = ?", date }
  14.364 -  :add_from{ "($) as issue", {
  14.365 -    Issue:new_selector()
  14.366 -      :add_field("''", "old_state")
  14.367 -      :add_field("'new'", "new_state")
  14.368 -      :add_field("created", "sort")
  14.369 -    :union(Issue:new_selector()
  14.370 -      :add_field("'new'", "old_state")
  14.371 -      :add_field("'accepted'", "new_state")
  14.372 -      :add_field("accepted", "sort")
  14.373 -      :add_where("accepted NOTNULL")
  14.374 -    ):union(Issue:new_selector()
  14.375 -      :add_field("'accepted'", "old_state")
  14.376 -      :add_field("'frozen'", "new_state")
  14.377 -      :add_field("half_frozen", "sort")
  14.378 -      :add_where("half_frozen NOTNULL")
  14.379 -    ):union(Issue:new_selector()
  14.380 -      :add_field("'frozen'", "old_state")
  14.381 -      :add_field("'voting'", "new_state")
  14.382 -      :add_field("fully_frozen", "sort")
  14.383 -      :add_where("fully_frozen NOTNULL")
  14.384 -    ):union(Issue:new_selector()
  14.385 -      :add_field("'new'", "old_state")
  14.386 -      :add_field("'cancelled'", "new_state")
  14.387 -      :add_field("closed", "sort")
  14.388 -      :add_where("closed NOTNULL AND accepted ISNULL")
  14.389 -    ):union(Issue:new_selector()
  14.390 -      :add_field("'accepted'", "old_state")
  14.391 -      :add_field("'cancelled'", "new_state")
  14.392 -      :add_field("closed", "sort")
  14.393 -      :add_where("closed NOTNULL AND half_frozen ISNULL AND accepted NOTNULL")
  14.394 -    ):union(Issue:new_selector()
  14.395 -      :add_field("'frozen'", "old_state")
  14.396 -      :add_field("'cancelled'", "new_state")
  14.397 -      :add_field("closed", "sort")
  14.398 -      :add_where("closed NOTNULL AND fully_frozen ISNULL AND half_frozen NOTNULL")
  14.399 -    ):union(Issue:new_selector()
  14.400 -      :add_field("'voting'", "old_state")
  14.401 -      :add_field("'finished'", "new_state")
  14.402 -      :add_field("closed", "sort")
  14.403 -      :add_where("closed NOTNULL AND fully_frozen NOTNULL AND half_frozen ISNULL")
  14.404 -    )
  14.405 +    if param.get("option_" .. event .. "_interested", atom.boolean) then
  14.406 +      filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR _interest.member_id NOTNULL"
  14.407 +    end
  14.408 +
  14.409 +    if param.get("option_" .. event .. "_initiator", atom.boolean) then
  14.410 +      filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR _initiator.member_id NOTNULL"
  14.411 +    end
  14.412 +
  14.413 +    if #filters > 0 then
  14.414 +      local filter_string = "(" .. table.concat(filters, ") OR (") .. ")"
  14.415 +      tmp:add_where{ filter_string, app.session.member.id }
  14.416 +    end
  14.417 +  
  14.418 +    if not timeline_selector then
  14.419 +      timeline_selector = tmp
  14.420 +    else
  14.421 +      timeline_selector:union_all(tmp)
  14.422 +    end
  14.423 +  end
  14.424 +end
  14.425 +
  14.426 +if timeline_selector then
  14.427 +  
  14.428 +  local initiatives_per_page = param.get("initiatives_per_page", atom.number)
  14.429 +  
  14.430 +  local outer_timeline_selector = db:new_selector()
  14.431 +  outer_timeline_selector._class = Timeline
  14.432 +  outer_timeline_selector:add_field{ "timeline.*" }
  14.433 +  outer_timeline_selector:from({"($)", { timeline_selector }}, "timeline" )
  14.434 +  outer_timeline_selector:add_order_by("occurrence DESC")
  14.435 +  
  14.436 +  slot.put("<br />")
  14.437 +  execute.view{
  14.438 +    module = "timeline",
  14.439 +    view = "_list",
  14.440 +    params = {
  14.441 +      timeline_selector = outer_timeline_selector,
  14.442 +      per_page = param.get("per_page", atom.number),
  14.443 +      event_names = event_names,
  14.444 +      initiatives_per_page = initiatives_per_page
  14.445 +    }
  14.446    }
  14.447 -}
  14.448 +
  14.449 +else
  14.450  
  14.451 -execute.view{
  14.452 -  module = "issue",
  14.453 -  view = "_list",
  14.454 -  params = {
  14.455 -    issues_selector = issues_selector,
  14.456 -    initiatives_per_page = param.get("initiatives_per_page", atom.number),
  14.457 -    initiatives_no_sort = true,
  14.458 -    no_filter = true,
  14.459 -    no_sort = true,
  14.460 -    per_page = param.get("per_page"),
  14.461 -  }
  14.462 -}
  14.463 +  slot.put(_"No events selected to list")
  14.464 +
  14.465 +end
  14.466 \ No newline at end of file
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/app/main/timeline/list_filter.lua	Fri Jan 22 12:00:00 2010 +0100
    15.3 @@ -0,0 +1,38 @@
    15.4 +slot.put_into("title", _"Manage timeline filters")
    15.5 +
    15.6 +slot.select("actions", function()
    15.7 +  ui.link{
    15.8 +    content = function()
    15.9 +        ui.image{ static = "icons/16/cancel.png" }
   15.10 +        slot.put(_"Back to timeline")
   15.11 +    end,
   15.12 +    module = "timeline",
   15.13 +    action = "update"
   15.14 +  }
   15.15 +end)
   15.16 +
   15.17 +local timeline_filters = app.session.member:get_setting_maps_by_key("timeline_filters")
   15.18 +
   15.19 +ui.list{
   15.20 +  records = timeline_filters,
   15.21 +  columns = {
   15.22 +    {
   15.23 +      name = "subkey"
   15.24 +    },
   15.25 +    {
   15.26 +      content = function(timeline_filter)
   15.27 +        ui.link{
   15.28 +          attr = { class = "action" },
   15.29 +          content = function()
   15.30 +              slot.put(_"Delete filter")
   15.31 +          end,
   15.32 +          module = "timeline",
   15.33 +          action = "delete_filter",
   15.34 +          params = { 
   15.35 +            name = timeline_filter.subkey
   15.36 +          }
   15.37 +        }
   15.38 +      end
   15.39 +    }
   15.40 +  }
   15.41 +}
   15.42 \ No newline at end of file
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/app/main/timeline/save_filter.lua	Fri Jan 22 12:00:00 2010 +0100
    16.3 @@ -0,0 +1,27 @@
    16.4 +slot.put_into("title", _"Save timeline filters")
    16.5 +
    16.6 +slot.select("actions", function()
    16.7 +  ui.link{
    16.8 +    content = function()
    16.9 +        ui.image{ static = "icons/16/cancel.png" }
   16.10 +        slot.put(_"Cancel")
   16.11 +    end,
   16.12 +    module = "timeline",
   16.13 +    view = "index"
   16.14 +  }
   16.15 +end)
   16.16 +
   16.17 +ui.form{
   16.18 +  attr = { class = "vertical" },
   16.19 +  module = "timeline",
   16.20 +  action = "save",
   16.21 +  content = function()
   16.22 +    ui.field.text{
   16.23 +      label = _"Name",
   16.24 +      name = "name",
   16.25 +    }
   16.26 +    ui.submit{
   16.27 +      text = _"Save"
   16.28 +    }
   16.29 +  end
   16.30 +}
    17.1 --- a/app/main/vote/list.lua	Sun Jan 10 12:00:00 2010 +0100
    17.2 +++ b/app/main/vote/list.lua	Fri Jan 22 12:00:00 2010 +0100
    17.3 @@ -109,7 +109,9 @@
    17.4                      id = "entry_" .. tostring(initiative.id)
    17.5                    },
    17.6                    content = function()
    17.7 -                    local initiators = initiative.initiating_members
    17.8 +                    local initiators_selector = initiative:get_reference_selector("initiating_members")
    17.9 +                      :add_where("accepted")
   17.10 +                    local initiators = initiators_selector:exec()
   17.11                      local initiator_names = {}
   17.12                      for i, initiator in ipairs(initiators) do
   17.13                        initiator_names[#initiator_names+1] = initiator.name
    18.1 --- a/app/main/vote/show_incoming.lua	Sun Jan 10 12:00:00 2010 +0100
    18.2 +++ b/app/main/vote/show_incoming.lua	Fri Jan 22 12:00:00 2010 +0100
    18.3 @@ -6,14 +6,14 @@
    18.4    :join("delegating_voter", nil, "delegating_voter.member_id = member.id")
    18.5    :add_where{ "delegating_voter.issue_id = ?", issue.id }
    18.6    :add_where{ "delegating_voter.delegate_member_ids[1] = ?", member.id }
    18.7 -  :add_field{ "delegating_voter.weight" }
    18.8 +  :add_field("delegating_voter.weight", "voter_weight")
    18.9  
   18.10  execute.view{
   18.11    module = "member",
   18.12    view = "_list",
   18.13 -  params = { 
   18.14 +  params = {
   18.15      members_selector = members_selector,
   18.16 -    issue = issue,
   18.17 +    initiative = initiative,
   18.18      trustee = member
   18.19    }
   18.20  }
   18.21 \ No newline at end of file
    19.1 --- a/config/default.lua	Sun Jan 10 12:00:00 2010 +0100
    19.2 +++ b/config/default.lua	Fri Jan 22 12:00:00 2010 +0100
    19.3 @@ -1,5 +1,5 @@
    19.4  config.app_name = "LiquidFeedback"
    19.5 -config.app_version = "beta6"
    19.6 +config.app_version = "beta7"
    19.7  
    19.8  config.app_title = config.app_name .. " (" .. request.get_config_name() .. " environment)"
    19.9  
    20.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    20.2 +++ b/env/util/gregor.lua	Fri Jan 22 12:00:00 2010 +0100
    20.3 @@ -0,0 +1,11 @@
    20.4 +function util.gregor(el_id)
    20.5 +  ui.script{ script =
    20.6 +       'gregor_addGui({' ..
    20.7 +          'element_id: "' .. el_id .. '",' ..
    20.8 +          'month_names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],' ..
    20.9 +          'weekday_names: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],' ..
   20.10 +          'week_mode: "iso",' ..
   20.11 +          'week_numbers: "left",' ..
   20.12 +        '});'
   20.13 +  }
   20.14 +end
   20.15 \ No newline at end of file
    21.1 --- a/locale/help/initiative.revoke.de.txt	Sun Jan 10 12:00:00 2010 +0100
    21.2 +++ b/locale/help/initiative.revoke.de.txt	Fri Jan 22 12:00:00 2010 +0100
    21.3 @@ -1,2 +1,2 @@
    21.4  =Initiative zurückziehen=
    21.5 -Du kannst diese Initiative zurückziehen. Dies kann nicht rückgängig gemacht werden. Natürlich hast du jederzeit die Möglichkeit, eine neue Initiative zu starten. Den Unterstützern kannst du eine alternative Initiative empfehlen.
    21.6 +Du kannst diese Initiative zurückziehen. Dies kann nicht rückgängig gemacht werden. Natürlich hast du jederzeit die Möglichkeit, eine neue Initiative zu starten. Den Unterstützern kannst du eine alternative Initiative empfehlen. Angeboten werden dir hierfür alle von dir unterstützten Initiativen.
    22.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    22.2 +++ b/locale/help/timeline.index.de.txt	Fri Jan 22 12:00:00 2010 +0100
    22.3 @@ -0,0 +1,4 @@
    22.4 +=Zeitachse=
    22.5 +Hier kannst du dich über Ereignisse im System informieren. Über Filter-Einstellungen kannst du steuern, welche  Ereignisarten dir angezeigt werden sollen. Du kannst die Anzeige für einzelne Ereignisarten auch von weiteren Bedingungen abhängig machen. Um zum Beispiel alle Themen anzuzeigen, die nach Erreichen des Unterstützerquorums in den Status ,,Diskussion'' gelangen, wählst du das Themen-Ereignis ,,Thema akzeptiert'' und kannst zum Beispiel zusätzlich festlegen, dass diese Ereignisse nur dann angezeigt werden sollen, wenn du Mitglied des jeweiligen Themenbereichs bist. 
    22.6 +=Filter speichern=
    22.7 +Du kannst Filtereinstellungen unter einem Namen abspeichern und die jeweilige Abfrage dann mit einem Klick ausführen. Wenn du kein anderes Datum auswählst, beziehen sich Abfragen standardmäßig auf heute.
    24.1 --- a/locale/translations.de.lua	Sun Jan 10 12:00:00 2010 +0100
    24.2 +++ b/locale/translations.de.lua	Fri Jan 22 12:00:00 2010 +0100
    24.3 @@ -1,5 +1,6 @@
    24.4  #!/usr/bin/env lua
    24.5  return {
    24.6 +["#{count} more initiatives"] = "#{count} weitere Initiativen";
    24.7  ["#{interested_issues_to_vote_count} issue(s) you are interested in"] = "#{interested_issues_to_vote_count} Themen, die Dich interessieren";
    24.8  ["#{issues_to_vote_count} issue(s)"] = "#{issues_to_vote_count} Themen";
    24.9  ["#{number} Image(s) has been deleted"] = "Es wurde(n) #{number} Bild(er) gelöscht";
   24.10 @@ -25,7 +26,6 @@
   24.11  ["Administrator"] = "Administrator";
   24.12  ["Admission time"] = "Zeit für die Zulassung";
   24.13  ["Admitted"] = "zugelassen";
   24.14 -["All"] = "Alle";
   24.15  ["Any"] = "Alle";
   24.16  ["Are you sure?"] = "Sicher?";
   24.17  ["Area"] = "Themenbereich";
   24.18 @@ -40,6 +40,7 @@
   24.19  ["Autoreject is on."] = "Auto-Ablehnen ist an";
   24.20  ["Avatar"] = "Avatar";
   24.21  ["Back"] = "Zurück";
   24.22 +["Back to timeline"] = "Zurück zur Zeitachse";
   24.23  ["Become a member"] = "Mitglied werden";
   24.24  ["Birthday"] = "Geburtstag";
   24.25  ["Can't remove last initiator"] = "Der letzte Initiator kann nicht entfernt werden";
   24.26 @@ -78,8 +79,10 @@
   24.27  ["Created at"] = "Erzeugt am/um";
   24.28  ["Current draft"] = "Aktueller Entwurf";
   24.29  ["Current votings in areas you are member of and issues you are interested in:"] = "Jetzt laufende Abstimmungen zu Themen aus Deinen Themenbereichen oder solchen an denen Du interessiert bist:";
   24.30 +["Date"] = "Datum";
   24.31  ["Degree"] = "Grad";
   24.32  ["Delegations"] = "Delegationen";
   24.33 +["Delete filter"] = "Filter löschen";
   24.34  ["Description"] = "Beschreibung";
   24.35  ["Details"] = "Details";
   24.36  ["Developer features"] = "Entwicklerfunktionen";
   24.37 @@ -100,6 +103,7 @@
   24.38  ["Edit initiative"] = "Initiative bearbeiten";
   24.39  ["Edit my page"] = "Meine Seite bearbeiten";
   24.40  ["Edit my profile"] = "Mein Profil bearbeiten";
   24.41 +["Edit timeline filter"] = "Zeitachsen-Filter bearbeiten";
   24.42  ["Email address"] = "E-Mail-Adresse";
   24.43  ["Email address confirmation"] = "Bestätigung der E-Mail-Adresse";
   24.44  ["Email address is confirmed now"] = "E-Mail-Adresse ist jetzt bestätigt";
   24.45 @@ -117,11 +121,11 @@
   24.46  ["Fully frozen at"] = "Ganz eingefroren am/um";
   24.47  ["Global delegation"] = "Globale Delegation";
   24.48  ["Global delegation active"] = "Globale Delegation aktiv";
   24.49 -["Global timeline"] = "Globale Zeitlinie";
   24.50  ["Half frozen at"] = "Halb eingefroren am/um";
   24.51  ["Hello "] = "Hallo ";
   24.52  ["Help for: #{text}"] = "Hilfe zu: #{text}";
   24.53  ["Hide"] = "Verstecken";
   24.54 +["Hide filter details"] = "Filter-Details verstecken";
   24.55  ["Hide this help message"] = "Diesen Hilfetext ausblenden";
   24.56  ["Home"] = "Startseite";
   24.57  ["I accept the terms of use by checking the following checkbox:"] = "Ich akzeptiere die Nutzungsbedingungen durch Auswahl der folgenden Ankreuzbox:";
   24.58 @@ -131,14 +135,16 @@
   24.59  ["Images"] = "Bilder";
   24.60  ["In discussion"] = "In Diskussion";
   24.61  ["Incoming delegations"] = "Eingehende Delegationen";
   24.62 +["Initiated"] = "Initiert";
   24.63  ["Initiated initiatives"] = "Initierte Initiativen";
   24.64 +["Initiative events"] = "Initiativen-Ereignisse";
   24.65  ["Initiative is revoked now"] = "Initiative ist jetzt zurückgezogen";
   24.66  ["Initiative quorum"] = "Quorum Inititive";
   24.67 +["Initiative revoked"] = "Initiative zurückgezogen";
   24.68  ["Initiative successfully created"] = "Initiative erfolgreich erzeugt";
   24.69  ["Initiative successfully updated"] = "Initiative erfolgreich aktualisiert";
   24.70  ["Initiative: '#{name}'"] = "Initiative: '#{name}'";
   24.71  ["Initiatives"] = "Initiativen";
   24.72 -["Initiatives per page"] = "Initiativen je Seite";
   24.73  ["Initiatives that invited you to become initiator:"] = "Initiative, die Dich eingeladen haben, Initiator zu werden:";
   24.74  ["Initiator"] = "Initiator";
   24.75  ["Initiators"] = "Initiatoren";
   24.76 @@ -154,16 +160,20 @@
   24.77  ["Invite code"] = "Invite-Code";
   24.78  ["Invite initiator"] = "Initiator einladen";
   24.79  ["Invited"] = "Eingeladen";
   24.80 -["Inviting initiator"] = "Initiatoren einladen";
   24.81  ["Issue"] = "Thema";
   24.82  ["Issue ##{id}"] = "Thema ##{id}";
   24.83  ["Issue ##{id} (#{policy_name})"] = "Thema ##{id} (#{policy_name})";
   24.84 +["Issue accepted"] = "Thema akzeptiert";
   24.85 +["Issue canceled"] = "Thema abgebrochen";
   24.86  ["Issue delegation"] = "Issue-Delegation";
   24.87  ["Issue delegation active"] = "Delegation für Thema aktiv";
   24.88 +["Issue events"] = "Themen-Ergeignisse";
   24.89 +["Issue finished"] = "Thema abgeschlossen";
   24.90 +["Issue finished without voting"] = "Thema ohne Abstimmung abgeschlossen";
   24.91 +["Issue frozen"] = "Thema eingefroren";
   24.92  ["Issue policy"] = "Regelwerk für Thema";
   24.93  ["Issue quorum"] = "Quorum Thema";
   24.94  ["Issues"] = "Themen";
   24.95 -["Issues per page"] = "Themen je Seite";
   24.96  ["JavaScript is disabled or not available."] = "JavaScript ist abgeschaltet oder nicht verfügbar.";
   24.97  ["Last author"] = "Letzter Autor";
   24.98  ["Last snapshot:"] = "Letzte Auszählung:";
   24.99 @@ -175,6 +185,8 @@
  24.100  ["Login successful!"] = "Anmeldung erfolgreich";
  24.101  ["Logout"] = "Abmelden";
  24.102  ["Logout successful"] = "Abmeldung erfolgreich";
  24.103 +["Manage filter"] = "Filter verwalten";
  24.104 +["Manage timeline filters"] = "Zeitachsen-Filter verwalten";
  24.105  ["Mark suggestion as implemented and express dissatisfaction"] = "Anregung als umgesetzt markieren und Unzufriedenheit ausdrücken";
  24.106  ["Mark suggestion as implemented and express satisfaction"] = "Anregung als umgesetzt markieren und Zufriedenheit ausdrücken";
  24.107  ["Mark suggestion as not implemented and express dissatisfaction"] = "Anregung als nicht umgesetzt markieren und Unzufriedenheit ausdrücken";
  24.108 @@ -191,6 +203,7 @@
  24.109  ["Member list"] = "Mitgliederliste";
  24.110  ["Member name"] = "Mitglied Name";
  24.111  ["Member name history for '#{name}'"] = "Namenshistorie für '#{name}'";
  24.112 +["Member of area"] = "Mitglied des Themenbereichs";
  24.113  ["Member page"] = "Mitgliederseite";
  24.114  ["Member successfully registered"] = "Mitglied erfolgreich registriert";
  24.115  ["Member successfully updated"] = "Mitglied erfolgreich aktualisert";
  24.116 @@ -207,16 +220,21 @@
  24.117  ["My opinion"] = "Meine Meinung";
  24.118  ["Name"] = "Name";
  24.119  ["New"] = "Neu";
  24.120 +["New draft"] = "Neuer Entwurf";
  24.121  ["New draft has been added to initiative"] = "Neuer Entwurf wurde der Initiative hinzugefügt";
  24.122  ["New draft revision"] = "Neue Revision des Entwurfs";
  24.123 +["New initiative"] = "Neue Initiative";
  24.124 +["New issue"] = "Neues Thema";
  24.125  ["New password"] = "Neues Kennwort";
  24.126  ["New passwords does not match."] = "Du hast nicht zweimal das gleiche Kennwort eingegeben";
  24.127  ["New passwords is too short."] = "Das neue Kennwort ist zu kurz";
  24.128 +["New suggestion"] = "Neue Anregung";
  24.129  ["Newest"] = "Neueste";
  24.130  ["Next state"] = "Nächster Zustand";
  24.131  ["No"] = "Nein";
  24.132  ["No changes to your images were made"] = "An Deinen Bildern wurde nichts geändert";
  24.133  ["No delegation"] = "Keine Delegation";
  24.134 +["No events selected to list"] = "Keine Ereignisse ausgewählt";
  24.135  ["No membership at all"] = "Gar keine Mitgliedschaft";
  24.136  ["No support at all"] = "Gar keine Unterstützung";
  24.137  ["Not a member"] = "Kein Mitglied";
  24.138 @@ -262,6 +280,7 @@
  24.139  ["Population"] = "Grundgesamtheit";
  24.140  ["Posts"] = "Ämter";
  24.141  ["Potential support"] = "Potentielle Unterstützung";
  24.142 +["Potential supported"] = "Potentiell unterstützt";
  24.143  ["Potential supporter"] = "Potentielle Unterstützer";
  24.144  ["Profession"] = "Beruf";
  24.145  ["Profile"] = "Profil";
  24.146 @@ -284,7 +303,10 @@
  24.147  ["Remove my interest"] = "Interesse abmelden";
  24.148  ["Remove my membership"] = "Mitgliedschaft aufgeben";
  24.149  ["Remove my support from this initiative"] = "Meine Unterstützung der Initiative entziehen";
  24.150 +["Rename"] = "Umbenennen";
  24.151 +["Rename filter"] = "Filter umbenennen";
  24.152  ["Repeat new password"] = "Neues Kennwort wiederholen";
  24.153 +["Replace filter"] = "Filter ersetzen";
  24.154  ["Request password reset link"] = "Link zum Rücksetzen des Kennworts anfordern";
  24.155  ["Reset code"] = "Rücksetzcode";
  24.156  ["Reset code is invalid!"] = "Rücksetzcode ist ungültig";
  24.157 @@ -295,12 +317,16 @@
  24.158  ["Revoked at"] = "Zurückgezogen am/um";
  24.159  ["Saturday"] = "Samstag";
  24.160  ["Save"] = "Speichern";
  24.161 -["Saved as contact"] = "Als Kontakt speichern";
  24.162 +["Save as new filter"] = "Als neuen Filter speichern";
  24.163 +["Save current filter"] = "Aktuellen Filter speichern";
  24.164 +["Save timeline filters"] = "Zeitachsen-Filter speichern";
  24.165 +["Saved as contact"] = "Als Kontakt gespeichert";
  24.166  ["Search"] = "Suchen";
  24.167  ["Search initiatives"] = "Suche Initiativen";
  24.168  ["Search issues"] = "Suche Themen";
  24.169  ["Search members"] = "Suche Mitglieder";
  24.170  ["Search results for: '#{search}'"] = "Suchergebnisse für: '#{search}'";
  24.171 +["Select filter to replace"] = "Wähle zu ersetzenden Filter";
  24.172  ["Set URL"] = "URL setzen";
  24.173  ["Set area delegation"] = "Delegation für Themengebiet festlegen";
  24.174  ["Set autoreject"] = "Auto-Ablehnen anschalten";
  24.175 @@ -316,9 +342,11 @@
  24.176  ["Show areas in use"] = "Zeige verwendete Themenbereiche";
  24.177  ["Show areas not in use"] = "Zeige nicht verwendente Themenbereiche";
  24.178  ["Show diff"] = "Änderungen anzeigen";
  24.179 +["Show filter details"] = "Zeige Filter-Details";
  24.180  ["Show locked members"] = "Zeige gesperrte Mitglieder";
  24.181  ["Show member"] = "Mitglied anzeigen";
  24.182  ["Show name history"] = "Namenshistorie zeigen";
  24.183 +["Show only events which match... (or associtated)"] = "Zeige nur Ereignisse welche folgendes erfüllen... (oder-verknüpft)";
  24.184  ["Software"] = "Software";
  24.185  ["Some JavaScript based functions (voting in particular) will not work.\nFor this beta, please use a current version of Firefox, Safari, Opera(?), Konqueror or another (more) standard compliant browser.\nAlternative access without JavaScript will be available soon."] = "Einige auf JavaScript basierende Funktionen (insbesondere der Abstimmung) sind nicht benutzbar.\nFür diese Beta verwende bitte eine aktuelle Version von Firefox, Safari, Opera(?), Konqueror oder einen anderen (mehr) den Standards entsprechenden Browser.\nEin alternativer Zugriff ohne JavaScript wird bald zur Verfügung stehen.";
  24.186  ["Sorry, but there is not confirmed email address for your account. Please contact the administrator or support."] = "Sorry, aber für diesen Account ist keine bestätigte E-Mail-Adresse hinterlegt. Bitte wende Dich an den Administrator oder den Support.";
  24.187 @@ -344,10 +372,12 @@
  24.188  ["Sunday"] = "Sonntag";
  24.189  ["Support"] = "Unterstützung";
  24.190  ["Support this initiative"] = "Diese Initiative unterstützen";
  24.191 +["Supported"] = "Unterstützt";
  24.192  ["Supported initiatives"] = "Unterstützte Initiativen";
  24.193  ["Supporter"] = "Unterstützer";
  24.194  ["Terms accepted"] = "Bedingungen akzeptiert";
  24.195  ["The code you've entered is invalid"] = "Der Code, den Du eingeben hast, ist nicht gültig!";
  24.196 +["The draft of this initiative has been updated!"] = "Der Entwurfstext der Initiative wurde aktualisiert!";
  24.197  ["The drafts do not differ"] = "Die Entwürfe unterscheiden sich nicht";
  24.198  ["The initiators suggest to support the following initiative:"] = "Die Initiatoren empfehlen folgende Initiative zu unterstützen:";
  24.199  ["This initiative has been revoked at #{revoked}"] = "Diese Initiative wurde am/um #{revoked} zurückgezogen";
  24.200 @@ -369,8 +399,8 @@
  24.201  ["This username is too short!"] = "Dieser Benutzername ist zu kurz!";
  24.202  ["Thursday"] = "Donnerstag";
  24.203  ["Time left"] = "Restzeit";
  24.204 +["Timeline"] = "Zeitachse";
  24.205  ["Title (80 chars max)"] = "Title (max. 80 Zeichen)";
  24.206 -["Today"] = "Heute";
  24.207  ["Traditional wiki syntax"] = "Traditionaller Wiki-Syntax";
  24.208  ["Trustee"] = "Bevollmächtigter";
  24.209  ["Tuesday"] = "Dienstag";
  24.210 @@ -393,19 +423,23 @@
  24.211  ["Voting has not started yet."] = "Die Abstimmung hat noch nicht begonnen.";
  24.212  ["Voting proposal"] = "Abstimmungsvorlage";
  24.213  ["Voting requests"] = "Abstimmanträge";
  24.214 +["Voting started"] = "Abstimmung begonnen";
  24.215  ["Voting time"] = "Zeit für die Abstimmung";
  24.216  ["Website"] = "Webseite";
  24.217  ["Wednesday"] = "Mittwoch";
  24.218  ["Wiki engine"] = "Wiki engine";
  24.219  ["Yes"] = "Ja";
  24.220 -["Yesterday"] = "Gestern";
  24.221  ["You are already initator"] = "Du bist bereits Initiator";
  24.222  ["You are already not supporting this initiative"] = "Diese Initiative hat bereits keine Unterstützung von Dir";
  24.223  ["You are already supporting the latest draft"] = "Du unterstützt bereits den neuesten Entwurf";
  24.224  ["You are currently not supporting this initiative. By adding suggestions to this initiative you will automatically become a potential supporter."] = "Du bist zur Zeit kein Unterstützer dieser Initiative. Wenn Du eine Anregung hinzufügst wirst Du automatisch potentieller Unterstützer!";
  24.225 +["You are iniator of this initiative"] = "Du bist Initiator dieser Initiative";
  24.226 +["You are interested in this issue"] = "Du bist an diesem Thema interessiert";
  24.227  ["You are invited to become initiator of this initiative."] = "Du bist eingeladen Initiator dieser Initiative zu werden.";
  24.228  ["You are member"] = "Du bist Mitglied";
  24.229  ["You are now initiator of this initiative"] = "Du bist jetzt Initiator dieser Initiative";
  24.230 +["You are potential supporter of this initiative"] = "Du bist potentieller Unterstützer dieser Initiative";
  24.231 +["You are supporting this initiative"] = "Du unterstützt diese Initiative";
  24.232  ["You can't suggest the initiative you are revoking"] = "Du kannst nicht die Initiative empfehlen, die Du löschen möchtest";
  24.233  ["You didn't saved any member as contact yet."] = "Du hast noch kein Mitglied als Kontakt gespeichert!";
  24.234  ["You have saved this member as contact"] = "Du hast das Mitglied als Kontakt gespeichert";
    25.1 --- a/model/member.lua	Sun Jan 10 12:00:00 2010 +0100
    25.2 +++ b/model/member.lua	Fri Jan 22 12:00:00 2010 +0100
    25.3 @@ -310,3 +310,31 @@
    25.4    }
    25.5    return success
    25.6  end
    25.7 +
    25.8 +function Member.object:get_setting_by_key(key)
    25.9 +end
   25.10 +
   25.11 +function Member.object:set_setting(key, value)
   25.12 +end
   25.13 +
   25.14 +function Member.object:get_setting_maps_by_key(key)
   25.15 +  return SettingMap:new_selector()
   25.16 +    :add_where{ "member_id = ?", self.id }
   25.17 +    :add_where{ "key = ?", key }
   25.18 +    :add_order_by("subkey")
   25.19 +    :exec()
   25.20 +end
   25.21 +
   25.22 +function Member.object:get_setting_map_by_key_and_subkey(key, subkey)
   25.23 +  return SettingMap:new_selector()
   25.24 +    :add_where{ "member_id = ?", self.id }
   25.25 +    :add_where{ "key = ?", key }
   25.26 +    :add_where{ "subkey = ?", subkey }
   25.27 +    :add_order_by("subkey")
   25.28 +    :optional_object_mode()
   25.29 +    :exec()
   25.30 +end
   25.31 +
   25.32 +function Member.object:set_setting_map(key, subkey, value)
   25.33 +  
   25.34 +end
    26.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    26.2 +++ b/model/setting_map.lua	Fri Jan 22 12:00:00 2010 +0100
    26.3 @@ -0,0 +1,3 @@
    26.4 +SettingMap = mondelefant.new_class()
    26.5 +SettingMap.table = 'setting_map'
    26.6 +SettingMap.primary_key = { "key", "subkey" }
    26.7 \ No newline at end of file
    27.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    27.2 +++ b/model/timeline.lua	Fri Jan 22 12:00:00 2010 +0100
    27.3 @@ -0,0 +1,42 @@
    27.4 +Timeline = mondelefant.new_class()
    27.5 +Timeline.table = 'timeline'
    27.6 +
    27.7 +Timeline:add_reference{
    27.8 +  mode          = '11',
    27.9 +  to            = "Member",
   27.10 +  this_key      = 'member_id',
   27.11 +  that_key      = 'id',
   27.12 +  ref           = 'member'
   27.13 +}
   27.14 +
   27.15 +Timeline:add_reference{
   27.16 +  mode          = '11',
   27.17 +  to            = "Issue",
   27.18 +  this_key      = 'issue_id',
   27.19 +  that_key      = 'id',
   27.20 +  ref           = 'issue'
   27.21 +}
   27.22 +
   27.23 +Timeline:add_reference{
   27.24 +  mode          = '11',
   27.25 +  to            = "Initiative",
   27.26 +  this_key      = 'initiative_id',
   27.27 +  that_key      = 'id',
   27.28 +  ref           = 'initiative'
   27.29 +}
   27.30 +
   27.31 +Timeline:add_reference{
   27.32 +  mode          = '11',
   27.33 +  to            = "Draft",
   27.34 +  this_key      = 'draft_id',
   27.35 +  that_key      = 'id',
   27.36 +  ref           = 'draft'
   27.37 +}
   27.38 +
   27.39 +Timeline:add_reference{
   27.40 +  mode          = '11',
   27.41 +  to            = "Suggestion",
   27.42 +  this_key      = 'suggestion_id',
   27.43 +  that_key      = 'id',
   27.44 +  ref           = 'suggestion'
   27.45 +}
    28.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    28.2 +++ b/static/gregor.js/gregor.css	Fri Jan 22 12:00:00 2010 +0100
    28.3 @@ -0,0 +1,92 @@
    28.4 +.gregor_sheet {
    28.5 +  border:          1px solid black;
    28.6 +  border-collapse: collapse;
    28.7 +  margin:          0;
    28.8 +  padding:         0;
    28.9 +  background:      #fff;
   28.10 +  color:           #000;
   28.11 +}
   28.12 +.gregor_sheet th, .gregor_sheet td {
   28.13 +  margin: 0;
   28.14 +  font-size: 70%;
   28.15 +}
   28.16 +.gregor_year, .gregor_month {
   28.17 +  background-color: #ddd;
   28.18 +  border-bottom:    1px solid black;
   28.19 +  font-weight:      normal;
   28.20 +  text-align:       center;
   28.21 +}
   28.22 +.gregor_year a, .gregor_month a {
   28.23 +  color:           #000;
   28.24 +  text-decoration: none;
   28.25 +}
   28.26 +.gregor_year a:hover, .gregor_month a:hover {
   28.27 +  color: #f00;
   28.28 +}
   28.29 +.gregor_weekday {
   28.30 +  font-size:   80%;
   28.31 +  padding-top: 0.5em;
   28.32 +}
   28.33 +.gregor_day {
   28.34 +  padding-bottom: 0.35em;
   28.35 +}
   28.36 +
   28.37 +.gregor_weekday,
   28.38 +.gregor_day {
   28.39 +  padding-left: 0.25em;
   28.40 +  padding-right: 0.25em;
   28.41 +}
   28.42 +.gregor_weeks_left .gregor_weekday,
   28.43 +.gregor_weeks_left .gregor_day {
   28.44 +  padding-left: 0;
   28.45 +  padding-right: 0.5em;
   28.46 +}
   28.47 +.gregor_weeks_right .gregor_weekday,
   28.48 +.gregor_weeks_right .gregor_day {
   28.49 +  padding-left: 0.5em;
   28.50 +  padding-right: 0;
   28.51 +}
   28.52 +.gregor_weekday {
   28.53 +  text-align: center;
   28.54 +}
   28.55 +.gregor_weeks_left .gregor_week {
   28.56 +  padding-left:  0.25em;
   28.57 +  padding-right: 0.5em;
   28.58 +}
   28.59 +.gregor_weeks_right .gregor_week {
   28.60 +  padding-left:  1em;
   28.61 +  padding-right: 0.25em;
   28.62 +}
   28.63 +.gregor_week {
   28.64 +  color:       #666;
   28.65 +  font-size:   80%;
   28.66 +  font-weight: normal;
   28.67 +  text-align:  right;
   28.68 +}
   28.69 +td.gregor_day {
   28.70 +  text-align: right;
   28.71 +}
   28.72 +td.gregor_day a {
   28.73 +  background-color: #ddd;
   28.74 +  color:            #000;
   28.75 +  display:          block;
   28.76 +  padding-bottom:   0.25ex;
   28.77 +  padding-right:    0.25em;
   28.78 +  padding-top:      0.25ex;
   28.79 +  text-decoration:  none;
   28.80 +  width:            2em;
   28.81 +}
   28.82 +td.gregor_day.gregor_sat a {
   28.83 +  background-color: #bbb;
   28.84 +}
   28.85 +td.gregor_day.gregor_sun a {
   28.86 +  background-color: #ea9;
   28.87 +}
   28.88 +td.gregor_day.gregor_today a {
   28.89 +  font-weight: bold;
   28.90 +}
   28.91 +td.gregor_day.gregor_selected a {
   28.92 +  background-color: #444;
   28.93 +  color:            #fff;
   28.94 +}
   28.95 +
    29.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    29.2 +++ b/static/gregor.js/gregor.js	Fri Jan 22 12:00:00 2010 +0100
    29.3 @@ -0,0 +1,845 @@
    29.4 +//
    29.5 +// Copyright (c) 2009 Public Software Group e. V., Berlin
    29.6 +//
    29.7 +// Permission is hereby granted, free of charge, to any person obtaining a
    29.8 +// copy of this software and associated documentation files (the
    29.9 +// "Software"), to deal in the Software without restriction, including 
   29.10 +// without limitation the rights to use, copy, modify, merge, publish,
   29.11 +// distribute, sublicense, and/or sell copies of the Software, and to
   29.12 +// permit persons to whom the Software is furnished to do so, subject to
   29.13 +// the following conditions:
   29.14 +//
   29.15 +// The above copyright notice and this permission notice shall be included
   29.16 +// in all copies or substantial portions of the Software.
   29.17 +//
   29.18 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   29.19 +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
   29.20 +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
   29.21 +// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
   29.22 +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
   29.23 +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
   29.24 +// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
   29.25 +//
   29.26 +
   29.27 +//
   29.28 +// All date calculations are based on the gregorian calender, also for
   29.29 +// dates before 1582 (before the gegorian calendar was introduced).
   29.30 +// The supported range is from January 1st 0001 up to December 31st 9999.
   29.31 +//
   29.32 +// gregor_daycount({year: <year>, month: <month>, day: <day>}) returns
   29.33 +// the number of days between the given date and January 1st 0001 (greg.).
   29.34 +//
   29.35 +// gregor_completeDate({year: <year>, month: <month>, day: <day>}) returns
   29.36 +// a structure (an object) with the following properties:
   29.37 +//   - daycount     (days since January 1st 0001, see gregor_daycount)
   29.38 +//   - year         (with century)
   29.39 +//   - month        (from 1 to 12)
   29.40 +//   - day          (from 1 to 28, 29, 30 or 31)
   29.41 +//   - iso_string   (string with format YYYY-MM-DD)
   29.42 +//   - us_weekday   (from 0 for Sunday to 6 for Monday)
   29.43 +//   - iso_weekday  (from 0 for Monday to 6 for Sunday)
   29.44 +//   - iso_weekyear (Year containing greater part of week st. w. Monday)
   29.45 +//   - iso_week     (from 1 to 52 or 53)
   29.46 +//   - us_week      (from 1 to 53 or 54)
   29.47 +//
   29.48 +// The structure (the object) passed as parameter to gregor_daycount or
   29.49 +// gregor_completeDate may describe a date in the following ways:
   29.50 +//   - daycount
   29.51 +//   - year, month, day
   29.52 +//   - year, us_week, us_weekday
   29.53 +//   - year, iso_week, iso_weekday
   29.54 +//   - iso_weekyear, iso_week, iso_weekday
   29.55 +//
   29.56 +// gregor_sheet({...}) returns a calendar sheet as DOM object. The
   29.57 +// structure (the object) passed to the function as an argument is altered
   29.58 +// by the function and used to store state variables. Initially it can
   29.59 +// contain the following fields:
   29.60 +//   - year            (year to show, defaults to todays year)
   29.61 +//   - month           (month to show, defaults to todays month)
   29.62 +//   - today           (structure describing a day, e.g. year, month, day)
   29.63 +//   - selected        (structure describing a day, e.g. year, month, day)
   29.64 +//   - navigation      ("enabled", "disabled", "hidden", default "enabled")
   29.65 +//   - week_mode       ("iso" or "us", defaults to "iso")
   29.66 +//   - week_numbers    ("left", "right" or null, defaults to null)
   29.67 +//   - month_names     (e.g. ["January", "Feburary", ...])
   29.68 +//   - weekday_names   (e.g. ["Mon", "Tue", ...] or ["Sun", "Mon", ...])
   29.69 +//   - day_callback    (function to render a cell)
   29.70 +//   - select_callback (function to be called, when a date was selected)
   29.71 +//   - element         (for internal use only)
   29.72 +// If "today" is undefined, it is automatically intitialized with the
   29.73 +// current clients date. If "selected" is undefined or null, no date is
   29.74 +// be initially selected. It is mandatory to provide month_names and
   29.75 +// weekday_names.
   29.76 +//
   29.77 +// gregor_addGui({...}) alters a referenced input field in a way that
   29.78 +// focussing on it causes a calendar being shown at its right side, where a
   29.79 +// date can be selected. The structure (the object) passed to this function
   29.80 +// is only evaluated once, and never modified. All options except "element"
   29.81 +// of the gregor_sheet({...}) function may be used as options to
   29.82 +// gregor_addGui({...}) as well. In addition an "element_id" must be
   29.83 +// provided as argument, containing the id of a text input field. The
   29.84 +// behaviour caused by the options "selected" and  "select_callback" are
   29.85 +// slightly different: If "selected" === undefined, then the current value
   29.86 +// of the text field referenced by "element_id" will be parsed and used as
   29.87 +// date selection. If "selected" === null, then no date will be initially
   29.88 +// selected, and the text field will be cleared. The "select_callback"
   29.89 +// function is always called once with the pre-selected date (or with null,
   29.90 +// if no date is initially selected). Whenever the selected date is changed
   29.91 +// or unselected later, the callback function is called again with the new
   29.92 +// date (or with null, in case of deselection).
   29.93 +//
   29.94 +// EXAMPLE:
   29.95 +//
   29.96 +// <input type="text" id="test_field" name="test_field" value=""/>
   29.97 +// <script type="text/javascript">
   29.98 +//   gregor_addGui({
   29.99 +//     element_id: 'test_field',
  29.100 +//     month_names: [
  29.101 +//       "January", "February", "March", "April", "May", "June",
  29.102 +//       "July", "August", "September", "October", "November", "December"
  29.103 +//     ],
  29.104 +//     weekday_names: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
  29.105 +//     week_numbers: "left"
  29.106 +//   });
  29.107 +// </script>
  29.108 +//
  29.109 +
  29.110 +
  29.111 +
  29.112 +
  29.113 +// Internal constants and helper functions for date calculation
  29.114 +
  29.115 +
  29.116 +gregor_c1   = 365;                  // days of a non-leap year
  29.117 +gregor_c4   = 4 * gregor_c1 + 1;    // days of a full 4 year cycle
  29.118 +gregor_c100 = 25 * gregor_c4 - 1;   // days of a full 100 year cycle
  29.119 +gregor_c400 = 4 * gregor_c100 + 1;  // days of a full 400 year cycle
  29.120 +
  29.121 +gregor_normalMonthOffsets = [
  29.122 +  0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
  29.123 +];
  29.124 +
  29.125 +function gregor_getMonthOffset(year, month) {
  29.126 +  if (
  29.127 +    (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) &&
  29.128 +    month > 2
  29.129 +  ) return gregor_normalMonthOffsets[month-1] + 1;
  29.130 +  else return gregor_normalMonthOffsets[month-1];
  29.131 +}
  29.132 +
  29.133 +function gregor_formatInteger(int, digits) {
  29.134 +  var result = int.toFixed();
  29.135 +  if (digits != null) {
  29.136 +    while (result.length < digits) result = "0" + result;
  29.137 +  }
  29.138 +  return result;
  29.139 +}
  29.140 +
  29.141 +
  29.142 +
  29.143 +// Calculate days since January 1st 0001 (Gegorian) for a given date
  29.144 +
  29.145 +
  29.146 +function gregor_daycount(obj) {
  29.147 +  if (
  29.148 +    obj.daycount >= 0 && obj.daycount <= 3652058 && obj.daycount % 1 == 0
  29.149 +  ) {
  29.150 +    return obj.daycount;
  29.151 +  } else if (
  29.152 +    obj.year >= 1 && obj.year <= 9999 && obj.year % 1 == 0 &&
  29.153 +    obj.month >= 1 && obj.month <= 12 && obj.month % 1 == 0 &&
  29.154 +    obj.day >= 0 && obj.day <= 31 && obj.day % 1 == 0
  29.155 +  ) {
  29.156 +    var n400 = Math.floor((obj.year-1) / 400);
  29.157 +    var r400 = (obj.year-1) % 400;
  29.158 +    var n100 = Math.floor(r400 / 100);
  29.159 +    var r100 = r400 % 100;
  29.160 +    var n4 = Math.floor(r100 / 4);
  29.161 +    var n1 = r100 % 4;
  29.162 +    return (
  29.163 +      gregor_c400 * n400 +
  29.164 +      gregor_c100 * n100 +
  29.165 +      gregor_c4   * n4   +
  29.166 +      gregor_c1   * n1   +
  29.167 +      gregor_getMonthOffset(obj.year, obj.month) + (obj.day - 1)
  29.168 +    );
  29.169 +  } else if (
  29.170 +    (
  29.171 +      (
  29.172 +        obj.year >= 1 && obj.year <= 9999 &&
  29.173 +        obj.year % 1 == 0 && obj.iso_weekyear == null
  29.174 +      ) || (
  29.175 +        obj.iso_weekyear >= 1 && obj.iso_weekyear <= 9999 &&
  29.176 +        obj.iso_weekyear % 1 == 0 && obj.year == null
  29.177 +      )
  29.178 +    ) &&
  29.179 +    obj.iso_week >= 0 && obj.iso_week <= 53 && obj.iso_week % 1 == 0 &&
  29.180 +    obj.iso_weekday >= 0 && obj.iso_weekday <= 6 &&
  29.181 +    obj.iso_weekday % 1 == 0
  29.182 +  ) {
  29.183 +    var jan4th = gregor_daycount({
  29.184 +      year:  (obj.year != null) ? obj.year : obj.iso_weekyear,
  29.185 +      month: 1,
  29.186 +      day:   4
  29.187 +    });
  29.188 +    var monday0 = jan4th - (jan4th % 7) - 7;  // monday of week 0
  29.189 +    return monday0 + 7 * obj.iso_week + obj.iso_weekday;
  29.190 +  } else if (
  29.191 +    obj.year >= 1 && obj.year <= 9999 && obj.year % 1 == 0 &&
  29.192 +    obj.us_week >= 0 && obj.us_week <= 54 && obj.us_week % 1 == 0 &&
  29.193 +    obj.us_weekday >= 0 && obj.us_weekday <= 6 && obj.us_weekday % 1 == 0
  29.194 +  ) {
  29.195 +    var jan1st = gregor_daycount({
  29.196 +      year:  obj.year,
  29.197 +      month: 1,
  29.198 +      day:   1
  29.199 +    });
  29.200 +    var sunday0 = jan1st - ((jan1st+1) % 7) - 7;  // sunday of week 0
  29.201 +    return sunday0 + 7 * obj.us_week + obj.us_weekday;
  29.202 +  }
  29.203 +}
  29.204 +
  29.205 +
  29.206 +
  29.207 +// Calculate all calendar related numbers for a given date
  29.208 +
  29.209 +
  29.210 +function gregor_completeDate(obj) {
  29.211 +  if (
  29.212 +    obj.daycount >= 0 && obj.daycount <= 3652058 && obj.daycount % 1 == 0
  29.213 +  ) {
  29.214 +    var daycount = obj.daycount;
  29.215 +    var n400 = Math.floor(daycount / gregor_c400);
  29.216 +    var r400 = daycount % gregor_c400;
  29.217 +    var n100 = Math.floor(r400 / gregor_c100);
  29.218 +    var r100 = r400 % gregor_c100;
  29.219 +    if (n100 == 4) { n100 = 3; r100 = gregor_c100; }
  29.220 +    var n4 = Math.floor(r100 / gregor_c4);
  29.221 +    var r4 = r100 % gregor_c4;
  29.222 +    var n1 = Math.floor(r4 / gregor_c1);
  29.223 +    var r1 = r4 % gregor_c1;
  29.224 +    if (n1 == 4) { n1 = 3; r1 = gregor_c1; }
  29.225 +    var year = 1 + 400 * n400 + 100 * n100 + 4 * n4 + n1;
  29.226 +    var month = 1 + Math.floor(r1 / 31);
  29.227 +    var monthOffset = gregor_getMonthOffset(year, month);
  29.228 +    if (month < 12) {
  29.229 +      var nextMonthOffset = gregor_getMonthOffset(year, month + 1);
  29.230 +      if (r1 >= nextMonthOffset) {
  29.231 +        month++;
  29.232 +        monthOffset = nextMonthOffset;
  29.233 +      }
  29.234 +    }
  29.235 +    var day = 1 + r1 - monthOffset;
  29.236 +    var iso_string = ("" +
  29.237 +      gregor_formatInteger(year, 4) + "-" +
  29.238 +      gregor_formatInteger(month, 2) + "-" +
  29.239 +      gregor_formatInteger(day, 2)
  29.240 +    );
  29.241 +    var us_weekday = (daycount+1) % 7;
  29.242 +    var iso_weekday = daycount % 7;
  29.243 +    var iso_weekyear = year;
  29.244 +    if (
  29.245 +      month == 1 && (
  29.246 +        (day == 3 && iso_weekday == 6) ||
  29.247 +        (day == 2 && iso_weekday >= 5) ||
  29.248 +        (day == 1 && iso_weekday >= 4)
  29.249 +      )
  29.250 +    ) iso_weekyear--;
  29.251 +    else if (
  29.252 +      month == 12 && (
  29.253 +        (day == 29 && iso_weekday == 0) ||
  29.254 +        (day == 30 && iso_weekday <= 1) ||
  29.255 +        (day == 31 && iso_weekday <= 2)
  29.256 +      )
  29.257 +    ) iso_weekyear++;
  29.258 +    var jan4th = gregor_daycount({year: iso_weekyear, month: 1, day: 4});
  29.259 +    var monday0 = jan4th - (jan4th % 7) - 7;  // monday of week 0
  29.260 +    var iso_week = Math.floor((daycount - monday0) / 7);
  29.261 +    var jan1st = gregor_daycount({year: year, month: 1, day: 1});
  29.262 +    var sunday0 = jan1st - ((jan1st+1) % 7) - 7;  // sunday of week 0
  29.263 +    var us_week = Math.floor((daycount - sunday0) / 7);
  29.264 +    return {
  29.265 +      daycount:     daycount,
  29.266 +      year:         year,
  29.267 +      month:        month,
  29.268 +      day:          day,
  29.269 +      iso_string:   iso_string,
  29.270 +      us_weekday:   (daycount+1) % 7,  // 0 = Sunday
  29.271 +      iso_weekday:  daycount % 7,      // 0 = Monday
  29.272 +      iso_weekyear: iso_weekyear,
  29.273 +      iso_week:     iso_week,
  29.274 +      us_week:      us_week
  29.275 +    };
  29.276 +  } else if (obj.daycount == null) {
  29.277 +    var daycount = gregor_daycount(obj);
  29.278 +    if (daycount != null) {
  29.279 +      return gregor_completeDate({daycount: gregor_daycount(obj)});
  29.280 +    }
  29.281 +  }
  29.282 +}
  29.283 +
  29.284 +
  29.285 +
  29.286 +// Test gregor_daycount and gregor_completeDate for consistency
  29.287 +// (Debugging only)
  29.288 +
  29.289 +
  29.290 +function gregor_test() {
  29.291 +  for (i=650000; i<900000; i++) {
  29.292 +    var obj = gregor_completeDate({daycount: i});
  29.293 +    var j;
  29.294 +    j = gregor_daycount({
  29.295 +      year: obj.year,
  29.296 +      month: obj.month,
  29.297 +      day: obj.day
  29.298 +    });
  29.299 +    if (i != j) { alert("ERROR"); return; }
  29.300 +    j = gregor_daycount({
  29.301 +      iso_weekyear: obj.iso_weekyear,
  29.302 +      iso_week:     obj.iso_week,
  29.303 +      iso_weekday:  obj.iso_weekday
  29.304 +    });
  29.305 +    if (i != j) { alert("ERROR"); return; }
  29.306 +    j = gregor_daycount({
  29.307 +      year: obj.year,
  29.308 +      iso_week:
  29.309 +        (obj.iso_weekyear == obj.year + 1) ? 53 :
  29.310 +        (obj.iso_weekyear == obj.year - 1) ? 0 :
  29.311 +        obj.iso_week,
  29.312 +      iso_weekday: obj.iso_weekday
  29.313 +    });
  29.314 +    if (i != j) { alert("ERROR"); return; }
  29.315 +    j = gregor_daycount({
  29.316 +      year: obj.year,
  29.317 +      us_week: obj.us_week,
  29.318 +      us_weekday: obj.us_weekday
  29.319 +    });
  29.320 +    if (i != j) { alert("ERROR"); return; }
  29.321 +  }
  29.322 +  alert("SUCCESS");
  29.323 +}
  29.324 +
  29.325 +
  29.326 +
  29.327 +// Graphical calendar
  29.328 +
  29.329 +
  29.330 +gregor_iso_weekday_css = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
  29.331 +
  29.332 +function gregor_sheet(args) {
  29.333 +
  29.334 +  // setting args.today and args.selected
  29.335 +  if (args.today === undefined) {
  29.336 +    var js_date = new Date();
  29.337 +    args.today = gregor_completeDate({
  29.338 +      year:  js_date.getFullYear(),
  29.339 +      month: js_date.getMonth() + 1,
  29.340 +      day:   js_date.getDate()
  29.341 +    });
  29.342 +  } else if (args.today != null) {
  29.343 +    args.today = gregor_completeDate(args.today);
  29.344 +  }
  29.345 +  if (args.selected == "today") {
  29.346 +    args.selected = args.today;
  29.347 +  } else if (args.selected != null) {
  29.348 +    args.selected = gregor_completeDate(args.selected);
  29.349 +  }
  29.350 +
  29.351 +  // setting args.year and args.month
  29.352 +  if (args.year == null) {
  29.353 +    if (args.selected != null) args.year = args.selected.year;
  29.354 +    else args.year = args.today.year;
  29.355 +  }
  29.356 +  if (args.month == null) {
  29.357 +    if (args.selected != null) args.month = args.selected.month;
  29.358 +    else args.month = args.today.month;
  29.359 +  }
  29.360 +
  29.361 +  // setting first_day
  29.362 +  var first_day = gregor_completeDate({
  29.363 +    year:  args.year,
  29.364 +    month: args.month,
  29.365 +    day: 1
  29.366 +  });
  29.367 +  if (first_day == null) return;
  29.368 +
  29.369 +  // checking args.navigation, args.week_mode and args.week_numbers
  29.370 +  if (args.navigation == null) args.navigation = "enabled";
  29.371 +  else if (
  29.372 +    args.navigation != "enabled" &&
  29.373 +    args.navigation != "disabled" &&
  29.374 +    args.navigation != "hidden"
  29.375 +  ) return;
  29.376 +  if (args.week_mode == null) args.week_mode = "iso";
  29.377 +  else if (args.week_mode != "iso" && args.week_mode != "us") return;
  29.378 +  if (
  29.379 +    args.week_numbers != null &&
  29.380 +    args.week_numbers != "left" &&
  29.381 +    args.week_numbers != "right"
  29.382 +  ) return;
  29.383 +
  29.384 +  // checking args.month.names and args.weekday_names
  29.385 +  if (args.month_names.length != 12) return;
  29.386 +  if (args.weekday_names.length != 7) return;
  29.387 +
  29.388 +  // calculating number of days in month
  29.389 +  var count;
  29.390 +  if (args.year < 9999 || args.month < 12) {
  29.391 +    count = gregor_daycount({
  29.392 +      year:  (args.month == 12) ? (args.year + 1) : args.year,
  29.393 +      month: (args.month == 12) ? 1 : (args.month + 1),
  29.394 +      day:   1
  29.395 +    }) - first_day.daycount;
  29.396 +  } else {
  29.397 +    // workaround for year 9999
  29.398 +    count = 31;
  29.399 +  }
  29.400 +
  29.401 +  // locale variables for UI construction
  29.402 +  var table, row, cell, element;
  29.403 +
  29.404 +  // take table element from args.element,
  29.405 +  // if neccessary create and store it
  29.406 +  if (args.element == null) {
  29.407 +    table = document.createElement("table");
  29.408 +    args.element = table;
  29.409 +  } else {
  29.410 +    table = args.element;
  29.411 +    while (table.firstChild) table.removeChild(table.firstChild);
  29.412 +  }
  29.413 +
  29.414 +  // set CSS class of table according to args.week_numbers
  29.415 +  if (args.week_numbers == "left") {
  29.416 +    table.className = "gregor_sheet gregor_weeks_left";
  29.417 +  } else if (args.week_numbers == "right") {
  29.418 +    table.className = "gregor_sheet gregor_weeks_right";
  29.419 +  } else {
  29.420 +    table.className = "gregor_sheet gregor_weeks_none";
  29.421 +  }
  29.422 +
  29.423 +  // begin of table head
  29.424 +  var thead = document.createElement("thead");
  29.425 +
  29.426 +  // navigation
  29.427 +  if (args.navigation != "hidden") {
  29.428 +
  29.429 +    // UI head row containing the year
  29.430 +    row = document.createElement("tr");
  29.431 +    row.className = "gregor_year_row";
  29.432 +    cell = document.createElement("th");
  29.433 +    cell.className = "gregor_year";
  29.434 +    cell.colSpan = args.week_numbers ? 8 : 7;
  29.435 +    if (args.navigation == "enabled") {
  29.436 +      element = document.createElement("a");
  29.437 +      element.className = "gregor_turn gregor_turn_left";
  29.438 +      element.style.cssFloat   = "left";
  29.439 +      element.style.styleFloat = "left";
  29.440 +      element.href = "#";
  29.441 +      element.onclick = function() {
  29.442 +        if (args.year > 1) args.year--;
  29.443 +        gregor_sheet(args);
  29.444 +        return false;
  29.445 +      }
  29.446 +      element.ondblclick = element.onclick;
  29.447 +      element.appendChild(document.createTextNode("<<"));
  29.448 +      cell.appendChild(element);
  29.449 +      element = document.createElement("a");
  29.450 +      element.className = "gregor_turn gregor_turn_right";
  29.451 +      element.style.cssFloat   = "right";
  29.452 +      element.style.styleFloat = "right";
  29.453 +      element.href = "#";
  29.454 +      element.onclick = function() {
  29.455 +        if (args.year < 9999) args.year++;
  29.456 +        gregor_sheet(args);
  29.457 +        return false;
  29.458 +      }
  29.459 +      element.ondblclick = element.onclick;
  29.460 +      element.appendChild(document.createTextNode(">>"));
  29.461 +      cell.appendChild(element);
  29.462 +    }
  29.463 +    cell.appendChild(document.createTextNode(first_day.year));
  29.464 +    row.appendChild(cell);
  29.465 +    thead.appendChild(row);
  29.466 +
  29.467 +    // UI head row containing the month
  29.468 +    row = document.createElement("tr");
  29.469 +    row.className = "gregor_month_row";
  29.470 +    cell = document.createElement("th");
  29.471 +    cell.className = "gregor_month";
  29.472 +    cell.colSpan = args.week_numbers ? 8 : 7;
  29.473 +    if (args.navigation == "enabled") {
  29.474 +      element = document.createElement("a");
  29.475 +      element.className = "gregor_turn gregor_turn_left";
  29.476 +      element.style.cssFloat   = "left";
  29.477 +      element.style.styleFloat = "left";
  29.478 +      element.href = "#";
  29.479 +      element.onclick = function() {
  29.480 +        if (args.year > 1 || args.month > 1) {
  29.481 +          args.month--;
  29.482 +          if (args.month < 1) {
  29.483 +            args.month = 12;
  29.484 +            args.year--;
  29.485 +          }
  29.486 +        }
  29.487 +        gregor_sheet(args);
  29.488 +        return false;
  29.489 +      }
  29.490 +      element.ondblclick = element.onclick;
  29.491 +      element.appendChild(document.createTextNode("<<"));
  29.492 +      cell.appendChild(element);
  29.493 +      element = document.createElement("a");
  29.494 +      element.className = "gregor_turn gregor_turn_right";
  29.495 +      element.style.cssFloat   = "right";
  29.496 +      element.style.styleFloat = "right";
  29.497 +      element.href = "#";
  29.498 +      element.onclick = function() {
  29.499 +        if (args.year < 9999 || args.month < 12) {
  29.500 +          args.month++;
  29.501 +          if (args.month > 12) {
  29.502 +            args.month = 1;
  29.503 +            args.year++;
  29.504 +          }
  29.505 +        }
  29.506 +        gregor_sheet(args);
  29.507 +        return false;
  29.508 +      }
  29.509 +      element.ondblclick = element.onclick;
  29.510 +      element.appendChild(document.createTextNode(">>"));
  29.511 +      cell.appendChild(element);
  29.512 +    }
  29.513 +    cell.appendChild(document.createTextNode(
  29.514 +      args.month_names[first_day.month-1]
  29.515 +    ));
  29.516 +    row.appendChild(cell);
  29.517 +    thead.appendChild(row);
  29.518 +
  29.519 +  // end of navigation
  29.520 +  }
  29.521 +
  29.522 +  // UI weekday row
  29.523 +  row = document.createElement("tr");
  29.524 +  row.className = "gregor_weekday_row";
  29.525 +  if (args.week_numbers == "left") {
  29.526 +    cell = document.createElement("th");
  29.527 +    cell.className = "gregor_corner";
  29.528 +    row.appendChild(cell);
  29.529 +  }
  29.530 +  for (var i=0; i<7; i++) {
  29.531 +    cell = document.createElement("th");
  29.532 +    cell.className = (
  29.533 +      "gregor_weekday gregor_" +
  29.534 +      gregor_iso_weekday_css[(args.week_mode == "us") ? ((i+6)%7) : i]
  29.535 +    );
  29.536 +    cell.appendChild(document.createTextNode(args.weekday_names[i]));
  29.537 +    row.appendChild(cell);
  29.538 +  }
  29.539 +  if (args.week_numbers == "right") {
  29.540 +    cell = document.createElement("th");
  29.541 +    cell.className = "gregor_corner";
  29.542 +    row.appendChild(cell);
  29.543 +  }
  29.544 +  thead.appendChild(row);
  29.545 +
  29.546 +  // end of table head and begin of table body
  29.547 +  table.appendChild(thead);
  29.548 +  var tbody = document.createElement("tbody");
  29.549 +
  29.550 +  // definition of insert_week function
  29.551 +  var week = (
  29.552 +    (args.week_mode == "us") ? first_day.us_week : first_day.iso_week
  29.553 +  );
  29.554 +  insert_week = function() {
  29.555 +    cell = document.createElement("th");
  29.556 +    cell.className = "gregor_week";
  29.557 +    cell.appendChild(document.createTextNode(
  29.558 +      (week < 10) ? ("0" + week) : week)
  29.559 +    );
  29.560 +    week++;
  29.561 +    if (
  29.562 +      args.week_mode == "iso" && (
  29.563 +        (
  29.564 +          args.month == 1 && week > 52
  29.565 +        ) || (
  29.566 +          args.month == 12 && week == 53 && (
  29.567 +            first_day.iso_weekday == 0 ||
  29.568 +            first_day.iso_weekday == 5 ||
  29.569 +            first_day.iso_weekday == 6
  29.570 +          )
  29.571 +        )
  29.572 +      )
  29.573 +    ) week = 1;
  29.574 +    row.appendChild(cell);
  29.575 +  }
  29.576 +
  29.577 +  // output data fields
  29.578 +  row = document.createElement("tr");
  29.579 +  if (args.week_numbers == "left") insert_week();
  29.580 +  var filler_count = (
  29.581 +    (args.week_mode == "us") ? first_day.us_weekday : first_day.iso_weekday
  29.582 +  );
  29.583 +  for (var i=0; i<filler_count; i++) {
  29.584 +    cell = document.createElement("td");
  29.585 +    cell.className = "gregor_empty";
  29.586 +    row.appendChild(cell);
  29.587 +  }
  29.588 +  for (var i=0; i<count; i++) {
  29.589 +    var date = gregor_completeDate({daycount: first_day.daycount + i});
  29.590 +    if (row == null) {
  29.591 +      row = document.createElement("tr");
  29.592 +      if (args.week_numbers == "left") insert_week();
  29.593 +    }
  29.594 +    cell = document.createElement("td");
  29.595 +    var cssClass = (
  29.596 +      "gregor_day gregor_" + gregor_iso_weekday_css[date.iso_weekday]
  29.597 +    );
  29.598 +    if (args.today != null && date.daycount == args.today.daycount) {
  29.599 +      cssClass += " gregor_today";
  29.600 +    }
  29.601 +    if (args.selected != null && date.daycount == args.selected.daycount) {
  29.602 +      cssClass += " gregor_selected";
  29.603 +    }
  29.604 +    cell.className = cssClass;
  29.605 +    if (args.day_callback) {
  29.606 +      args.day_callback(cell, date);
  29.607 +    } else {
  29.608 +      element = document.createElement("a");
  29.609 +      element.href = "#";
  29.610 +      var generate_onclick = function(date) {
  29.611 +        return function() {
  29.612 +          args.selected = date;
  29.613 +          gregor_sheet(args);
  29.614 +          if (args.select_callback != null) {
  29.615 +            args.select_callback(date);
  29.616 +          }
  29.617 +          return false;
  29.618 +        }
  29.619 +      }
  29.620 +      element.onclick = generate_onclick(date);
  29.621 +      element.ondblclick = element.onclick;
  29.622 +      element.appendChild(document.createTextNode(first_day.day + i));
  29.623 +      cell.appendChild(element);
  29.624 +    }
  29.625 +    row.appendChild(cell);
  29.626 +    if (row.childNodes.length == ((args.week_numbers == "left") ? 8 : 7)) {
  29.627 +      if (args.week_numbers == "right") insert_week();
  29.628 +      tbody.appendChild(row);
  29.629 +      row = null;
  29.630 +    }
  29.631 +  }
  29.632 +  if (row != null) {
  29.633 +    while (row.childNodes.length < ((args.week_numbers == "left") ? 8 : 7)) {
  29.634 +      cell = document.createElement("td");
  29.635 +      cell.className = "gregor_empty";
  29.636 +      row.appendChild(cell);
  29.637 +    }
  29.638 +    if (args.week_numbers == "right") insert_week();
  29.639 +    tbody.appendChild(row);
  29.640 +  }
  29.641 +
  29.642 +  // end of table body
  29.643 +  table.appendChild(tbody);
  29.644 +
  29.645 +  // return table
  29.646 +  return table;
  29.647 +}
  29.648 +
  29.649 +
  29.650 +
  29.651 +// Rich form field support
  29.652 +
  29.653 +
  29.654 +function gregor_getAbsoluteLeft(elem) {
  29.655 + var result = 0;
  29.656 + while (elem && elem.style.position != "static") {
  29.657 +  result += elem.offsetLeft;
  29.658 +  elem = elem.offsetParent;
  29.659 + }
  29.660 + return result;
  29.661 +}
  29.662 +
  29.663 +function gregor_getAbsoluteTop(elem) {
  29.664 + var result = 0;
  29.665 + while (elem && elem.style.position != "static") {
  29.666 +  result += elem.offsetTop;
  29.667 +  elem = elem.offsetParent;
  29.668 + }
  29.669 + return result;
  29.670 +}
  29.671 +
  29.672 +function gregor_formatDate(format, date) {
  29.673 +  if (date == null) return "";
  29.674 +  var result = format;
  29.675 +  result = result.replace(/Y+/, function(s) {
  29.676 +    if (s.length == 2) return gregor_formatInteger(date.year % 100, 2);
  29.677 +    else return gregor_formatInteger(date.year, s.length);
  29.678 +  });
  29.679 +  result = result.replace(/M+/, function(s) {
  29.680 +    return gregor_formatInteger(date.month, s.length);
  29.681 +  });
  29.682 +  result = result.replace(/D+/, function(s) {
  29.683 +    return gregor_formatInteger(date.day, s.length);
  29.684 +  });
  29.685 +  return result;
  29.686 +}
  29.687 +
  29.688 +function gregor_map2digitYear(y2, maxYear) {
  29.689 +  var guess = Math.floor(maxYear / 100) * 100 + y2;
  29.690 +  if (guess <= maxYear) return guess;
  29.691 +  else return guess - 100;
  29.692 +}
  29.693 +
  29.694 +function gregor_parseDate(format, string, terminated, maxYear) {
  29.695 +  var numericParts, formatParts;
  29.696 +  numericParts = string.match(/^\s*([0-9]{4})-([0-9]{2})-([0-9]{2})\s*$/);
  29.697 +  if (numericParts != null) {
  29.698 +    return gregor_completeDate({
  29.699 +      year:  numericParts[1],
  29.700 +      month: numericParts[2],
  29.701 +      day:   numericParts[3]
  29.702 +    });
  29.703 +  }
  29.704 +  numericParts = string.match(/[0-9]+/g);
  29.705 +  if (numericParts == null) return null;
  29.706 +  formatParts = format.match(/[YMD]+/g);
  29.707 +  if (numericParts.length != formatParts.length) return null;
  29.708 +  if (
  29.709 +    !terminated && (
  29.710 +      numericParts[numericParts.length-1].length <
  29.711 +      formatParts[formatParts.length-1].length
  29.712 +    )
  29.713 +  ) return null;
  29.714 +  var year, month, day;
  29.715 +  for (var i=0; i<numericParts.length; i++) {
  29.716 +    var numericPart = numericParts[i];
  29.717 +    var formatPart = formatParts[i];
  29.718 +    if (formatPart.match(/^Y+$/)) {
  29.719 +      if (numericPart.length == 2 && maxYear != null) {
  29.720 +        year = gregor_map2digitYear(parseInt(numericPart, 10), maxYear);
  29.721 +      } else if (numericPart.length > 2) {
  29.722 +        year = parseInt(numericPart, 10);
  29.723 +      }
  29.724 +    } else if (formatPart.match(/^M+$/)) {
  29.725 +      month = parseInt(numericPart, 10);
  29.726 +    } else if (formatPart.match(/^D+$/)) {
  29.727 +      day = parseInt(numericPart, 10);
  29.728 +    } else {
  29.729 +      //alert("Not implemented.");
  29.730 +      return null;
  29.731 +    }
  29.732 +  }
  29.733 +  return gregor_completeDate({year: year, month: month, day: day});
  29.734 +}
  29.735 +
  29.736 +function gregor_addGui(args) {
  29.737 +
  29.738 +  // copy argument structure
  29.739 +  var state = {};
  29.740 +  for (key in args) state[key] = args[key];
  29.741 +
  29.742 +  // unset state.element, which should never be set anyway
  29.743 +  state.element = null;
  29.744 +
  29.745 +  // save original values of "year" and "month" options
  29.746 +  var original_year = state.year;
  29.747 +  var original_month = state.month;
  29.748 +
  29.749 +  // get text field element
  29.750 +  var element = document.getElementById(state.element_id);
  29.751 +  state.element_id = null;
  29.752 +
  29.753 +  // setup state.today, state.selected and state.format options
  29.754 +  if (state.today === undefined) {
  29.755 +    var js_date = new Date();
  29.756 +    state.today = gregor_completeDate({
  29.757 +      year:  js_date.getFullYear(),
  29.758 +      month: js_date.getMonth() + 1,
  29.759 +      day:   js_date.getDate()
  29.760 +    });
  29.761 +  } else if (state.today != null) {
  29.762 +    state.today = gregor_completeDate(args.today);
  29.763 +  }
  29.764 +  if (state.selected == "today") {
  29.765 +    state.selected = state.today;
  29.766 +  } else if (args.selected != null) {
  29.767 +    state.selected = gregor_completeDate(state.selected);
  29.768 +  }
  29.769 +  if (state.format == null) state.format = "YYYY-MM-DD";
  29.770 +
  29.771 +  // using state.future to calculate maxYear (for 2 digit year conversions)
  29.772 +  var maxYear = (state.today == null) ? null : (
  29.773 +    state.today.year +
  29.774 +    ((state.future == null) ? 12 : state.future)
  29.775 +  );
  29.776 +
  29.777 +  // hook into state.select_callback
  29.778 +  var select_callback = state.select_callback;
  29.779 +  state.select_callback = function(date) {
  29.780 +    element.value = gregor_formatDate(state.format, date);
  29.781 +    if (select_callback) select_callback(date);
  29.782 +  };
  29.783 +
  29.784 +  // function to parse text field and update calendar sheet state
  29.785 +  var updateSheet = function(terminated) {
  29.786 +    var date = gregor_parseDate(
  29.787 +      state.format, element.value, terminated, maxYear
  29.788 +    );
  29.789 +    if (date) {
  29.790 +      state.year = null;
  29.791 +      state.month = null;
  29.792 +    }
  29.793 +    state.selected = date;
  29.794 +    gregor_sheet(state);
  29.795 +  };
  29.796 +
  29.797 +  // Initial synchronization
  29.798 +  if (state.selected === undefined) updateSheet(true);
  29.799 +  element.value = gregor_formatDate(state.format, state.selected);
  29.800 +  if (select_callback) select_callback(state.selected);
  29.801 +
  29.802 +  // variables storing popup status
  29.803 +  var visible = false;
  29.804 +  var focus = false;
  29.805 +  var protection = false;
  29.806 +
  29.807 +  // event handlers for text field
  29.808 +  element.onfocus = function() {
  29.809 +    focus = true;
  29.810 +    if (!visible) {
  29.811 +      state.year = original_year;
  29.812 +      state.month = original_month;
  29.813 +      gregor_sheet(state);
  29.814 +      state.element.style.position = "absolute";
  29.815 +      state.element.style.top = gregor_getAbsoluteTop(element) + element.offsetHeight;
  29.816 +      state.element.style.left = gregor_getAbsoluteLeft(element);
  29.817 +      state.element.onmousedown = function() {
  29.818 +        protection = true;
  29.819 +      };
  29.820 +      state.element.onmouseup = function() {
  29.821 +        protection = false;
  29.822 +        element.focus();
  29.823 +      };
  29.824 +      state.element.onmouseout = state.element.onmouseup;
  29.825 +      element.parentNode.appendChild(state.element);
  29.826 +      visible = true;
  29.827 +    }
  29.828 +  };
  29.829 +  element.onblur = function() {
  29.830 +    focus = false;
  29.831 +    window.setTimeout(function() {
  29.832 +      if (visible && !focus && !protection) {
  29.833 +        updateSheet(true);
  29.834 +        element.value = gregor_formatDate(state.format, state.selected);
  29.835 +        if (select_callback) select_callback(state.selected);
  29.836 +        state.element.parentNode.removeChild(state.element);
  29.837 +        visible = false;
  29.838 +        protection = false;
  29.839 +      }
  29.840 +    }, 1);
  29.841 +  };
  29.842 +  element.onkeyup = function() {
  29.843 +    updateSheet(false);
  29.844 +    if (select_callback) select_callback(state.selected);
  29.845 +  };
  29.846 +
  29.847 +}
  29.848 +
    30.1 Binary file static/icons/16/clock.png has changed
    31.1 Binary file static/icons/16/magnifier.png has changed
    32.1 Binary file static/icons/16/text_list_bullets.png has changed
    33.1 Binary file static/icons/16/user_edit.png has changed
    34.1 --- a/static/style.css	Sun Jan 10 12:00:00 2010 +0100
    34.2 +++ b/static/style.css	Fri Jan 22 12:00:00 2010 +0100
    34.3 @@ -438,6 +438,14 @@
    34.4   * ui.paginate
    34.5   */
    34.6  
    34.7 +.ui_paginate_head {
    34.8 +  margin-bottom: 1ex;
    34.9 +}
   34.10 +
   34.11 +.ui_paginate_foot {
   34.12 +  margin-top: 1ex;
   34.13 +}
   34.14 +
   34.15  .ui_paginate_select a {
   34.16    padding: 0.5ex;
   34.17  }
   34.18 @@ -517,6 +525,11 @@
   34.19    padding-right: 0.5em;
   34.20  }
   34.21  
   34.22 +.ui_field_label.label_right {
   34.23 +  text-align: left;
   34.24 +  width: auto;
   34.25 +}
   34.26 +
   34.27  .login input[type=text],
   34.28  .login input[type=password] {
   34.29    width: 10em;
   34.30 @@ -581,6 +594,14 @@
   34.31    background-color: #ddd;
   34.32  }
   34.33  
   34.34 +.nohover tr:hover td {
   34.35 +  background-color: #fff;
   34.36 +}
   34.37 +
   34.38 +.nohover table tr:hover td {
   34.39 +  background-color: #ddd;
   34.40 +}
   34.41 +
   34.42  
   34.43  tr table tr:hover td {
   34.44    background-color: #fff;
   34.45 @@ -817,7 +838,8 @@
   34.46  .draft_updated_info,
   34.47  .voting_active_info,
   34.48  .revoked_info,
   34.49 -.initiator_invite_info {
   34.50 +.initiator_invite_info,
   34.51 +.motd {
   34.52    background-color: #fec;
   34.53    border: 2px solid #b96;
   34.54    padding: 1ex;
   34.55 @@ -901,6 +923,10 @@
   34.56    color: #000;
   34.57  }
   34.58  
   34.59 +.action_active {
   34.60 +  background-color: #fec;
   34.61 +}
   34.62 +
   34.63  /*************************************************************************
   34.64   * Voting
   34.65   */

Impressum / About Us