# HG changeset patch
# User bsw
# Date 1264158000 -3600
# Node ID 77d58efe99fd7ce8713052f15e12039a8e8531a4
# Parent 72c5e0ee7c984f7ee7e7e24e126bb5dcbdade600
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)
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/_filter/21_auth.lua
--- a/app/main/_filter/21_auth.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/_filter/21_auth.lua Fri Jan 22 12:00:00 2010 +0100
@@ -10,6 +10,7 @@
or request.get_action() == "reset_password"
or request.get_view() == "confirm_notify_email"
or request.get_action() == "confirm_notify_email"
+ or request.get_action() == "set_lang"
)
)
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/_filter_view/30_navigation.lua
--- a/app/main/_filter_view/30_navigation.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/_filter_view/30_navigation.lua Fri Jan 22 12:00:00 2010 +0100
@@ -42,50 +42,77 @@
slot.select('navigation', function()
- ui.link{
- content = function()
- ui.image{ static = "icons/16/house.png" }
- slot.put(_"Home")
- end,
- module = 'index',
- view = 'index'
- }
+ ui.link{
+ content = function()
+ ui.image{ static = "icons/16/house.png" }
+ slot.put(_"Home")
+ end,
+ module = 'index',
+ view = 'index'
+ }
+
+ local setting_key = "liquidfeedback_frontend_timeline_current_options"
+ local setting = Setting:by_pk(app.session.member.id, setting_key)
- ui.link{
- content = function()
- ui.image{ static = "icons/16/package.png" }
- slot.put(_"Areas")
- end,
- module = 'area',
- view = 'list'
- }
+ timeline_params = {}
+ if setting then
+ for event_ident, filter_idents in setting.value:gmatch("(%S+):(%S+)") do
+ timeline_params["option_" .. event_ident] = true
+ if filter_idents ~= "*" then
+ for filter_ident in filter_idents:gmatch("([^\|]+)") do
+ timeline_params["option_" .. event_ident .. "_" .. filter_ident] = true
+ end
+ end
+ end
+ end
+
+ timeline_params.date = param.get("date") or today
+
+ ui.link{
+ content = function()
+ ui.image{ static = "icons/16/time.png" }
+ slot.put(_"Timeline")
+ end,
+ module = "timeline",
+ action = "update"
+-- params = timeline_params
+ }
- ui.link{
- content = function()
- ui.image{ static = "icons/16/group.png" }
- slot.put(_"Members")
- end,
- module = 'member',
- view = 'list'
- }
+ ui.link{
+ content = function()
+ ui.image{ static = "icons/16/package.png" }
+ slot.put(_"Areas")
+ end,
+ module = 'area',
+ view = 'list'
+ }
+
+ ui.link{
+ content = function()
+ ui.image{ static = "icons/16/group.png" }
+ slot.put(_"Members")
+ end,
+ module = 'member',
+ view = 'list'
+ }
- ui.link{
- content = function()
- ui.image{ static = "icons/16/book_edit.png" }
- slot.put(_"Contacts")
- end,
- module = 'contact',
- view = 'list'
- }
+ ui.link{
+ content = function()
+ ui.image{ static = "icons/16/book_edit.png" }
+ slot.put(_"Contacts")
+ end,
+ module = 'contact',
+ view = 'list'
+ }
- ui.link{
- content = function()
- ui.image{ static = "icons/16/information.png" }
- slot.put(_"About")
- end,
- module = 'index',
- view = 'about'
- }
+ ui.link{
+ content = function()
+ ui.image{ static = "icons/16/information.png" }
+ slot.put(_"About")
+ end,
+ module = 'index',
+ view = 'about'
+ }
if app.session.member.admin then
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/_layout/default.html
--- a/app/main/_layout/default.html Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/_layout/default.html Fri Jan 22 12:00:00 2010 +0100
@@ -3,6 +3,7 @@
+
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/index/login.lua
--- a/app/main/index/login.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/index/login.lua Fri Jan 22 12:00:00 2010 +0100
@@ -12,6 +12,55 @@
slot.put_into("title", encode.html(config.app_title))
+slot.select("title", function()
+ ui.container{
+ attr = { class = "lang_chooser" },
+ content = function()
+ for i, lang in ipairs{"en", "de"} do
+ ui.link{
+ content = function()
+ ui.image{
+ static = "lang/" .. lang .. ".png",
+ attr = { style = "margin-left: 0.5em;", alt = lang }
+ }
+ end,
+ module = "index",
+ action = "set_lang",
+ params = { lang = lang },
+ routing = {
+ default = {
+ mode = "redirect",
+ module = request.get_module(),
+ view = request.get_view(),
+ id = param.get_id_cgi(),
+ params = param.get_all_cgi()
+ }
+ }
+ }
+ end
+ end
+ }
+end)
+
+
+local lang = locale.get("lang")
+local basepath = request.get_app_basepath()
+local file_name = basepath .. "/locale/motd/" .. lang .. "_public.txt"
+local file = io.open(file_name)
+if file ~= nil then
+ local help_text = file:read("*a")
+ if #help_text > 0 then
+ ui.container{
+ attr = { class = "motd wiki" },
+ content = function()
+ slot.put(format.wiki_text(help_text))
+ end
+ }
+ end
+end
+
+
+
ui.tag{
tag = 'p',
content = _'You need to be logged in, to use this system.'
@@ -51,3 +100,4 @@
}
end
}
+
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/initiative/_list.lua
--- a/app/main/initiative/_list.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/initiative/_list.lua Fri Jan 22 12:00:00 2010 +0100
@@ -1,6 +1,18 @@
local initiatives_selector = param.get("initiatives_selector", "table")
initiatives_selector:join("issue", nil, "issue.id = initiative.issue_id")
+local limit = param.get("limit", atom.number)
+
+local more_initiatives_count
+if limit then
+ local initiatives_count = initiatives_selector:count()
+ if initiatives_count > limit then
+ more_initiatives_count = initiatives_count - limit
+ end
+ initiatives_selector:limit(limit)
+end
+
+
local issue = param.get("issue", "table")
local order_options = {}
@@ -53,12 +65,22 @@
end
end
+initiatives_selector
+ :left_join("initiator", "_initiator", { "_initiator.initiative_id = initiative.id AND _initiator.member_id = ?", app.session.member.id} )
+ :left_join("supporter", "_supporter", { "_supporter.initiative_id = initiative.id AND _supporter.member_id = ?", app.session.member.id} )
+
+ :add_field("(_initiator.member_id NOTNULL)", "is_initiator")
+ :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")
+ :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")
+
+
ui_order{
name = name,
selector = initiatives_selector,
options = order_options,
content = function()
ui.paginate{
+ name = issue and "issue_" .. tostring(issue.id) .. "_page" or nil,
selector = initiatives_selector,
per_page = param.get("per_page", atom.number),
content = function()
@@ -130,6 +152,30 @@
static = "icons/16/new.png"
}
end
+ if record.is_supporter then
+ slot.put(" ")
+ local label = _"You are supporting this initiative"
+ ui.image{
+ attr = { alt = label, title = label },
+ static = "icons/16/thumb_up_green.png"
+ }
+ end
+ if record.is_potential_supporter then
+ slot.put(" ")
+ local label = _"You are potential supporter of this initiative"
+ ui.image{
+ attr = { alt = label, title = label },
+ static = "icons/16/thumb_up.png"
+ }
+ end
+ if record.is_initiator then
+ slot.put(" ")
+ local label = _"You are iniator of this initiative"
+ ui.image{
+ attr = { alt = label, title = label },
+ static = "icons/16/user_edit.png"
+ }
+ end
end
}
@@ -141,4 +187,14 @@
end
}
end
-}
\ No newline at end of file
+}
+
+if more_initiatives_count then
+ ui.link{
+ attr = { style = "font-size: 75%; font-style: italic;" },
+ content = _("#{count} more initiatives", { count = more_initiatives_count }),
+ module = "issue",
+ view = "show",
+ id = issue.id,
+ }
+end
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/initiative/show.lua
--- a/app/main/initiative/show.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/initiative/show.lua Fri Jan 22 12:00:00 2010 +0100
@@ -151,18 +151,22 @@
ui.tag{
tag = "span",
content = function()
- if initiative.discussion_url and #initiative.discussion_url > 0 then
- ui.link{
- attr = {
- class = "actions",
- target = "_blank",
- title = initiative.discussion_url
- },
- content = function()
- slot.put(encode.html(initiative.discussion_url))
- end,
- external = initiative.discussion_url
- }
+ if initiative.discussion_url:find("^https?://") then
+ if initiative.discussion_url and #initiative.discussion_url > 0 then
+ ui.link{
+ attr = {
+ class = "actions",
+ target = "_blank",
+ title = initiative.discussion_url
+ },
+ content = function()
+ slot.put(encode.html(initiative.discussion_url))
+ end,
+ external = initiative.discussion_url
+ }
+ end
+ else
+ slot.put(encode.html(initiative.discussion_url))
end
slot.put(" ")
if initiator and initiator.accepted and not initiative.issue.half_frozen and not initiative.issue.closed and not initiative.revoked then
@@ -248,7 +252,7 @@
ui.container{
attr = { class = "draft_updated_info" },
content = function()
- slot.put("The draft of this initiative has been updated!")
+ slot.put(_"The draft of this initiative has been updated!")
slot.put(" ")
ui.link{
content = _"Show diff",
@@ -358,18 +362,27 @@
end
}
-local members_selector = initiative:get_reference_selector("supporting_members_snapshot")
+local members_selector = initiative:get_reference_selector("supporting_members_snapshot")
:join("issue", nil, "issue.id = direct_supporter_snapshot.issue_id")
: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")
:add_field("direct_interest_snapshot.weight")
:add_where("direct_supporter_snapshot.event = issue.latest_snapshot_event")
:add_where("direct_supporter_snapshot.satisfied")
-local satisfied_supporter_count = members_selector:count()
+local tmp = db:query("SELECT count(1) AS count, sum(weight) AS weight FROM (" .. tostring(members_selector) .. ") as subquery", "object")
+local direct_satisfied_supporter_count = tmp.count
+local indirect_satisfied_supporter_count = (tmp.weight or 0) - tmp.count
+
+local count_string
+if indirect_satisfied_supporter_count > 0 then
+ count_string = "(" .. tostring(direct_satisfied_supporter_count) .. "+" .. tostring(indirect_satisfied_supporter_count) .. ")"
+else
+ count_string = "(" .. tostring(direct_satisfied_supporter_count) .. ")"
+end
tabs[#tabs+1] = {
name = "satisfied_supporter",
- label = _"Supporter" .. " (" .. tostring(satisfied_supporter_count) .. ")",
+ label = _"Supporter" .. " " .. count_string,
content = function()
execute.view{
module = "member",
@@ -389,11 +402,20 @@
:add_where("direct_supporter_snapshot.event = issue.latest_snapshot_event")
:add_where("NOT direct_supporter_snapshot.satisfied")
-local potential_supporter_count = members_selector:count()
+local tmp = db:query("SELECT count(1) AS count, sum(weight) AS weight FROM (" .. tostring(members_selector) .. ") as subquery", "object")
+local direct_potential_supporter_count = tmp.count
+local indirect_potential_supporter_count = (tmp.weight or 0) - tmp.count
+
+local count_string
+if indirect_potential_supporter_count > 0 then
+ count_string = "(" .. tostring(direct_potential_supporter_count) .. "+" .. tostring(indirect_potential_supporter_count) .. ")"
+else
+ count_string = "(" .. tostring(direct_potential_supporter_count) .. ")"
+end
tabs[#tabs+1] = {
name = "supporter",
- label = _"Potential supporter" .. " (" .. tostring(potential_supporter_count) .. ")",
+ label = _"Potential supporter" .. " " .. count_string,
content = function()
execute.view{
module = "member",
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/issue/_list.lua
--- a/app/main/issue/_list.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/issue/_list.lua Fri Jan 22 12:00:00 2010 +0100
@@ -1,5 +1,9 @@
local issues_selector = param.get("issues_selector", "table")
+issues_selector
+ :left_join("interest", "_interest", { "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id} )
+ :add_field("(_interest.member_id NOTNULL)", "is_interested")
+
local ui_filter = ui.filter
if param.get("filter", atom.boolean) == false then
ui_filter = function(args) args.content() end
@@ -150,6 +154,36 @@
end
end
},
+ {
+ type = "boolean",
+ name = "supported",
+ label = _"Supported",
+ selector_modifier = function(selector, value)
+ if value then
+ 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 })
+ end
+ end
+ },
+ {
+ type = "boolean",
+ name = "potentially_supported",
+ label = _"Potential supported",
+ selector_modifier = function(selector, value)
+ if value then
+ 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 })
+ end
+ end
+ },
+ {
+ type = "boolean",
+ name = "initiated",
+ label = _"Initiated",
+ selector_modifier = function(selector, value)
+ if value then
+ 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 })
+ end
+ end
+ },
},
content = function()
local ui_order = ui.order
@@ -215,6 +249,14 @@
}
slot.put("
")
end
+ if record.is_interested then
+ local label = _"You are interested in this issue",
+ ui.image{
+ attr = { alt = label, title = label },
+ static = "icons/16/eye.png"
+ }
+ slot.put(" ")
+ end
ui.link{
text = _("Issue ##{id}", { id = tostring(record.id) }),
module = "issue",
@@ -265,7 +307,6 @@
issue = record,
initiatives_selector = initiatives_selector,
highlight_string = highlight_string,
- limit = 3,
per_page = param.get("initiatives_per_page", atom.number),
no_sort = param.get("initiatives_no_sort", atom.boolean)
}
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/member/_show.lua
--- a/app/main/member/_show.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/member/_show.lua Fri Jan 22 12:00:00 2010 +0100
@@ -73,7 +73,7 @@
ui.tag{
tag = "span",
content = function()
- slot.put(encode.html_newlines(member.address))
+ slot.put(encode.html_newlines(html.encode(member.address)))
end
}
end
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/timeline/_action/delete_filter.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/main/timeline/_action/delete_filter.lua Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,3 @@
+local timeline_filter = app.session.member:get_setting_map_by_key_and_subkey("timeline_filters", param.get("name"))
+
+timeline_filter:destroy()
\ No newline at end of file
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/timeline/_action/save.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/main/timeline/_action/save.lua Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,41 @@
+local id = param.get("id", atom.number)
+
+local setting_key = "liquidfeedback_frontend_timeline_current_options"
+local setting = Setting:by_pk(app.session.member.id, setting_key)
+local options_string = setting.value
+
+local timeline_filter
+
+local subkey = param.get("name")
+
+setting_map = SettingMap:new()
+setting_map.member_id = app.session.member.id
+setting_map.key = "timeline_filters"
+setting_map.subkey = subkey
+setting_map.value = options_string
+setting_map:save()
+
+local timeline_params = {}
+if options_string then
+ for event_ident, filter_idents in setting.value:gmatch("(%S+):(%S+)") do
+ timeline_params["option_" .. event_ident] = true
+ if filter_idents ~= "*" then
+ for filter_ident in filter_idents:gmatch("([^\|]+)") do
+ timeline_params["option_" .. event_ident .. "_" .. filter_ident] = true
+ end
+ end
+ end
+end
+
+local setting_key = "liquidfeedback_frontend_timeline_current_date"
+local setting = Setting:by_pk(app.session.member.id, setting_key)
+
+if setting then
+ timeline_params.date = setting.value
+end
+
+request.redirect{
+ module = "timeline",
+ view = "index",
+ params = timeline_params
+}
\ No newline at end of file
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/timeline/_action/update.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/main/timeline/_action/update.lua Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,101 @@
+execute.view{
+ module = "timeline",
+ view = "_constants"
+}
+
+local options_string = param.get("options_string")
+
+if not options_string then
+ local active_options = ""
+ for event_ident, event_name in pairs(event_names) do
+ if param.get("option_" .. event_ident, atom.boolean) then
+ active_options = active_options .. event_ident .. ":"
+ local filter_idents = {}
+ for filter_ident, filter_name in pairs(filter_names) do
+ if param.get("option_" .. event_ident .. "_" .. filter_ident, atom.boolean) then
+ filter_idents[#filter_idents+1] = filter_ident
+ end
+ end
+ if #filter_idents > 0 then
+ active_options = active_options .. table.concat(filter_idents, "|") .. " "
+ else
+ active_options = active_options .. "* "
+ end
+ end
+ end
+ if #active_options > 0 then
+ options_string = active_options
+ end
+end
+
+if not options_string then
+ 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:* "
+end
+
+local setting_key = "liquidfeedback_frontend_timeline_current_options"
+local setting = Setting:by_pk(app.session.member.id, setting_key)
+
+if not setting or setting.value ~= options_string then
+ if not setting then
+ setting = Setting:new()
+ setting.member_id = app.session.member_id
+ setting.key = setting_key
+ end
+ if options_string then
+ setting.value = options_string
+ setting:save()
+ end
+end
+
+local date = param.get("date")
+
+if date and #date > 0 then
+ local setting_key = "liquidfeedback_frontend_timeline_current_date"
+ local setting = Setting:by_pk(app.session.member.id, setting_key)
+ if not setting or setting.value ~= date then
+ if not setting then
+ setting = Setting:new()
+ setting.member_id = app.session.member.id
+ setting.key = setting_key
+ end
+ setting.value = date
+ setting:save()
+ end
+end
+
+local setting_key = "liquidfeedback_frontend_timeline_current_options"
+local setting = Setting:by_pk(app.session.member.id, setting_key)
+
+local timeline_params = {}
+if setting and setting.value then
+ for event_ident, filter_idents in setting.value:gmatch("(%S+):(%S+)") do
+ timeline_params["option_" .. event_ident] = true
+ if filter_idents ~= "*" then
+ for filter_ident in filter_idents:gmatch("([^\|]+)") do
+ timeline_params["option_" .. event_ident .. "_" .. filter_ident] = true
+ end
+ end
+ end
+end
+
+local setting_key = "liquidfeedback_frontend_timeline_current_date"
+local setting = Setting:by_pk(app.session.member.id, setting_key)
+
+if setting then
+ timeline_params.date = setting.value
+end
+
+timeline_params.show_options = param.get("show_options", atom.boolean)
+
+if param.get("save", atom.boolean) then
+ request.redirect{
+ module = "timeline",
+ view = "save_filter"
+ }
+else
+ request.redirect{
+ module = "timeline",
+ view = "index",
+ params = timeline_params
+ }
+end
\ No newline at end of file
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/timeline/_constants.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/main/timeline/_constants.lua Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,31 @@
+event_names = {
+ issue_created = _"New issue",
+ issue_canceled = _"Issue canceled",
+ issue_accepted = _"Issue accepted",
+ issue_half_frozen = _"Issue frozen",
+ issue_finished_without_voting = _"Issue finished without voting",
+ issue_voting_started = _"Voting started",
+ issue_finished_after_voting = _"Issue finished",
+ initiative_created = _"New initiative",
+ initiative_revoked = _"Initiative revoked",
+ draft_created = _"New draft",
+ suggestion_created = _"New suggestion"
+}
+
+filter_names = {
+ contact = _"Saved as contact",
+ interested = _"Interested",
+ supporter = _"Supported",
+ potential_supporter = _"Potential supported",
+ initiator = _"Initiated",
+ membership = _"Member of area"
+}
+
+option_names = {}
+for key, val in pairs(event_names) do
+ option_names[key] = val
+end
+for key, val in pairs(filter_names) do
+ option_names[key] = val
+end
+
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/timeline/_list.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/main/timeline/_list.lua Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,124 @@
+local timeline_selector = param.get("timeline_selector", "table")
+local event_names = param.get("event_names", "table")
+local initiatives_per_page = param.get("initiatives_per_page", atom.number) or 3
+
+ui.paginate{
+ per_page = param.get("per_page", atom.number) or 25,
+ selector = timeline_selector,
+ content = function()
+ local timelines = timeline_selector:exec()
+ timelines:load("issue")
+ timelines:load("initiative")
+ timelines:load("member")
+ ui.list{
+ attr = { class = "nohover" },
+ records = timelines,
+ columns = {
+ {
+ field_attr = { style = "width: 10em;" },
+ content = function(timeline)
+ ui.field.text{
+ attr = { style = "font-size: 75%; font-weight: bold; background-color: #ccc; display: block; margin-bottom: 1ex;"},
+ value = format.time(timeline.occurrence)
+ }
+ ui.field.text{
+ attr = { style = "font-size: 75%; font-weight: bold;"},
+ value = event_names[timeline.event] or timeline.event
+ }
+ end
+ },
+ {
+ content = function(timeline)
+ local issue
+ local initiative
+ if timeline.issue then
+ issue = timeline.issue
+ elseif timeline.initiative then
+ initiative = timeline.initiative
+ issue = initiative.issue
+ elseif timeline.draft then
+ initiative = timeline.draft.initiative
+ issue = initiative.issue
+ elseif timeline.suggestion then
+ initiative = timeline.suggestion.initiative
+ issue = initiative.issue
+ end
+ if issue then
+ if timeline.is_interested then
+ local label = _"You are interested in this issue",
+ ui.image{
+ attr = { alt = label, title = label, style = "float: left; margin-right: 0.5em;" },
+ static = "icons/16/eye.png"
+ }
+ end
+ slot.put(" ")
+ ui.tag{
+ tag = "span",
+ attr = { style = "font-size: 75%; font-weight: bold; background-color: #ccc; display: block; margin-bottom: 1ex;"},
+ content = issue.area.name .. ", " .. _("Issue ##{id}", { id = issue.id })
+ }
+ else
+ ui.tag{
+ tag = "span",
+ attr = { style = "font-size: 75%; background-color: #ccc; display: block; margin-bottom: 1ex;"},
+ content = function() slot.put(" ") end
+ }
+ end
+
+ if timeline.member then
+ execute.view{
+ module = "member_image",
+ view = "_show",
+ params = {
+ member = timeline.member,
+ image_type = "avatar",
+ show_dummy = true
+ }
+ }
+ ui.link{
+ content = timeline.member.name,
+ module = "member",
+ view = "show",
+ id = timeline.member.id
+ }
+ end
+ if timeline.issue then
+ local initiatives_selector = timeline.issue
+ :get_reference_selector("initiatives")
+ execute.view{
+ module = "initiative",
+ view = "_list",
+ params = {
+ issue = timeline.issue,
+ initiatives_selector = initiatives_selector,
+ per_page = initiatives_per_page,
+ no_sort = true,
+ limit = initiatives_per_page
+ }
+ }
+ elseif initiative then
+ execute.view{
+ module = "initiative",
+ view = "_list",
+ params = {
+ issue = initiative.issue,
+ initiatives_selector = Initiative:new_selector():add_where{ "initiative.id = ?", initiative.id },
+ per_page = initiatives_per_page,
+ no_sort = true
+ }
+ }
+ end
+ if timeline.suggestion then
+ ui.link{
+ module = "suggestion",
+ view = "show",
+ id = timeline.suggestion.id,
+ content = timeline.suggestion.name
+ }
+ end
+ end
+ },
+ }
+ }
+ end
+}
\ No newline at end of file
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/timeline/index.lua
--- a/app/main/timeline/index.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/timeline/index.lua Fri Jan 22 12:00:00 2010 +0100
@@ -1,3 +1,12 @@
+execute.view{
+ module = "timeline",
+ view = "_constants"
+}
+
+local options_box_count = param.get("options_box_count", atom.number) or 1
+if options_box_count > 10 then
+ options_box_count = 10
+end
local function format_dow(dow)
local dows = {
@@ -11,141 +20,331 @@
}
return dows[dow+1]
end
+slot.put_into("title", _"Timeline")
-slot.put_into("title", _"Global timeline")
+slot.select("actions", function()
+ local setting_key = "liquidfeedback_frontend_timeline_current_options"
+ local setting = Setting:by_pk(app.session.member.id, setting_key)
+ local current_options = ""
+ if setting then
+ current_options = setting.value
+ end
+ local setting_maps = app.session.member:get_setting_maps_by_key("timeline_filters")
+ for i, setting_map in ipairs(setting_maps) do
+ local active
+ local options_string = setting_map.value
+ local name = setting_map.subkey
+ if options_string == current_options then
+ active = true
+ end
+ timeline_params.date = param.get("date")
+ ui.link{
+ attr = { class = active and "action_active" or nil },
+ content = function()
+ ui.image{ static = "icons/16/time.png" }
+ slot.put(encode.html(name))
+ end,
+ module = 'timeline',
+ action = 'update',
+ params = {
+ options_string = options_string
+ },
+ }
+ end
+ if #setting_maps > 0 then
+ ui.link{
+ content = function()
+ ui.image{ static = "icons/16/wrench.png" }
+ slot.put(_"Manage filter")
+ end,
+ module = "timeline",
+ view = "list_filter",
+ }
+ end
+ ui.link{
+ content = function()
+ ui.image{ static = "icons/16/bullet_disk.png" }
+ slot.put(_"Save current filter")
+ end,
+ module = "timeline",
+ view = "save_filter",
+ attr = {
+ onclick = "el=document.getElementById('timeline_save');el.checked=true;el.form.submit();return(false);"
+ }
+ }
+end)
+util.help("timeline.index", _"Timeline")
ui.form{
- attr = { class = "vertical" },
module = "timeline",
- view = "index",
- method = "get",
+ action = "update",
content = function()
- 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; ")
- local today = tmp[1].date
- for i, record in ipairs(tmp) do
- local content
- if i == 1 then
- content = _"Today"
- elseif i == 2 then
- content = _"Yesterday"
- else
- content = format_dow(record.dow)
- end
- ui.link{
- content = content,
- attr = { onclick = "el = document.getElementById('timeline_search_date'); el.value = '" .. tostring(record.date) .. "'; el.form.submit(); return(false);" },
- module = "timeline",
- view = "index",
- params = { date = record.date }
- }
- slot.put(" ")
+
+
+ ui.tag{
+ tag = "label",
+ attr = { style = "font-size: 130%;" },
+ content = _"Date" .. ":"
+ }
+ slot.put(" ")
+ local date = param.get("date")
+ if not date or #date == 0 then
+ date = tostring(db:query("select now()::date as date")[1].date)
end
- ui.field.hidden{
- attr = { id = "timeline_search_date" },
- name = "date",
- value = param.get("date") or today
+ ui.tag{
+ tag = "input",
+ attr = {
+ type = "text",
+ id = "timeline_search_date",
+ style = "width: 10em;",
+ onchange = "this.form.submit();",
+ name = "date",
+ value = date
+ },
+ content = function() end
+ }
+
+ ui.script{ static = "gregor.js/gregor.js" }
+ util.gregor("timeline_search_date", "document.getElementById('timeline_search_date').form.submit();")
+
+
+ ui.link{
+ attr = { style = "margin-left: 1em; font-size: 130%; font-weight: bold;", onclick = "document.getElementById('timeline_search_date').form.submit();return(false);" },
+ content = function()
+ ui.image{
+ attr = { style = "margin-right: 0.25em;" },
+ static = "icons/16/magnifier.png"
+ }
+ slot.put(_"Search")
+ end,
+ external = "#",
+ }
+ local show_options = param.get("show_options", atom.boolean)
+ ui.link{
+ 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);" },
+ content = function()
+ ui.image{
+ attr = { style = "margin-right: 0.25em;" },
+ static = "icons/16/text_list_bullets.png"
+ }
+ slot.put(not show_options and _"Show filter details" or _"Hide filter details")
+ end,
+ external = "#",
+ }
+
+ ui.field.boolean{
+ attr = { id = "timeline_show_options", style = "display: none;", onchange="this.form.submit();" },
+ name = "show_options",
+ value = param.get("show_options", atom.boolean)
+ }
+
+ ui.field.boolean{
+ attr = { id = "timeline_save", style = "display: none;", onchange="this.form.submit();" },
+ name = "save",
+ value = false
}
- ui.field.select{
- attr = { onchange = "this.form.submit();" },
- name = "per_page",
- label = _"Issues per page",
- foreign_records = {
- { id = "10", name = "10" },
- { id = "25", name = "25" },
- { id = "50", name = "50" },
- { id = "100", name = "100" },
- { id = "250", name = "250" },
- { id = "all", name = _"All" },
+
+ ui.container{
+ attr = {
+ id = "timeline_options_boxes",
+ class = "vertical",
+ style = not param.get("show_options", atom.boolean) and "display: none;" or nil
},
- foreign_id = "id",
- foreign_name = "name",
- value = param.get("per_page")
- }
- local initiatives_per_page = param.get("initiatives_per_page", atom.integer) or 3
+ content = function()
+
+ local function option_field(event_ident, filter_ident)
+ local param_name
+ if not filter_ident then
+ param_name = "option_" .. event_ident
+ else
+ param_name = "option_" .. event_ident .. "_" .. filter_ident
+ end
+ local value = param.get(param_name, atom.boolean)
+ ui.field.boolean{
+ attr = { id = param_name },
+ name = param_name,
+ value = value,
+ }
+ end
+
+ local function filter_option_fields(event_ident, filter_idents)
+
+ for i, filter_ident in ipairs(filter_idents) do
+ slot.put("")
+ option_field(event_ident, filter_ident)
+ slot.put(" | ")
+ ui.tag{
+ attr = { ["for"] = "option_" .. event_ident .. "_" .. filter_ident },
+ tag = "label",
+ content = filter_names[filter_ident]
+ }
+ slot.put(" | ")
+ end
+
+ end
- ui.field.select{
- attr = { onchange = "this.form.submit();" },
- name = "initiatives_per_page",
- label = _"Initiatives per page",
- foreign_records = {
- { id = 1, name = "1" },
- { id = 3, name = "3" },
- { id = 5, name = "5" },
- { id = 10, name = "10" },
- { id = 25, name = "25" },
- { id = 50, name = "50" },
- },
- foreign_id = "id",
- foreign_name = "name",
- value = initiatives_per_page
+ local event_groups = {
+ {
+ title = _"Issue events",
+ event_idents = {
+ "issue_created",
+ "issue_canceled",
+ "issue_accepted",
+ "issue_half_frozen",
+ "issue_finished_without_voting",
+ "issue_voting_started",
+ "issue_finished_after_voting",
+ },
+ filter_idents = {
+ "membership",
+ "interested"
+ }
+ },
+ {
+ title = _"Initiative events",
+ event_idents = {
+ "initiative_created",
+ "initiative_revoked",
+ "draft_created",
+ "suggestion_created",
+ },
+ filter_idents = {
+ "membership",
+ "interested",
+ "supporter",
+ "potential_supporter",
+ "initiator"
+ }
+ }
+ }
+
+ slot.put("
")
+
+ slot.put("")
+
+ for i_event_group, event_group in ipairs(event_groups) do
+ slot.put("")
+ slot.put("")
+ slot.put(event_group.title)
+ slot.put(" | ")
+ slot.put(_"Show only events which match... (or associtated)")
+ slot.put(" | ")
+ slot.put("
")
+ local event_idents = event_group.event_idents
+ for i, event_ident in ipairs(event_idents) do
+ slot.put("")
+ option_field(event_ident)
+ slot.put(" | ")
+ ui.tag{
+ attr = { ["for"] = "option_" .. event_ident },
+ tag = "label",
+ content = event_names[event_ident]
+ }
+ slot.put(" | ")
+ filter_option_fields(event_ident, event_group.filter_idents)
+ slot.put("
")
+ end
+ end
+
+ slot.put("
")
+
+ end
}
end
}
local date = param.get("date")
-if not date then
+if not date or #date == 0 then
date = "today"
end
-local issues_selector = db:new_selector()
-issues_selector._class = Issue
+
+local timeline_selector
+
+for event, event_name in pairs(event_names) do
+
+ if param.get("option_" .. event, atom.boolean) then
+
+ local tmp = Timeline:new_selector()
+ :add_where{ "occurrence::date = ?", date }
+
+ :left_join("draft", nil, "draft.id = timeline.draft_id")
+ :left_join("suggestion", nil, "suggestion.id = timeline.suggestion_id")
+ :left_join("initiative", nil, "initiative.id = timeline.initiative_id or initiative.id = draft.initiative_id or initiative.id = suggestion.initiative_id")
+ :left_join("issue", nil, "issue.id = timeline.issue_id or issue.id = initiative.issue_id")
+ :left_join("area", nil, "area.id = issue.area_id")
+
+ :left_join("interest", "_interest", { "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id} )
+ :left_join("membership", "_membership", { "_membership.area_id = area.id AND _membership.member_id = ?", app.session.member.id} )
+ :left_join("initiator", "_initiator", { "_initiator.initiative_id = initiative.id AND _initiator.member_id = ?", app.session.member.id} )
+ :left_join("supporter", "_supporter", { "_supporter.initiative_id = initiative.id AND _supporter.member_id = ?", app.session.member.id} )
+
+ :add_field("(_interest.member_id NOTNULL)", "is_interested")
+ :add_field("(_initiator.member_id NOTNULL)", "is_initiator")
+ :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")
+ :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")
+ -- :left_join("member", nil, "member.id = timeline.member_id")
+
+ tmp:add_where{ "event = ?", event }
+
+ local filters = {}
+ if param.get("option_" .. event .. "_membership", atom.boolean) then
+ 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"
+ end
+
+ if param.get("option_" .. event .. "_supporter", atom.boolean) then
+ 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))"
+ end
+
+ if param.get("option_" .. event .. "_potential_supporter", atom.boolean) then
+ 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))"
+ end
-issues_selector
- :add_field("*")
- :add_where{ "sort::date = ?", date }
- :add_from{ "($) as issue", {
- Issue:new_selector()
- :add_field("''", "old_state")
- :add_field("'new'", "new_state")
- :add_field("created", "sort")
- :union(Issue:new_selector()
- :add_field("'new'", "old_state")
- :add_field("'accepted'", "new_state")
- :add_field("accepted", "sort")
- :add_where("accepted NOTNULL")
- ):union(Issue:new_selector()
- :add_field("'accepted'", "old_state")
- :add_field("'frozen'", "new_state")
- :add_field("half_frozen", "sort")
- :add_where("half_frozen NOTNULL")
- ):union(Issue:new_selector()
- :add_field("'frozen'", "old_state")
- :add_field("'voting'", "new_state")
- :add_field("fully_frozen", "sort")
- :add_where("fully_frozen NOTNULL")
- ):union(Issue:new_selector()
- :add_field("'new'", "old_state")
- :add_field("'cancelled'", "new_state")
- :add_field("closed", "sort")
- :add_where("closed NOTNULL AND accepted ISNULL")
- ):union(Issue:new_selector()
- :add_field("'accepted'", "old_state")
- :add_field("'cancelled'", "new_state")
- :add_field("closed", "sort")
- :add_where("closed NOTNULL AND half_frozen ISNULL AND accepted NOTNULL")
- ):union(Issue:new_selector()
- :add_field("'frozen'", "old_state")
- :add_field("'cancelled'", "new_state")
- :add_field("closed", "sort")
- :add_where("closed NOTNULL AND fully_frozen ISNULL AND half_frozen NOTNULL")
- ):union(Issue:new_selector()
- :add_field("'voting'", "old_state")
- :add_field("'finished'", "new_state")
- :add_field("closed", "sort")
- :add_where("closed NOTNULL AND fully_frozen NOTNULL AND half_frozen ISNULL")
- )
+ if param.get("option_" .. event .. "_interested", atom.boolean) then
+ 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"
+ end
+
+ if param.get("option_" .. event .. "_initiator", atom.boolean) then
+ 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"
+ end
+
+ if #filters > 0 then
+ local filter_string = "(" .. table.concat(filters, ") OR (") .. ")"
+ tmp:add_where{ filter_string, app.session.member.id }
+ end
+
+ if not timeline_selector then
+ timeline_selector = tmp
+ else
+ timeline_selector:union_all(tmp)
+ end
+ end
+end
+
+if timeline_selector then
+
+ local initiatives_per_page = param.get("initiatives_per_page", atom.number)
+
+ local outer_timeline_selector = db:new_selector()
+ outer_timeline_selector._class = Timeline
+ outer_timeline_selector:add_field{ "timeline.*" }
+ outer_timeline_selector:from({"($)", { timeline_selector }}, "timeline" )
+ outer_timeline_selector:add_order_by("occurrence DESC")
+
+ slot.put("
")
+ execute.view{
+ module = "timeline",
+ view = "_list",
+ params = {
+ timeline_selector = outer_timeline_selector,
+ per_page = param.get("per_page", atom.number),
+ event_names = event_names,
+ initiatives_per_page = initiatives_per_page
+ }
}
-}
+
+else
-execute.view{
- module = "issue",
- view = "_list",
- params = {
- issues_selector = issues_selector,
- initiatives_per_page = param.get("initiatives_per_page", atom.number),
- initiatives_no_sort = true,
- no_filter = true,
- no_sort = true,
- per_page = param.get("per_page"),
- }
-}
+ slot.put(_"No events selected to list")
+
+end
\ No newline at end of file
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/timeline/list_filter.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/main/timeline/list_filter.lua Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,38 @@
+slot.put_into("title", _"Manage timeline filters")
+
+slot.select("actions", function()
+ ui.link{
+ content = function()
+ ui.image{ static = "icons/16/cancel.png" }
+ slot.put(_"Back to timeline")
+ end,
+ module = "timeline",
+ action = "update"
+ }
+end)
+
+local timeline_filters = app.session.member:get_setting_maps_by_key("timeline_filters")
+
+ui.list{
+ records = timeline_filters,
+ columns = {
+ {
+ name = "subkey"
+ },
+ {
+ content = function(timeline_filter)
+ ui.link{
+ attr = { class = "action" },
+ content = function()
+ slot.put(_"Delete filter")
+ end,
+ module = "timeline",
+ action = "delete_filter",
+ params = {
+ name = timeline_filter.subkey
+ }
+ }
+ end
+ }
+ }
+}
\ No newline at end of file
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/timeline/save_filter.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/app/main/timeline/save_filter.lua Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,27 @@
+slot.put_into("title", _"Save timeline filters")
+
+slot.select("actions", function()
+ ui.link{
+ content = function()
+ ui.image{ static = "icons/16/cancel.png" }
+ slot.put(_"Cancel")
+ end,
+ module = "timeline",
+ view = "index"
+ }
+end)
+
+ui.form{
+ attr = { class = "vertical" },
+ module = "timeline",
+ action = "save",
+ content = function()
+ ui.field.text{
+ label = _"Name",
+ name = "name",
+ }
+ ui.submit{
+ text = _"Save"
+ }
+ end
+}
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/vote/list.lua
--- a/app/main/vote/list.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/vote/list.lua Fri Jan 22 12:00:00 2010 +0100
@@ -109,7 +109,9 @@
id = "entry_" .. tostring(initiative.id)
},
content = function()
- local initiators = initiative.initiating_members
+ local initiators_selector = initiative:get_reference_selector("initiating_members")
+ :add_where("accepted")
+ local initiators = initiators_selector:exec()
local initiator_names = {}
for i, initiator in ipairs(initiators) do
initiator_names[#initiator_names+1] = initiator.name
diff -r 72c5e0ee7c98 -r 77d58efe99fd app/main/vote/show_incoming.lua
--- a/app/main/vote/show_incoming.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/app/main/vote/show_incoming.lua Fri Jan 22 12:00:00 2010 +0100
@@ -6,14 +6,14 @@
:join("delegating_voter", nil, "delegating_voter.member_id = member.id")
:add_where{ "delegating_voter.issue_id = ?", issue.id }
:add_where{ "delegating_voter.delegate_member_ids[1] = ?", member.id }
- :add_field{ "delegating_voter.weight" }
+ :add_field("delegating_voter.weight", "voter_weight")
execute.view{
module = "member",
view = "_list",
- params = {
+ params = {
members_selector = members_selector,
- issue = issue,
+ initiative = initiative,
trustee = member
}
}
\ No newline at end of file
diff -r 72c5e0ee7c98 -r 77d58efe99fd config/default.lua
--- a/config/default.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/config/default.lua Fri Jan 22 12:00:00 2010 +0100
@@ -1,5 +1,5 @@
config.app_name = "LiquidFeedback"
-config.app_version = "beta6"
+config.app_version = "beta7"
config.app_title = config.app_name .. " (" .. request.get_config_name() .. " environment)"
diff -r 72c5e0ee7c98 -r 77d58efe99fd env/util/gregor.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/env/util/gregor.lua Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,11 @@
+function util.gregor(el_id)
+ ui.script{ script =
+ 'gregor_addGui({' ..
+ 'element_id: "' .. el_id .. '",' ..
+ 'month_names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],' ..
+ 'weekday_names: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],' ..
+ 'week_mode: "iso",' ..
+ 'week_numbers: "left",' ..
+ '});'
+ }
+end
\ No newline at end of file
diff -r 72c5e0ee7c98 -r 77d58efe99fd locale/help/initiative.revoke.de.txt
--- a/locale/help/initiative.revoke.de.txt Sun Jan 10 12:00:00 2010 +0100
+++ b/locale/help/initiative.revoke.de.txt Fri Jan 22 12:00:00 2010 +0100
@@ -1,2 +1,2 @@
=Initiative zurückziehen=
-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.
+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.
diff -r 72c5e0ee7c98 -r 77d58efe99fd locale/help/timeline.index.de.txt
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/locale/help/timeline.index.de.txt Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,4 @@
+=Zeitachse=
+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.
+=Filter speichern=
+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.
diff -r 72c5e0ee7c98 -r 77d58efe99fd locale/motd/de_public.txt
diff -r 72c5e0ee7c98 -r 77d58efe99fd locale/translations.de.lua
--- a/locale/translations.de.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/locale/translations.de.lua Fri Jan 22 12:00:00 2010 +0100
@@ -1,5 +1,6 @@
#!/usr/bin/env lua
return {
+["#{count} more initiatives"] = "#{count} weitere Initiativen";
["#{interested_issues_to_vote_count} issue(s) you are interested in"] = "#{interested_issues_to_vote_count} Themen, die Dich interessieren";
["#{issues_to_vote_count} issue(s)"] = "#{issues_to_vote_count} Themen";
["#{number} Image(s) has been deleted"] = "Es wurde(n) #{number} Bild(er) gelöscht";
@@ -25,7 +26,6 @@
["Administrator"] = "Administrator";
["Admission time"] = "Zeit für die Zulassung";
["Admitted"] = "zugelassen";
-["All"] = "Alle";
["Any"] = "Alle";
["Are you sure?"] = "Sicher?";
["Area"] = "Themenbereich";
@@ -40,6 +40,7 @@
["Autoreject is on."] = "Auto-Ablehnen ist an";
["Avatar"] = "Avatar";
["Back"] = "Zurück";
+["Back to timeline"] = "Zurück zur Zeitachse";
["Become a member"] = "Mitglied werden";
["Birthday"] = "Geburtstag";
["Can't remove last initiator"] = "Der letzte Initiator kann nicht entfernt werden";
@@ -78,8 +79,10 @@
["Created at"] = "Erzeugt am/um";
["Current draft"] = "Aktueller Entwurf";
["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:";
+["Date"] = "Datum";
["Degree"] = "Grad";
["Delegations"] = "Delegationen";
+["Delete filter"] = "Filter löschen";
["Description"] = "Beschreibung";
["Details"] = "Details";
["Developer features"] = "Entwicklerfunktionen";
@@ -100,6 +103,7 @@
["Edit initiative"] = "Initiative bearbeiten";
["Edit my page"] = "Meine Seite bearbeiten";
["Edit my profile"] = "Mein Profil bearbeiten";
+["Edit timeline filter"] = "Zeitachsen-Filter bearbeiten";
["Email address"] = "E-Mail-Adresse";
["Email address confirmation"] = "Bestätigung der E-Mail-Adresse";
["Email address is confirmed now"] = "E-Mail-Adresse ist jetzt bestätigt";
@@ -117,11 +121,11 @@
["Fully frozen at"] = "Ganz eingefroren am/um";
["Global delegation"] = "Globale Delegation";
["Global delegation active"] = "Globale Delegation aktiv";
-["Global timeline"] = "Globale Zeitlinie";
["Half frozen at"] = "Halb eingefroren am/um";
["Hello "] = "Hallo ";
["Help for: #{text}"] = "Hilfe zu: #{text}";
["Hide"] = "Verstecken";
+["Hide filter details"] = "Filter-Details verstecken";
["Hide this help message"] = "Diesen Hilfetext ausblenden";
["Home"] = "Startseite";
["I accept the terms of use by checking the following checkbox:"] = "Ich akzeptiere die Nutzungsbedingungen durch Auswahl der folgenden Ankreuzbox:";
@@ -131,14 +135,16 @@
["Images"] = "Bilder";
["In discussion"] = "In Diskussion";
["Incoming delegations"] = "Eingehende Delegationen";
+["Initiated"] = "Initiert";
["Initiated initiatives"] = "Initierte Initiativen";
+["Initiative events"] = "Initiativen-Ereignisse";
["Initiative is revoked now"] = "Initiative ist jetzt zurückgezogen";
["Initiative quorum"] = "Quorum Inititive";
+["Initiative revoked"] = "Initiative zurückgezogen";
["Initiative successfully created"] = "Initiative erfolgreich erzeugt";
["Initiative successfully updated"] = "Initiative erfolgreich aktualisiert";
["Initiative: '#{name}'"] = "Initiative: '#{name}'";
["Initiatives"] = "Initiativen";
-["Initiatives per page"] = "Initiativen je Seite";
["Initiatives that invited you to become initiator:"] = "Initiative, die Dich eingeladen haben, Initiator zu werden:";
["Initiator"] = "Initiator";
["Initiators"] = "Initiatoren";
@@ -154,16 +160,20 @@
["Invite code"] = "Invite-Code";
["Invite initiator"] = "Initiator einladen";
["Invited"] = "Eingeladen";
-["Inviting initiator"] = "Initiatoren einladen";
["Issue"] = "Thema";
["Issue ##{id}"] = "Thema ##{id}";
["Issue ##{id} (#{policy_name})"] = "Thema ##{id} (#{policy_name})";
+["Issue accepted"] = "Thema akzeptiert";
+["Issue canceled"] = "Thema abgebrochen";
["Issue delegation"] = "Issue-Delegation";
["Issue delegation active"] = "Delegation für Thema aktiv";
+["Issue events"] = "Themen-Ergeignisse";
+["Issue finished"] = "Thema abgeschlossen";
+["Issue finished without voting"] = "Thema ohne Abstimmung abgeschlossen";
+["Issue frozen"] = "Thema eingefroren";
["Issue policy"] = "Regelwerk für Thema";
["Issue quorum"] = "Quorum Thema";
["Issues"] = "Themen";
-["Issues per page"] = "Themen je Seite";
["JavaScript is disabled or not available."] = "JavaScript ist abgeschaltet oder nicht verfügbar.";
["Last author"] = "Letzter Autor";
["Last snapshot:"] = "Letzte Auszählung:";
@@ -175,6 +185,8 @@
["Login successful!"] = "Anmeldung erfolgreich";
["Logout"] = "Abmelden";
["Logout successful"] = "Abmeldung erfolgreich";
+["Manage filter"] = "Filter verwalten";
+["Manage timeline filters"] = "Zeitachsen-Filter verwalten";
["Mark suggestion as implemented and express dissatisfaction"] = "Anregung als umgesetzt markieren und Unzufriedenheit ausdrücken";
["Mark suggestion as implemented and express satisfaction"] = "Anregung als umgesetzt markieren und Zufriedenheit ausdrücken";
["Mark suggestion as not implemented and express dissatisfaction"] = "Anregung als nicht umgesetzt markieren und Unzufriedenheit ausdrücken";
@@ -191,6 +203,7 @@
["Member list"] = "Mitgliederliste";
["Member name"] = "Mitglied Name";
["Member name history for '#{name}'"] = "Namenshistorie für '#{name}'";
+["Member of area"] = "Mitglied des Themenbereichs";
["Member page"] = "Mitgliederseite";
["Member successfully registered"] = "Mitglied erfolgreich registriert";
["Member successfully updated"] = "Mitglied erfolgreich aktualisert";
@@ -207,16 +220,21 @@
["My opinion"] = "Meine Meinung";
["Name"] = "Name";
["New"] = "Neu";
+["New draft"] = "Neuer Entwurf";
["New draft has been added to initiative"] = "Neuer Entwurf wurde der Initiative hinzugefügt";
["New draft revision"] = "Neue Revision des Entwurfs";
+["New initiative"] = "Neue Initiative";
+["New issue"] = "Neues Thema";
["New password"] = "Neues Kennwort";
["New passwords does not match."] = "Du hast nicht zweimal das gleiche Kennwort eingegeben";
["New passwords is too short."] = "Das neue Kennwort ist zu kurz";
+["New suggestion"] = "Neue Anregung";
["Newest"] = "Neueste";
["Next state"] = "Nächster Zustand";
["No"] = "Nein";
["No changes to your images were made"] = "An Deinen Bildern wurde nichts geändert";
["No delegation"] = "Keine Delegation";
+["No events selected to list"] = "Keine Ereignisse ausgewählt";
["No membership at all"] = "Gar keine Mitgliedschaft";
["No support at all"] = "Gar keine Unterstützung";
["Not a member"] = "Kein Mitglied";
@@ -262,6 +280,7 @@
["Population"] = "Grundgesamtheit";
["Posts"] = "Ämter";
["Potential support"] = "Potentielle Unterstützung";
+["Potential supported"] = "Potentiell unterstützt";
["Potential supporter"] = "Potentielle Unterstützer";
["Profession"] = "Beruf";
["Profile"] = "Profil";
@@ -284,7 +303,10 @@
["Remove my interest"] = "Interesse abmelden";
["Remove my membership"] = "Mitgliedschaft aufgeben";
["Remove my support from this initiative"] = "Meine Unterstützung der Initiative entziehen";
+["Rename"] = "Umbenennen";
+["Rename filter"] = "Filter umbenennen";
["Repeat new password"] = "Neues Kennwort wiederholen";
+["Replace filter"] = "Filter ersetzen";
["Request password reset link"] = "Link zum Rücksetzen des Kennworts anfordern";
["Reset code"] = "Rücksetzcode";
["Reset code is invalid!"] = "Rücksetzcode ist ungültig";
@@ -295,12 +317,16 @@
["Revoked at"] = "Zurückgezogen am/um";
["Saturday"] = "Samstag";
["Save"] = "Speichern";
-["Saved as contact"] = "Als Kontakt speichern";
+["Save as new filter"] = "Als neuen Filter speichern";
+["Save current filter"] = "Aktuellen Filter speichern";
+["Save timeline filters"] = "Zeitachsen-Filter speichern";
+["Saved as contact"] = "Als Kontakt gespeichert";
["Search"] = "Suchen";
["Search initiatives"] = "Suche Initiativen";
["Search issues"] = "Suche Themen";
["Search members"] = "Suche Mitglieder";
["Search results for: '#{search}'"] = "Suchergebnisse für: '#{search}'";
+["Select filter to replace"] = "Wähle zu ersetzenden Filter";
["Set URL"] = "URL setzen";
["Set area delegation"] = "Delegation für Themengebiet festlegen";
["Set autoreject"] = "Auto-Ablehnen anschalten";
@@ -316,9 +342,11 @@
["Show areas in use"] = "Zeige verwendete Themenbereiche";
["Show areas not in use"] = "Zeige nicht verwendente Themenbereiche";
["Show diff"] = "Änderungen anzeigen";
+["Show filter details"] = "Zeige Filter-Details";
["Show locked members"] = "Zeige gesperrte Mitglieder";
["Show member"] = "Mitglied anzeigen";
["Show name history"] = "Namenshistorie zeigen";
+["Show only events which match... (or associtated)"] = "Zeige nur Ereignisse welche folgendes erfüllen... (oder-verknüpft)";
["Software"] = "Software";
["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.";
["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.";
@@ -344,10 +372,12 @@
["Sunday"] = "Sonntag";
["Support"] = "Unterstützung";
["Support this initiative"] = "Diese Initiative unterstützen";
+["Supported"] = "Unterstützt";
["Supported initiatives"] = "Unterstützte Initiativen";
["Supporter"] = "Unterstützer";
["Terms accepted"] = "Bedingungen akzeptiert";
["The code you've entered is invalid"] = "Der Code, den Du eingeben hast, ist nicht gültig!";
+["The draft of this initiative has been updated!"] = "Der Entwurfstext der Initiative wurde aktualisiert!";
["The drafts do not differ"] = "Die Entwürfe unterscheiden sich nicht";
["The initiators suggest to support the following initiative:"] = "Die Initiatoren empfehlen folgende Initiative zu unterstützen:";
["This initiative has been revoked at #{revoked}"] = "Diese Initiative wurde am/um #{revoked} zurückgezogen";
@@ -369,8 +399,8 @@
["This username is too short!"] = "Dieser Benutzername ist zu kurz!";
["Thursday"] = "Donnerstag";
["Time left"] = "Restzeit";
+["Timeline"] = "Zeitachse";
["Title (80 chars max)"] = "Title (max. 80 Zeichen)";
-["Today"] = "Heute";
["Traditional wiki syntax"] = "Traditionaller Wiki-Syntax";
["Trustee"] = "Bevollmächtigter";
["Tuesday"] = "Dienstag";
@@ -393,19 +423,23 @@
["Voting has not started yet."] = "Die Abstimmung hat noch nicht begonnen.";
["Voting proposal"] = "Abstimmungsvorlage";
["Voting requests"] = "Abstimmanträge";
+["Voting started"] = "Abstimmung begonnen";
["Voting time"] = "Zeit für die Abstimmung";
["Website"] = "Webseite";
["Wednesday"] = "Mittwoch";
["Wiki engine"] = "Wiki engine";
["Yes"] = "Ja";
-["Yesterday"] = "Gestern";
["You are already initator"] = "Du bist bereits Initiator";
["You are already not supporting this initiative"] = "Diese Initiative hat bereits keine Unterstützung von Dir";
["You are already supporting the latest draft"] = "Du unterstützt bereits den neuesten Entwurf";
["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!";
+["You are iniator of this initiative"] = "Du bist Initiator dieser Initiative";
+["You are interested in this issue"] = "Du bist an diesem Thema interessiert";
["You are invited to become initiator of this initiative."] = "Du bist eingeladen Initiator dieser Initiative zu werden.";
["You are member"] = "Du bist Mitglied";
["You are now initiator of this initiative"] = "Du bist jetzt Initiator dieser Initiative";
+["You are potential supporter of this initiative"] = "Du bist potentieller Unterstützer dieser Initiative";
+["You are supporting this initiative"] = "Du unterstützt diese Initiative";
["You can't suggest the initiative you are revoking"] = "Du kannst nicht die Initiative empfehlen, die Du löschen möchtest";
["You didn't saved any member as contact yet."] = "Du hast noch kein Mitglied als Kontakt gespeichert!";
["You have saved this member as contact"] = "Du hast das Mitglied als Kontakt gespeichert";
diff -r 72c5e0ee7c98 -r 77d58efe99fd model/member.lua
--- a/model/member.lua Sun Jan 10 12:00:00 2010 +0100
+++ b/model/member.lua Fri Jan 22 12:00:00 2010 +0100
@@ -310,3 +310,31 @@
}
return success
end
+
+function Member.object:get_setting_by_key(key)
+end
+
+function Member.object:set_setting(key, value)
+end
+
+function Member.object:get_setting_maps_by_key(key)
+ return SettingMap:new_selector()
+ :add_where{ "member_id = ?", self.id }
+ :add_where{ "key = ?", key }
+ :add_order_by("subkey")
+ :exec()
+end
+
+function Member.object:get_setting_map_by_key_and_subkey(key, subkey)
+ return SettingMap:new_selector()
+ :add_where{ "member_id = ?", self.id }
+ :add_where{ "key = ?", key }
+ :add_where{ "subkey = ?", subkey }
+ :add_order_by("subkey")
+ :optional_object_mode()
+ :exec()
+end
+
+function Member.object:set_setting_map(key, subkey, value)
+
+end
diff -r 72c5e0ee7c98 -r 77d58efe99fd model/setting_map.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/model/setting_map.lua Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,3 @@
+SettingMap = mondelefant.new_class()
+SettingMap.table = 'setting_map'
+SettingMap.primary_key = { "key", "subkey" }
\ No newline at end of file
diff -r 72c5e0ee7c98 -r 77d58efe99fd model/timeline.lua
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/model/timeline.lua Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,42 @@
+Timeline = mondelefant.new_class()
+Timeline.table = 'timeline'
+
+Timeline:add_reference{
+ mode = '11',
+ to = "Member",
+ this_key = 'member_id',
+ that_key = 'id',
+ ref = 'member'
+}
+
+Timeline:add_reference{
+ mode = '11',
+ to = "Issue",
+ this_key = 'issue_id',
+ that_key = 'id',
+ ref = 'issue'
+}
+
+Timeline:add_reference{
+ mode = '11',
+ to = "Initiative",
+ this_key = 'initiative_id',
+ that_key = 'id',
+ ref = 'initiative'
+}
+
+Timeline:add_reference{
+ mode = '11',
+ to = "Draft",
+ this_key = 'draft_id',
+ that_key = 'id',
+ ref = 'draft'
+}
+
+Timeline:add_reference{
+ mode = '11',
+ to = "Suggestion",
+ this_key = 'suggestion_id',
+ that_key = 'id',
+ ref = 'suggestion'
+}
diff -r 72c5e0ee7c98 -r 77d58efe99fd static/gregor.js/gregor.css
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/gregor.js/gregor.css Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,92 @@
+.gregor_sheet {
+ border: 1px solid black;
+ border-collapse: collapse;
+ margin: 0;
+ padding: 0;
+ background: #fff;
+ color: #000;
+}
+.gregor_sheet th, .gregor_sheet td {
+ margin: 0;
+ font-size: 70%;
+}
+.gregor_year, .gregor_month {
+ background-color: #ddd;
+ border-bottom: 1px solid black;
+ font-weight: normal;
+ text-align: center;
+}
+.gregor_year a, .gregor_month a {
+ color: #000;
+ text-decoration: none;
+}
+.gregor_year a:hover, .gregor_month a:hover {
+ color: #f00;
+}
+.gregor_weekday {
+ font-size: 80%;
+ padding-top: 0.5em;
+}
+.gregor_day {
+ padding-bottom: 0.35em;
+}
+
+.gregor_weekday,
+.gregor_day {
+ padding-left: 0.25em;
+ padding-right: 0.25em;
+}
+.gregor_weeks_left .gregor_weekday,
+.gregor_weeks_left .gregor_day {
+ padding-left: 0;
+ padding-right: 0.5em;
+}
+.gregor_weeks_right .gregor_weekday,
+.gregor_weeks_right .gregor_day {
+ padding-left: 0.5em;
+ padding-right: 0;
+}
+.gregor_weekday {
+ text-align: center;
+}
+.gregor_weeks_left .gregor_week {
+ padding-left: 0.25em;
+ padding-right: 0.5em;
+}
+.gregor_weeks_right .gregor_week {
+ padding-left: 1em;
+ padding-right: 0.25em;
+}
+.gregor_week {
+ color: #666;
+ font-size: 80%;
+ font-weight: normal;
+ text-align: right;
+}
+td.gregor_day {
+ text-align: right;
+}
+td.gregor_day a {
+ background-color: #ddd;
+ color: #000;
+ display: block;
+ padding-bottom: 0.25ex;
+ padding-right: 0.25em;
+ padding-top: 0.25ex;
+ text-decoration: none;
+ width: 2em;
+}
+td.gregor_day.gregor_sat a {
+ background-color: #bbb;
+}
+td.gregor_day.gregor_sun a {
+ background-color: #ea9;
+}
+td.gregor_day.gregor_today a {
+ font-weight: bold;
+}
+td.gregor_day.gregor_selected a {
+ background-color: #444;
+ color: #fff;
+}
+
diff -r 72c5e0ee7c98 -r 77d58efe99fd static/gregor.js/gregor.js
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/static/gregor.js/gregor.js Fri Jan 22 12:00:00 2010 +0100
@@ -0,0 +1,845 @@
+//
+// Copyright (c) 2009 Public Software Group e. V., Berlin
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+// SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+
+//
+// All date calculations are based on the gregorian calender, also for
+// dates before 1582 (before the gegorian calendar was introduced).
+// The supported range is from January 1st 0001 up to December 31st 9999.
+//
+// gregor_daycount({year: , month: , day: }) returns
+// the number of days between the given date and January 1st 0001 (greg.).
+//
+// gregor_completeDate({year: , month: , day: }) returns
+// a structure (an object) with the following properties:
+// - daycount (days since January 1st 0001, see gregor_daycount)
+// - year (with century)
+// - month (from 1 to 12)
+// - day (from 1 to 28, 29, 30 or 31)
+// - iso_string (string with format YYYY-MM-DD)
+// - us_weekday (from 0 for Sunday to 6 for Monday)
+// - iso_weekday (from 0 for Monday to 6 for Sunday)
+// - iso_weekyear (Year containing greater part of week st. w. Monday)
+// - iso_week (from 1 to 52 or 53)
+// - us_week (from 1 to 53 or 54)
+//
+// The structure (the object) passed as parameter to gregor_daycount or
+// gregor_completeDate may describe a date in the following ways:
+// - daycount
+// - year, month, day
+// - year, us_week, us_weekday
+// - year, iso_week, iso_weekday
+// - iso_weekyear, iso_week, iso_weekday
+//
+// gregor_sheet({...}) returns a calendar sheet as DOM object. The
+// structure (the object) passed to the function as an argument is altered
+// by the function and used to store state variables. Initially it can
+// contain the following fields:
+// - year (year to show, defaults to todays year)
+// - month (month to show, defaults to todays month)
+// - today (structure describing a day, e.g. year, month, day)
+// - selected (structure describing a day, e.g. year, month, day)
+// - navigation ("enabled", "disabled", "hidden", default "enabled")
+// - week_mode ("iso" or "us", defaults to "iso")
+// - week_numbers ("left", "right" or null, defaults to null)
+// - month_names (e.g. ["January", "Feburary", ...])
+// - weekday_names (e.g. ["Mon", "Tue", ...] or ["Sun", "Mon", ...])
+// - day_callback (function to render a cell)
+// - select_callback (function to be called, when a date was selected)
+// - element (for internal use only)
+// If "today" is undefined, it is automatically intitialized with the
+// current clients date. If "selected" is undefined or null, no date is
+// be initially selected. It is mandatory to provide month_names and
+// weekday_names.
+//
+// gregor_addGui({...}) alters a referenced input field in a way that
+// focussing on it causes a calendar being shown at its right side, where a
+// date can be selected. The structure (the object) passed to this function
+// is only evaluated once, and never modified. All options except "element"
+// of the gregor_sheet({...}) function may be used as options to
+// gregor_addGui({...}) as well. In addition an "element_id" must be
+// provided as argument, containing the id of a text input field. The
+// behaviour caused by the options "selected" and "select_callback" are
+// slightly different: If "selected" === undefined, then the current value
+// of the text field referenced by "element_id" will be parsed and used as
+// date selection. If "selected" === null, then no date will be initially
+// selected, and the text field will be cleared. The "select_callback"
+// function is always called once with the pre-selected date (or with null,
+// if no date is initially selected). Whenever the selected date is changed
+// or unselected later, the callback function is called again with the new
+// date (or with null, in case of deselection).
+//
+// EXAMPLE:
+//
+//
+//
+//
+
+
+
+
+// Internal constants and helper functions for date calculation
+
+
+gregor_c1 = 365; // days of a non-leap year
+gregor_c4 = 4 * gregor_c1 + 1; // days of a full 4 year cycle
+gregor_c100 = 25 * gregor_c4 - 1; // days of a full 100 year cycle
+gregor_c400 = 4 * gregor_c100 + 1; // days of a full 400 year cycle
+
+gregor_normalMonthOffsets = [
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365
+];
+
+function gregor_getMonthOffset(year, month) {
+ if (
+ (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)) &&
+ month > 2
+ ) return gregor_normalMonthOffsets[month-1] + 1;
+ else return gregor_normalMonthOffsets[month-1];
+}
+
+function gregor_formatInteger(int, digits) {
+ var result = int.toFixed();
+ if (digits != null) {
+ while (result.length < digits) result = "0" + result;
+ }
+ return result;
+}
+
+
+
+// Calculate days since January 1st 0001 (Gegorian) for a given date
+
+
+function gregor_daycount(obj) {
+ if (
+ obj.daycount >= 0 && obj.daycount <= 3652058 && obj.daycount % 1 == 0
+ ) {
+ return obj.daycount;
+ } else if (
+ obj.year >= 1 && obj.year <= 9999 && obj.year % 1 == 0 &&
+ obj.month >= 1 && obj.month <= 12 && obj.month % 1 == 0 &&
+ obj.day >= 0 && obj.day <= 31 && obj.day % 1 == 0
+ ) {
+ var n400 = Math.floor((obj.year-1) / 400);
+ var r400 = (obj.year-1) % 400;
+ var n100 = Math.floor(r400 / 100);
+ var r100 = r400 % 100;
+ var n4 = Math.floor(r100 / 4);
+ var n1 = r100 % 4;
+ return (
+ gregor_c400 * n400 +
+ gregor_c100 * n100 +
+ gregor_c4 * n4 +
+ gregor_c1 * n1 +
+ gregor_getMonthOffset(obj.year, obj.month) + (obj.day - 1)
+ );
+ } else if (
+ (
+ (
+ obj.year >= 1 && obj.year <= 9999 &&
+ obj.year % 1 == 0 && obj.iso_weekyear == null
+ ) || (
+ obj.iso_weekyear >= 1 && obj.iso_weekyear <= 9999 &&
+ obj.iso_weekyear % 1 == 0 && obj.year == null
+ )
+ ) &&
+ obj.iso_week >= 0 && obj.iso_week <= 53 && obj.iso_week % 1 == 0 &&
+ obj.iso_weekday >= 0 && obj.iso_weekday <= 6 &&
+ obj.iso_weekday % 1 == 0
+ ) {
+ var jan4th = gregor_daycount({
+ year: (obj.year != null) ? obj.year : obj.iso_weekyear,
+ month: 1,
+ day: 4
+ });
+ var monday0 = jan4th - (jan4th % 7) - 7; // monday of week 0
+ return monday0 + 7 * obj.iso_week + obj.iso_weekday;
+ } else if (
+ obj.year >= 1 && obj.year <= 9999 && obj.year % 1 == 0 &&
+ obj.us_week >= 0 && obj.us_week <= 54 && obj.us_week % 1 == 0 &&
+ obj.us_weekday >= 0 && obj.us_weekday <= 6 && obj.us_weekday % 1 == 0
+ ) {
+ var jan1st = gregor_daycount({
+ year: obj.year,
+ month: 1,
+ day: 1
+ });
+ var sunday0 = jan1st - ((jan1st+1) % 7) - 7; // sunday of week 0
+ return sunday0 + 7 * obj.us_week + obj.us_weekday;
+ }
+}
+
+
+
+// Calculate all calendar related numbers for a given date
+
+
+function gregor_completeDate(obj) {
+ if (
+ obj.daycount >= 0 && obj.daycount <= 3652058 && obj.daycount % 1 == 0
+ ) {
+ var daycount = obj.daycount;
+ var n400 = Math.floor(daycount / gregor_c400);
+ var r400 = daycount % gregor_c400;
+ var n100 = Math.floor(r400 / gregor_c100);
+ var r100 = r400 % gregor_c100;
+ if (n100 == 4) { n100 = 3; r100 = gregor_c100; }
+ var n4 = Math.floor(r100 / gregor_c4);
+ var r4 = r100 % gregor_c4;
+ var n1 = Math.floor(r4 / gregor_c1);
+ var r1 = r4 % gregor_c1;
+ if (n1 == 4) { n1 = 3; r1 = gregor_c1; }
+ var year = 1 + 400 * n400 + 100 * n100 + 4 * n4 + n1;
+ var month = 1 + Math.floor(r1 / 31);
+ var monthOffset = gregor_getMonthOffset(year, month);
+ if (month < 12) {
+ var nextMonthOffset = gregor_getMonthOffset(year, month + 1);
+ if (r1 >= nextMonthOffset) {
+ month++;
+ monthOffset = nextMonthOffset;
+ }
+ }
+ var day = 1 + r1 - monthOffset;
+ var iso_string = ("" +
+ gregor_formatInteger(year, 4) + "-" +
+ gregor_formatInteger(month, 2) + "-" +
+ gregor_formatInteger(day, 2)
+ );
+ var us_weekday = (daycount+1) % 7;
+ var iso_weekday = daycount % 7;
+ var iso_weekyear = year;
+ if (
+ month == 1 && (
+ (day == 3 && iso_weekday == 6) ||
+ (day == 2 && iso_weekday >= 5) ||
+ (day == 1 && iso_weekday >= 4)
+ )
+ ) iso_weekyear--;
+ else if (
+ month == 12 && (
+ (day == 29 && iso_weekday == 0) ||
+ (day == 30 && iso_weekday <= 1) ||
+ (day == 31 && iso_weekday <= 2)
+ )
+ ) iso_weekyear++;
+ var jan4th = gregor_daycount({year: iso_weekyear, month: 1, day: 4});
+ var monday0 = jan4th - (jan4th % 7) - 7; // monday of week 0
+ var iso_week = Math.floor((daycount - monday0) / 7);
+ var jan1st = gregor_daycount({year: year, month: 1, day: 1});
+ var sunday0 = jan1st - ((jan1st+1) % 7) - 7; // sunday of week 0
+ var us_week = Math.floor((daycount - sunday0) / 7);
+ return {
+ daycount: daycount,
+ year: year,
+ month: month,
+ day: day,
+ iso_string: iso_string,
+ us_weekday: (daycount+1) % 7, // 0 = Sunday
+ iso_weekday: daycount % 7, // 0 = Monday
+ iso_weekyear: iso_weekyear,
+ iso_week: iso_week,
+ us_week: us_week
+ };
+ } else if (obj.daycount == null) {
+ var daycount = gregor_daycount(obj);
+ if (daycount != null) {
+ return gregor_completeDate({daycount: gregor_daycount(obj)});
+ }
+ }
+}
+
+
+
+// Test gregor_daycount and gregor_completeDate for consistency
+// (Debugging only)
+
+
+function gregor_test() {
+ for (i=650000; i<900000; i++) {
+ var obj = gregor_completeDate({daycount: i});
+ var j;
+ j = gregor_daycount({
+ year: obj.year,
+ month: obj.month,
+ day: obj.day
+ });
+ if (i != j) { alert("ERROR"); return; }
+ j = gregor_daycount({
+ iso_weekyear: obj.iso_weekyear,
+ iso_week: obj.iso_week,
+ iso_weekday: obj.iso_weekday
+ });
+ if (i != j) { alert("ERROR"); return; }
+ j = gregor_daycount({
+ year: obj.year,
+ iso_week:
+ (obj.iso_weekyear == obj.year + 1) ? 53 :
+ (obj.iso_weekyear == obj.year - 1) ? 0 :
+ obj.iso_week,
+ iso_weekday: obj.iso_weekday
+ });
+ if (i != j) { alert("ERROR"); return; }
+ j = gregor_daycount({
+ year: obj.year,
+ us_week: obj.us_week,
+ us_weekday: obj.us_weekday
+ });
+ if (i != j) { alert("ERROR"); return; }
+ }
+ alert("SUCCESS");
+}
+
+
+
+// Graphical calendar
+
+
+gregor_iso_weekday_css = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"];
+
+function gregor_sheet(args) {
+
+ // setting args.today and args.selected
+ if (args.today === undefined) {
+ var js_date = new Date();
+ args.today = gregor_completeDate({
+ year: js_date.getFullYear(),
+ month: js_date.getMonth() + 1,
+ day: js_date.getDate()
+ });
+ } else if (args.today != null) {
+ args.today = gregor_completeDate(args.today);
+ }
+ if (args.selected == "today") {
+ args.selected = args.today;
+ } else if (args.selected != null) {
+ args.selected = gregor_completeDate(args.selected);
+ }
+
+ // setting args.year and args.month
+ if (args.year == null) {
+ if (args.selected != null) args.year = args.selected.year;
+ else args.year = args.today.year;
+ }
+ if (args.month == null) {
+ if (args.selected != null) args.month = args.selected.month;
+ else args.month = args.today.month;
+ }
+
+ // setting first_day
+ var first_day = gregor_completeDate({
+ year: args.year,
+ month: args.month,
+ day: 1
+ });
+ if (first_day == null) return;
+
+ // checking args.navigation, args.week_mode and args.week_numbers
+ if (args.navigation == null) args.navigation = "enabled";
+ else if (
+ args.navigation != "enabled" &&
+ args.navigation != "disabled" &&
+ args.navigation != "hidden"
+ ) return;
+ if (args.week_mode == null) args.week_mode = "iso";
+ else if (args.week_mode != "iso" && args.week_mode != "us") return;
+ if (
+ args.week_numbers != null &&
+ args.week_numbers != "left" &&
+ args.week_numbers != "right"
+ ) return;
+
+ // checking args.month.names and args.weekday_names
+ if (args.month_names.length != 12) return;
+ if (args.weekday_names.length != 7) return;
+
+ // calculating number of days in month
+ var count;
+ if (args.year < 9999 || args.month < 12) {
+ count = gregor_daycount({
+ year: (args.month == 12) ? (args.year + 1) : args.year,
+ month: (args.month == 12) ? 1 : (args.month + 1),
+ day: 1
+ }) - first_day.daycount;
+ } else {
+ // workaround for year 9999
+ count = 31;
+ }
+
+ // locale variables for UI construction
+ var table, row, cell, element;
+
+ // take table element from args.element,
+ // if neccessary create and store it
+ if (args.element == null) {
+ table = document.createElement("table");
+ args.element = table;
+ } else {
+ table = args.element;
+ while (table.firstChild) table.removeChild(table.firstChild);
+ }
+
+ // set CSS class of table according to args.week_numbers
+ if (args.week_numbers == "left") {
+ table.className = "gregor_sheet gregor_weeks_left";
+ } else if (args.week_numbers == "right") {
+ table.className = "gregor_sheet gregor_weeks_right";
+ } else {
+ table.className = "gregor_sheet gregor_weeks_none";
+ }
+
+ // begin of table head
+ var thead = document.createElement("thead");
+
+ // navigation
+ if (args.navigation != "hidden") {
+
+ // UI head row containing the year
+ row = document.createElement("tr");
+ row.className = "gregor_year_row";
+ cell = document.createElement("th");
+ cell.className = "gregor_year";
+ cell.colSpan = args.week_numbers ? 8 : 7;
+ if (args.navigation == "enabled") {
+ element = document.createElement("a");
+ element.className = "gregor_turn gregor_turn_left";
+ element.style.cssFloat = "left";
+ element.style.styleFloat = "left";
+ element.href = "#";
+ element.onclick = function() {
+ if (args.year > 1) args.year--;
+ gregor_sheet(args);
+ return false;
+ }
+ element.ondblclick = element.onclick;
+ element.appendChild(document.createTextNode("<<"));
+ cell.appendChild(element);
+ element = document.createElement("a");
+ element.className = "gregor_turn gregor_turn_right";
+ element.style.cssFloat = "right";
+ element.style.styleFloat = "right";
+ element.href = "#";
+ element.onclick = function() {
+ if (args.year < 9999) args.year++;
+ gregor_sheet(args);
+ return false;
+ }
+ element.ondblclick = element.onclick;
+ element.appendChild(document.createTextNode(">>"));
+ cell.appendChild(element);
+ }
+ cell.appendChild(document.createTextNode(first_day.year));
+ row.appendChild(cell);
+ thead.appendChild(row);
+
+ // UI head row containing the month
+ row = document.createElement("tr");
+ row.className = "gregor_month_row";
+ cell = document.createElement("th");
+ cell.className = "gregor_month";
+ cell.colSpan = args.week_numbers ? 8 : 7;
+ if (args.navigation == "enabled") {
+ element = document.createElement("a");
+ element.className = "gregor_turn gregor_turn_left";
+ element.style.cssFloat = "left";
+ element.style.styleFloat = "left";
+ element.href = "#";
+ element.onclick = function() {
+ if (args.year > 1 || args.month > 1) {
+ args.month--;
+ if (args.month < 1) {
+ args.month = 12;
+ args.year--;
+ }
+ }
+ gregor_sheet(args);
+ return false;
+ }
+ element.ondblclick = element.onclick;
+ element.appendChild(document.createTextNode("<<"));
+ cell.appendChild(element);
+ element = document.createElement("a");
+ element.className = "gregor_turn gregor_turn_right";
+ element.style.cssFloat = "right";
+ element.style.styleFloat = "right";
+ element.href = "#";
+ element.onclick = function() {
+ if (args.year < 9999 || args.month < 12) {
+ args.month++;
+ if (args.month > 12) {
+ args.month = 1;
+ args.year++;
+ }
+ }
+ gregor_sheet(args);
+ return false;
+ }
+ element.ondblclick = element.onclick;
+ element.appendChild(document.createTextNode(">>"));
+ cell.appendChild(element);
+ }
+ cell.appendChild(document.createTextNode(
+ args.month_names[first_day.month-1]
+ ));
+ row.appendChild(cell);
+ thead.appendChild(row);
+
+ // end of navigation
+ }
+
+ // UI weekday row
+ row = document.createElement("tr");
+ row.className = "gregor_weekday_row";
+ if (args.week_numbers == "left") {
+ cell = document.createElement("th");
+ cell.className = "gregor_corner";
+ row.appendChild(cell);
+ }
+ for (var i=0; i<7; i++) {
+ cell = document.createElement("th");
+ cell.className = (
+ "gregor_weekday gregor_" +
+ gregor_iso_weekday_css[(args.week_mode == "us") ? ((i+6)%7) : i]
+ );
+ cell.appendChild(document.createTextNode(args.weekday_names[i]));
+ row.appendChild(cell);
+ }
+ if (args.week_numbers == "right") {
+ cell = document.createElement("th");
+ cell.className = "gregor_corner";
+ row.appendChild(cell);
+ }
+ thead.appendChild(row);
+
+ // end of table head and begin of table body
+ table.appendChild(thead);
+ var tbody = document.createElement("tbody");
+
+ // definition of insert_week function
+ var week = (
+ (args.week_mode == "us") ? first_day.us_week : first_day.iso_week
+ );
+ insert_week = function() {
+ cell = document.createElement("th");
+ cell.className = "gregor_week";
+ cell.appendChild(document.createTextNode(
+ (week < 10) ? ("0" + week) : week)
+ );
+ week++;
+ if (
+ args.week_mode == "iso" && (
+ (
+ args.month == 1 && week > 52
+ ) || (
+ args.month == 12 && week == 53 && (
+ first_day.iso_weekday == 0 ||
+ first_day.iso_weekday == 5 ||
+ first_day.iso_weekday == 6
+ )
+ )
+ )
+ ) week = 1;
+ row.appendChild(cell);
+ }
+
+ // output data fields
+ row = document.createElement("tr");
+ if (args.week_numbers == "left") insert_week();
+ var filler_count = (
+ (args.week_mode == "us") ? first_day.us_weekday : first_day.iso_weekday
+ );
+ for (var i=0; i 2) {
+ year = parseInt(numericPart, 10);
+ }
+ } else if (formatPart.match(/^M+$/)) {
+ month = parseInt(numericPart, 10);
+ } else if (formatPart.match(/^D+$/)) {
+ day = parseInt(numericPart, 10);
+ } else {
+ //alert("Not implemented.");
+ return null;
+ }
+ }
+ return gregor_completeDate({year: year, month: month, day: day});
+}
+
+function gregor_addGui(args) {
+
+ // copy argument structure
+ var state = {};
+ for (key in args) state[key] = args[key];
+
+ // unset state.element, which should never be set anyway
+ state.element = null;
+
+ // save original values of "year" and "month" options
+ var original_year = state.year;
+ var original_month = state.month;
+
+ // get text field element
+ var element = document.getElementById(state.element_id);
+ state.element_id = null;
+
+ // setup state.today, state.selected and state.format options
+ if (state.today === undefined) {
+ var js_date = new Date();
+ state.today = gregor_completeDate({
+ year: js_date.getFullYear(),
+ month: js_date.getMonth() + 1,
+ day: js_date.getDate()
+ });
+ } else if (state.today != null) {
+ state.today = gregor_completeDate(args.today);
+ }
+ if (state.selected == "today") {
+ state.selected = state.today;
+ } else if (args.selected != null) {
+ state.selected = gregor_completeDate(state.selected);
+ }
+ if (state.format == null) state.format = "YYYY-MM-DD";
+
+ // using state.future to calculate maxYear (for 2 digit year conversions)
+ var maxYear = (state.today == null) ? null : (
+ state.today.year +
+ ((state.future == null) ? 12 : state.future)
+ );
+
+ // hook into state.select_callback
+ var select_callback = state.select_callback;
+ state.select_callback = function(date) {
+ element.value = gregor_formatDate(state.format, date);
+ if (select_callback) select_callback(date);
+ };
+
+ // function to parse text field and update calendar sheet state
+ var updateSheet = function(terminated) {
+ var date = gregor_parseDate(
+ state.format, element.value, terminated, maxYear
+ );
+ if (date) {
+ state.year = null;
+ state.month = null;
+ }
+ state.selected = date;
+ gregor_sheet(state);
+ };
+
+ // Initial synchronization
+ if (state.selected === undefined) updateSheet(true);
+ element.value = gregor_formatDate(state.format, state.selected);
+ if (select_callback) select_callback(state.selected);
+
+ // variables storing popup status
+ var visible = false;
+ var focus = false;
+ var protection = false;
+
+ // event handlers for text field
+ element.onfocus = function() {
+ focus = true;
+ if (!visible) {
+ state.year = original_year;
+ state.month = original_month;
+ gregor_sheet(state);
+ state.element.style.position = "absolute";
+ state.element.style.top = gregor_getAbsoluteTop(element) + element.offsetHeight;
+ state.element.style.left = gregor_getAbsoluteLeft(element);
+ state.element.onmousedown = function() {
+ protection = true;
+ };
+ state.element.onmouseup = function() {
+ protection = false;
+ element.focus();
+ };
+ state.element.onmouseout = state.element.onmouseup;
+ element.parentNode.appendChild(state.element);
+ visible = true;
+ }
+ };
+ element.onblur = function() {
+ focus = false;
+ window.setTimeout(function() {
+ if (visible && !focus && !protection) {
+ updateSheet(true);
+ element.value = gregor_formatDate(state.format, state.selected);
+ if (select_callback) select_callback(state.selected);
+ state.element.parentNode.removeChild(state.element);
+ visible = false;
+ protection = false;
+ }
+ }, 1);
+ };
+ element.onkeyup = function() {
+ updateSheet(false);
+ if (select_callback) select_callback(state.selected);
+ };
+
+}
+
diff -r 72c5e0ee7c98 -r 77d58efe99fd static/icons/16/clock.png
Binary file static/icons/16/clock.png has changed
diff -r 72c5e0ee7c98 -r 77d58efe99fd static/icons/16/magnifier.png
Binary file static/icons/16/magnifier.png has changed
diff -r 72c5e0ee7c98 -r 77d58efe99fd static/icons/16/text_list_bullets.png
Binary file static/icons/16/text_list_bullets.png has changed
diff -r 72c5e0ee7c98 -r 77d58efe99fd static/icons/16/user_edit.png
Binary file static/icons/16/user_edit.png has changed
diff -r 72c5e0ee7c98 -r 77d58efe99fd static/style.css
--- a/static/style.css Sun Jan 10 12:00:00 2010 +0100
+++ b/static/style.css Fri Jan 22 12:00:00 2010 +0100
@@ -438,6 +438,14 @@
* ui.paginate
*/
+.ui_paginate_head {
+ margin-bottom: 1ex;
+}
+
+.ui_paginate_foot {
+ margin-top: 1ex;
+}
+
.ui_paginate_select a {
padding: 0.5ex;
}
@@ -517,6 +525,11 @@
padding-right: 0.5em;
}
+.ui_field_label.label_right {
+ text-align: left;
+ width: auto;
+}
+
.login input[type=text],
.login input[type=password] {
width: 10em;
@@ -581,6 +594,14 @@
background-color: #ddd;
}
+.nohover tr:hover td {
+ background-color: #fff;
+}
+
+.nohover table tr:hover td {
+ background-color: #ddd;
+}
+
tr table tr:hover td {
background-color: #fff;
@@ -817,7 +838,8 @@
.draft_updated_info,
.voting_active_info,
.revoked_info,
-.initiator_invite_info {
+.initiator_invite_info,
+.motd {
background-color: #fec;
border: 2px solid #b96;
padding: 1ex;
@@ -901,6 +923,10 @@
color: #000;
}
+.action_active {
+ background-color: #fec;
+}
+
/*************************************************************************
* Voting
*/