omniboard 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,50 @@
1
+ class Omniboard::Renderer
2
+ attr_accessor :columns
3
+
4
+ def initialize()
5
+ @columns = []
6
+ @current_column = nil
7
+ @current_group = nil
8
+ end
9
+
10
+ # Add one or more columns to our array of columns.
11
+ def add_column(*col)
12
+ @columns += col
13
+ end
14
+
15
+
16
+
17
+ def to_s
18
+ preamble + @columns.map{ |c| render_column(c) }.join("\n") + postamble
19
+ end
20
+
21
+ def preamble
22
+ ERB.new(template "preamble").result(binding)
23
+ end
24
+
25
+ def postamble
26
+ ERB.new(template "postamble").result(binding)
27
+ end
28
+
29
+ def render_column(column)
30
+ ERB.new(template("column")).result(binding)
31
+ end
32
+
33
+ def render_project(project)
34
+ ERB.new(template("project")).result(binding)
35
+ end
36
+
37
+ # Fetch a template from the templates folder
38
+ def template(template_name)
39
+ @templates ||= {}
40
+
41
+ if !@templates.has_key?(template_name)
42
+ template_file = template_name + ".erb"
43
+ template_path = File.join(__dir__, "templates", template_file)
44
+ raise(ArgumentError, "Attempting to find template #{template_file}, which does not exist.") unless File.exists?(template_path)
45
+
46
+ @templates[template_name] = File.read(template_path)
47
+ end
48
+ @templates[template_name]
49
+ end
50
+ end
@@ -0,0 +1,63 @@
1
+ # Represents some HTML-esque styled text
2
+ #
3
+ # The incredibly naive parser assumes all sorts of things about your notes, and
4
+ # faithfully turns them into a StyledText object.
5
+ #
6
+ # Will probably crash horridly if you try something nasty on it. Don't do that.
7
+ # Things it will choke on (off the top of my head):
8
+ #
9
+ # * Nested <run>s
10
+ # * <run>s with attributes
11
+ # * <text> or <note> tags within the body of this actual note
12
+ # * All kinds of other things?
13
+
14
+ class Omniboard::StyledText
15
+ # All the elements that make up this styled text
16
+ attr_accessor :elements
17
+
18
+ # Parse some styled html
19
+ def self.parse(styled_html)
20
+ # Remove outlying HTML - surrounding <note> and <text> elements
21
+ superfluous_tags = /<\/?(note|text)>/
22
+ styled_html = styled_html.gsub(superfluous_tags, "")
23
+
24
+ return_value = self.new
25
+
26
+ # Run on all text
27
+ until styled_html.empty?
28
+
29
+ next_run_index = styled_html.index("<run>")
30
+
31
+ if next_run_index.nil?
32
+ return_value << styled_html
33
+ styled_html = ""
34
+
35
+ else
36
+ # Get rid of any plain html!
37
+ if next_run_index != 0
38
+ return_value << styled_html[0...next_run_index]
39
+ styled_html = styled_html[next_run_index..-1]
40
+ end
41
+
42
+ run_end = styled_html.index("</run>") + "</run>".length
43
+ return_value << Omniboard::StyledTextElement.new(styled_html[0...run_end])
44
+ styled_html = styled_html[run_end..-1]
45
+ end
46
+ end
47
+ return_value
48
+ end
49
+
50
+ def initialize()
51
+ @elements = []
52
+ end
53
+
54
+ # Add an element to the elements array
55
+ def << element
56
+ @elements << element
57
+ end
58
+
59
+ # Turn this styled text into html!
60
+ def to_html
61
+ @elements.map{ |e| e.is_a?(String) ? e : e.to_html }.join("")
62
+ end
63
+ end
@@ -0,0 +1,40 @@
1
+ # Represents a little bit of styled text, complete with styling values and the like.
2
+ # Make me big, make me small, make me italic, just don't make me work weekends.
3
+ class Omniboard::StyledTextElement
4
+ # The actual text
5
+ attr_accessor :text
6
+
7
+ # Applied values
8
+ attr_accessor :styles
9
+
10
+ # Initialize from a string
11
+ def initialize(string)
12
+ @text = string[/<lit>(.*?)<\/lit>/,1] || ""
13
+ @styles = {}
14
+
15
+ raw_styles = string[/<style>(.*?)<\/style>/,1]
16
+ if raw_styles
17
+ raw_styles.scan(/<value key="(.*?)">(.*?)<\/value>/).each do |match|
18
+ @styles[match[0]] = match[1]
19
+ end
20
+ end
21
+ end
22
+
23
+ def [] k
24
+ @styles[k]
25
+ end
26
+
27
+ def to_html
28
+ surrounds = []
29
+ surrounds << "i" if self["font-italic"] == "yes"
30
+ surrounds << "b" if self["font-weight"].to_i > 7
31
+ surrounds << "u" if self["underline-style"] == "single"
32
+
33
+ tag(text, *surrounds)
34
+ end
35
+
36
+ private
37
+ def tag(text, *tags)
38
+ tags.map{|t| "<#{t}>"}.join("") + text + tags.reverse.map{|t| "</#{t}>"}.join("")
39
+ end
40
+ end
@@ -0,0 +1,25 @@
1
+ <div class="column width-<%=column.columns%>" style="flex-grow: <%=column.width%>">
2
+ <div class="column-header">
3
+ <h2><%=column%></h2>
4
+ <% if column.filter_button %>
5
+ <svg class="filter-button" xmlns:xlink="http://www.w3.org/1999/xlink"><use xlink:href="#filter-<%= column.property(:hide_dimmed) ? "remove" : "apply" %>" /></svg>
6
+ <% end %>
7
+ <% if column.property(:display_project_counts) != nil %><%= column.count_div %><% end %>
8
+ </div>
9
+ <%if column.can_be_grouped? %>
10
+ <% column.groups.each do |group| %>
11
+ <div class="project-wrapper">
12
+ <h3><%=column.group_name_for(group) %></h3>
13
+ <div class="project-group">
14
+ <% column.projects(group).each do |p| %>
15
+ <%= render_project(p) %>
16
+ <% end %>
17
+ </div>
18
+ </div>
19
+ <% end %>
20
+ <% else %>
21
+ <% column.projects.each do |p| %>
22
+ <%= render_project(p) %>
23
+ <% end %>
24
+ <% end %>
25
+ </div>
@@ -0,0 +1,226 @@
1
+ </section>
2
+ <section id="overlay-wrapper" class="hidden">
3
+ <section id="overlay-background"></section>
4
+ <section id="overlay-main">
5
+ <div class="close">×</div>
6
+ <div class="overlay-header">
7
+ <h2 class="name"></h2>
8
+ <h3><a class="id" href="#"></a></h3>
9
+ </div>
10
+ <div class="body">
11
+ <div class="note"></div>
12
+ <div class="tasks"></div>
13
+ </div>
14
+ </section>
15
+ </section>
16
+ <section id="modal-wrapper" class="hidden">
17
+ <section id="modal-background"></section>
18
+ <section id="modal-main">
19
+ <div class="close">×</div>
20
+ <h2 class="title"></h2>
21
+ <div class="body"></div>
22
+ </section>
23
+ </section>
24
+ <script>
25
+ //<![CDATA[
26
+ //Give us a favicon!
27
+ var favicon = document.querySelector("head link");
28
+ favicon.setAttribute("href", "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAARdJREFUOBGVU00SwVAMlseqCxasXMI4ghkHcAjHqBXuwwm4gnOwYNOd9+RLJVKtIjOZ5r0v35eftpRS6vxhgXOJPbILsadkIlrppd61PbnwhnGiZwd0Oed3JRz3p85sMWUooprYYYe7icTMug5H2yEOaAlWmQNTeTLjaNkZ9fkATlIBByK0wkp0eYHFrTA5oNQIYcCBNVTDSywgAVXqHcR4480Qe8Z4A1/EZTciYm+Bs82w2JSK51mqYQsYB1W9amoUMCUJYjlPkHFtOZqjrxFt53r5wxO8teSV7aLlqjO4Ws7HEY74Hdezn6dSOMuwxO/2UaAodIntIipQW04LresxFbAvx4MNMQrZPwNcBRpyX1chWFqt0APk/nSDodA5MgAAAABJRU5ErkJggg==");
29
+
30
+ //Foreach function
31
+ function foreach(sel, fnc){
32
+ var elems;
33
+ if (sel instanceof Array || sel instanceof NodeList) elems = sel;
34
+ else elems=document.querySelectorAll(sel);
35
+
36
+ for(var i=0;i<elems.length;i++)fnc(elems[i]);
37
+ }
38
+
39
+ //Add class to element
40
+ function addClass(elem, klass) {
41
+ var klasses = elem.className.split(/\s+/);
42
+
43
+ if (klasses.indexOf(klass) == -1) {
44
+ klasses.push(klass);
45
+ elem.className = klasses.join(" ");
46
+ }
47
+ }
48
+
49
+ function removeClass(elem, klass) {
50
+ var klasses = elem.className.split(/\s+/);
51
+ var i = klasses.indexOf(klass);
52
+
53
+ if (i >= 0) {
54
+ klasses.splice(i,1);
55
+ elem.className = klasses.join(" ");
56
+ }
57
+ }
58
+
59
+ //Element hide and show
60
+ function show(elem){ removeClass(elem, "hidden") }
61
+ function hide(elem){ addClass(elem, "hidden") }
62
+
63
+ //Overlay vars
64
+ var overlay = {
65
+ wrapper: document.querySelector("#overlay-wrapper"),
66
+ background: document.querySelector("#overlay-background"),
67
+ container: document.querySelector("#overlay-main"),
68
+ name: document.querySelector("#overlay-main .name"),
69
+ id: document.querySelector("#overlay-main .id"),
70
+ note: document.querySelector("#overlay-main .note"),
71
+ tasks: document.querySelector("#overlay-main .tasks"),
72
+ close: document.querySelector("#overlay-main .close")
73
+ };
74
+
75
+ //Show the overlay, get data from element
76
+ function showOverlay(elem) {
77
+ overlay.container.style.backgroundColor = elem.getAttribute("data-light-colour");
78
+
79
+ overlay.name.innerHTML = elem.getAttribute("data-name");
80
+ overlay.id.innerHTML = elem.getAttribute("data-id");
81
+ overlay.id.setAttribute("href", "omnifocus:///task/"+elem.getAttribute("data-id"));
82
+
83
+ //Set up note panel inner HTML. Optional UL for deferral and due dates,
84
+ // followed by notes
85
+ var noteHTML = "";
86
+ if (elem.getAttribute("data-deferred") != "" ||
87
+ elem.getAttribute("data-due") != "") {
88
+ noteHTML = "<ul>";
89
+ if (elem.getAttribute("data-deferred") != "") { noteHTML += "<li><b>Project deferred until:</b> " + elem.getAttribute("data-deferred") + "</li>"; }
90
+ if (elem.getAttribute("data-due") != "") { noteHTML += "<li><b>Project due on:</b> " + elem.getAttribute("data-due") + "</li>"; }
91
+ noteHTML += "</ul>"
92
+ }
93
+ overlay.note.innerHTML = noteHTML + "<h3>Notes:</h3>" + elem.getAttribute("data-note");
94
+ overlay.tasks.innerHTML = "<h3>Tasks:</h3>" + elem.getAttribute("data-tasks");
95
+
96
+ show(overlay.wrapper);
97
+ }
98
+
99
+ // Hide the overlay
100
+ function hideOverlay() {hide(overlay.wrapper); }
101
+
102
+ // Set element background colour
103
+ foreach(".project", function(p){ p.style.backgroundColor = p.getAttribute("data-colour");});
104
+
105
+ // Set up title click->overlay
106
+ foreach(".project h4", function(h){
107
+ h.style.cursor = "pointer";
108
+ h.addEventListener("click", function(){showOverlay(h.parentNode)},false);
109
+ });
110
+
111
+ // Modal vars
112
+ var modal = {
113
+ wrapper: document.querySelector("#modal-wrapper"),
114
+ background: document.querySelector("#modal-background"),
115
+ container: document.querySelector("#modal-main"),
116
+ title: document.querySelector("#modal-main .title"),
117
+ body: document.querySelector("#modal-main .body"),
118
+ close: document.querySelector("#modal-main .close")
119
+ }
120
+
121
+ // Show the modal, with a title and body
122
+ function showModal(title, body) {
123
+ modal.title.innerHTML = title;
124
+ modal.body.innerHTML = body;
125
+
126
+ show(modal.wrapper);
127
+ }
128
+
129
+ // Hide the modal
130
+ function hideModal(){ hide(modal.wrapper); }
131
+
132
+ //Background and close button listeners
133
+ modal.close.addEventListener("click", hideModal, false);
134
+ modal.background.addEventListener("click", hideModal, false);
135
+
136
+ //Hook up alerts to modal
137
+ foreach(".alert", function(alert){
138
+ alert.addEventListener("click", function(){
139
+ showModal(this.getAttribute("data-modal-title"), this.getAttribute("data-modal-body"));
140
+ }, false);
141
+ });
142
+
143
+ // Search box function
144
+ document.querySelector("#search").addEventListener("keyup", function(){
145
+ var searchString = this.value.toLowerCase();
146
+
147
+ foreach(".project", function(p){
148
+ var id = p.getAttribute("data-id");
149
+ var name = p.getAttribute("data-name");
150
+ var note = p.getAttribute("data-note");
151
+ if (name.toLowerCase().indexOf(searchString) >= 0 ||
152
+ id.toLowerCase().indexOf(searchString) >= 0 ||
153
+ note.toLowerCase().indexOf(searchString) >= 0
154
+ )
155
+ show(p);
156
+ else
157
+ hide(p);
158
+ });
159
+ updateGroupVisibility();
160
+ });
161
+
162
+ // Close button listener
163
+ overlay.close.addEventListener("click", hideOverlay, false);
164
+ // Overlay background listener
165
+ overlay.background.addEventListener("click", hideOverlay, false);
166
+
167
+ // Search box auto-focus with "/" when not focussed on a textbox
168
+ document.addEventListener("keypress", function(e){
169
+ if (!e) //IE fix
170
+ e = window.event;
171
+
172
+ // Escape most things
173
+ var tagName = e.target.tagName;
174
+ if (tagName == "INPUT" || tagName == "TEXTAREA") return true;
175
+ if (e.shiftKey || e.ctrlKey || e.altKey) return true;
176
+
177
+ if (e.key == "/") {
178
+ document.querySelector("#search").focus();
179
+ return false;
180
+ }
181
+ else
182
+ return true;
183
+ }, true);
184
+
185
+ // Filter button listeners
186
+ foreach(".filter-button", function(elem){
187
+ elem.addEventListener("click", function(){
188
+ var parent = this.parentNode.parentNode;
189
+ var use = this.querySelector("use");
190
+
191
+ var dimmedProjects = parent.querySelectorAll(".dimmed");
192
+ if (use.getAttribute("xlink:href") == "#filter-apply") {// Apply filter!
193
+ foreach(dimmedProjects, function(e){ addClass(e, "filtered") });
194
+ use.setAttribute("xlink:href", "#filter-remove");
195
+ }
196
+ else {
197
+ foreach(dimmedProjects, function(e){ removeClass(e, "filtered") });
198
+ use.setAttribute("xlink:href", "#filter-apply");
199
+ }
200
+
201
+ updateGroupVisibility();
202
+ }, false);
203
+ });
204
+
205
+ // Hide/show groups
206
+ function updateGroupVisibility() {
207
+ foreach(".project-wrapper", function(pw){
208
+
209
+ var projects = pw.querySelectorAll(".project");
210
+ var allProjectsHidden = true;
211
+ var j = 0;
212
+
213
+ while (allProjectsHidden && j < projects.length) {
214
+ if (projects[j].className.indexOf("hidden") == -1 && projects[j].className.indexOf("filtered") == -1)
215
+ allProjectsHidden = false;
216
+ j++;
217
+ }
218
+
219
+ if (allProjectsHidden) hide(pw);
220
+ else show(pw);
221
+ });
222
+ }
223
+ //]]>
224
+ </script>
225
+ </body>
226
+ </html>
@@ -0,0 +1,346 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
5
+ <link rel="icon" type="image/png" href="" />
6
+ <title>看板</title>
7
+ <style>
8
+ * { margin: 0px; padding: 0px; }
9
+ html {
10
+ font: 12px <%= Omniboard::Column.body_font %>;
11
+ font-size: 12px;
12
+ padding: 2rem;
13
+ }
14
+
15
+ header {
16
+ display: flex;
17
+ justify-content: space-between;
18
+ align-items: center;
19
+ border-bottom: 2px solid #888;
20
+ margin-bottom: 2rem;
21
+ }
22
+
23
+ header svg {
24
+ max-width: 20px;
25
+ max-height: 20px;
26
+ }
27
+
28
+ header>div>a {
29
+ padding-right: 2ex;
30
+ margin-right: 2ex;
31
+ border-right: 1px dotted grey;
32
+ }
33
+
34
+ header>div{
35
+ display: flex;
36
+ align-items: center;
37
+ }
38
+
39
+ .alert.error {
40
+ border-left: 4px solid red;
41
+ background-color: rgba(255,0,0,0.5);
42
+ }
43
+
44
+ .alert.warn {
45
+ border-left: 4px solid yellow;
46
+ background-color: rgba(220,200,0,0.5);
47
+ }
48
+
49
+ .alert {
50
+ border-top-right-radius: 5px;
51
+ border-bottom-right-radius: 5px;
52
+ padding: 5px;
53
+ cursor: pointer;
54
+ }
55
+
56
+ #search {
57
+ margin-right: 2em;
58
+ border-radius: 1em;
59
+ padding: 0.2em 0.4em;
60
+ }
61
+
62
+ #kanban{ display: flex; }
63
+ .column{ flex-basis: 0px; }
64
+ .column + .column{ margin-left: 2em; }
65
+ .project-group { display: flex; flex-wrap: wrap; }
66
+
67
+ h1, h2, h3, h4 { font-family: <%= Omniboard::Column.heading_font %>; color: #444; }
68
+
69
+ h1{ font-size: 2.5rem; }
70
+ h2{ font-size: 2rem; }
71
+ h3{ font-size: 1.5rem }
72
+ h4{ font-size: 1rem }
73
+
74
+ .column.width-1 .project{ width: calc(100% - 0.5em); }
75
+ .column.width-2 .project { width: calc(50% - 0.5em); }
76
+ .column.width-3 .project { width: calc(33% - 0.5em); }
77
+ .column.width-4 .project { width: calc(25% - 0.5em); }
78
+ .column.width-5 .project { width: calc(20% - 0.5em); }
79
+ .column.width-6 .project { width: calc(16% - 0.5em); }
80
+
81
+ .column-header {
82
+ margin-bottom: 0.8em;
83
+ border-bottom: 1px solid #333;
84
+ display: flex;
85
+ align-items: center;
86
+ }
87
+
88
+ .column-total {
89
+ font-size: 1.2rem;
90
+ padding: 0 2ex;
91
+ font-weight: bold;
92
+ color: #666;
93
+ }
94
+
95
+ .column-total.limit-breached {
96
+ color: #F33;
97
+ }
98
+
99
+ .filter-button {
100
+ opacity: 0.5;
101
+ height: 2em;
102
+ width: 2em;
103
+ margin-left: 2em;
104
+ cursor: pointer;
105
+ }
106
+
107
+ .filter-button:hover, .filter-button:active{ opacity: 1;}
108
+
109
+ .project{
110
+ border: 2px solid black;
111
+ border-radius: 8px;
112
+ margin: 0 0.5em 1em 0;
113
+ padding: 5px;
114
+ box-sizing: border-box;
115
+ font-weight: 300;
116
+ box-shadow: rgba(0,0,0,0.3) 1px 1px 3px;
117
+ position: relative;
118
+ }
119
+
120
+ .project.marked:after {
121
+ content: "";
122
+ position: absolute;
123
+ top: 0;
124
+ right: 0;
125
+ height: 10px;
126
+ width: 10px;
127
+ border-top-right-radius: 5px;
128
+ background: linear-gradient(-135deg, red 0%, red 50%, rgba(0,0,0,0) 50%);
129
+ }
130
+
131
+ .project.dimmed {
132
+ opacity: 0.8;
133
+ box-shadow: none;
134
+ }
135
+
136
+ .project.dimmed:hover {
137
+ opacity: 1.0;
138
+ box-shadow: rgba(0,0,0,0.3) 1px 1px 3px;
139
+ }
140
+
141
+ .project.compact .tasks, .project.compact .project-deferral, .project.compact .project-icon {
142
+ display: none;
143
+ }
144
+
145
+ .project-deferral {
146
+ position: absolute;
147
+ bottom: 3px;
148
+ right: 3px;
149
+ height: 10px;
150
+ width: 10px;
151
+
152
+ background-color: rgb(255, 255, 255);
153
+ border-top: 4px solid red;
154
+ box-shadow: 2px 2px 2px rgba(0, 0, 0, 0.5);
155
+
156
+ text-align: center;
157
+ font-weight: bold;
158
+ font-size: 8px;
159
+ }
160
+
161
+ .project-icon {
162
+ position: absolute;
163
+ right: 3px;
164
+ bottom: 0;
165
+ }
166
+
167
+ .project-icon img, .project-icon svg {
168
+ max-height: 10px;
169
+ max-width: 14px;
170
+ }
171
+
172
+ #overlay-background {
173
+ position: fixed;
174
+ top: 0px;
175
+ left: 0px;
176
+ height: 100%;
177
+ width: 100%;
178
+ background-color: rgba(0,0,0,0.2);
179
+ }
180
+
181
+ #overlay-main {
182
+ position: fixed;
183
+ top: 10%;
184
+ left: 10%;
185
+ height: 80%;
186
+ width: 80%;
187
+ padding: 2em;
188
+ box-sizing: border-box;
189
+
190
+ background-color: #ddd;
191
+ border: 2px solid #666;
192
+ border-radius: 10px;
193
+
194
+ display: flex;
195
+ flex-direction: column;
196
+ }
197
+
198
+ .close {
199
+ position: absolute;
200
+ top: 10px;
201
+ right: 10px;
202
+ font-size: 2rem;
203
+ cursor: pointer;
204
+ }
205
+
206
+ .overlay-header {
207
+ display: flex;
208
+ align-items: baseline
209
+ }
210
+
211
+ .overlay-header h2 {
212
+ border-bottom: none;
213
+ }
214
+
215
+ .overlay-header h3 {
216
+ padding-left: 3ex;
217
+ }
218
+
219
+ #overlay-main h3 a {
220
+ color: grey;
221
+ }
222
+
223
+ #overlay-main .body {
224
+ display: flex;
225
+ flex-grow: 1;
226
+ font-size: 1.2rem;
227
+ padding-top: 1em;
228
+ }
229
+
230
+ #overlay-main .note ul {
231
+ list-style: none;
232
+ margin-bottom: 1em;
233
+ }
234
+
235
+ #overlay-main .note {
236
+ flex: 2 2 0px;
237
+ margin-right: 1em;
238
+ border-right: 1px dotted grey;
239
+ }
240
+
241
+ #overlay-main .note p {
242
+ margin-bottom: 0.2em;
243
+ min-height: 1em;
244
+ }
245
+
246
+ #overlay-main .tasks {
247
+ flex: 1 1 0px;
248
+ margin-left: 1em;
249
+ }
250
+
251
+ #overlay-main .tasks li {
252
+ margin-bottom: 0.5em;
253
+ }
254
+
255
+ #modal-background{
256
+ position: fixed;
257
+ top: 0;
258
+ left: 0;
259
+ height: 100%;
260
+ width: 100%;
261
+ }
262
+
263
+ #modal-main {
264
+ position: fixed;
265
+ top: calc(50% - 100px);
266
+ left: calc(50% - 250px);
267
+ height: 200px;
268
+ width: 500px;
269
+ background-color: white;
270
+ border: 2px solid black;
271
+ box-shadow: 4px 4px 4px rgba(0,0,0,0.5);
272
+ border-radius: 5px;
273
+ }
274
+
275
+ #modal-main h2 {
276
+ border-bottom: 1px solid black;
277
+ padding: 0 1ex;
278
+ }
279
+
280
+ #modal-main .body {
281
+ padding: 1em;
282
+ }
283
+
284
+ .tasks .complete {
285
+ text-decoration: line-through;
286
+ color: #666;
287
+ }
288
+
289
+ .hidden, .filtered { display: none; }
290
+
291
+ <%= Omniboard.custom_css || "" %>
292
+ </style>
293
+ </head>
294
+ <body>
295
+ <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
296
+ <defs>
297
+ <symbol id="waiting-on" viewBox="0 0 10 10" stroke="black" stroke-width="1" fill="none">
298
+ <circle cx="5" cy="5" r="4.5" />
299
+ <path d="M5 5 l0 -4"/>
300
+ <path d="M5 5 l2 0"/>
301
+ </symbol>
302
+ <symbol id="hanging" viewBox="0 0 14 10" stroke="black" stroke-width="1" fill="none">
303
+ <path d="M 5,2 l 1,-1 q 1,-1 2,0 q 1,1 0,2 L 7,4 l -5,4.167 q -1,0.833 0,1 l 10,0 q 1,0 0,-0.833 L 7,4" />
304
+ </symbol>
305
+ <symbol id="filter-apply" viewBox="0 0 20 20">
306
+ <path d="M18.5214844,1.4776001C18.131958,1.086853,17.4981079,1.086792,17.1074829,1.477478L1.4785156,17.1084595
307
+ c-0.390625,0.390625-0.390625,1.0244141,0.0001221,1.4140015c0.390625,0.390686,1.0233765,0.390625,1.4140015-0.000061
308
+ L18.5214233,2.8916016C18.9121704,2.5009766,18.9121704,1.8682251,18.5214844,1.4776001z M3.1083984,13.4973145l2.5593262-2.5584717
309
+ C5.5981445,10.6357422,5.5546875,10.3234863,5.5546875,10c0-2.3789062,1.9902344-4.3085938,4.4453125-4.3085938
310
+ c0.2861328,0,0.5644531,0.0314941,0.8354492,0.081665l1.2021484-1.2016602C11.394043,4.467041,10.7192383,4.4003906,10,4.4003906
311
+ C3.4394531,4.4003906,0,9.2324219,0,10C0,10.4234619,1.057373,12.0908203,3.1083984,13.4973145z M16.8950195,6.5046387
312
+ L14.3330078,9.065918C14.4018555,9.3674316,14.4443359,9.6784668,14.4443359,10
313
+ c0,2.3789062-1.9892578,4.3066406-4.4443359,4.3066406c-0.2839355,0-0.5598145-0.0317383-0.8288574-0.0810547L7.967041,15.4291992
314
+ C8.609375,15.5330811,9.2827148,15.5996094,10,15.5996094c6.5605469,0,10-4.8339844,10-5.5996094
315
+ C20,9.5756836,18.9438477,7.9101562,16.8950195,6.5046387z"/>
316
+ </symbol>
317
+ <symbol id="filter-remove" viewBox="0 0 20 20">
318
+ <path d="M10,4.4C3.439,4.4,0,9.232,0,10c0,0.766,3.439,5.6,10,5.6c6.56,0,10-4.834,10-5.6C20,9.232,16.56,4.4,10,4.4z M10,14.307
319
+ c-2.455,0-4.445-1.928-4.445-4.307c0-2.379,1.99-4.309,4.445-4.309c2.455,0,4.444,1.93,4.444,4.309
320
+ C14.444,12.379,12.455,14.307,10,14.307z M10,10c-0.407-0.447,0.663-2.154,0-2.154c-1.228,0-2.223,0.965-2.223,2.154
321
+ c0,1.189,0.995,2.154,2.223,2.154c1.227,0,2.223-0.965,2.223-2.154C12.223,9.453,10.346,10.379,10,10z"/>
322
+ </symbol>
323
+ <symbol id="refresh" viewBox="0 0 1000 1000">
324
+ <path d="M0,395.833333 C0,393.663194 0.217013889,392.144097 0.651041667,391.276042 C28.4288194,274.956597 86.5885417,180.664062 175.130208,108.398438 C263.671875,36.1328125 367.404514,0 486.328125,0 C549.696181,0 611.002604,11.9357639 670.247396,35.8072917 C729.492188,59.6788194 782.335069,93.75 828.776042,138.020833 L912.760417,54.0364583 C921.006944,45.7899306 930.772569,41.6666667 942.057292,41.6666667 C953.342014,41.6666667 963.107639,45.7899306 971.354167,54.0364583 C979.600694,62.2829861 983.723958,72.0486111 983.723958,83.3333333 L983.723958,375 C983.723958,386.284722 979.600694,396.050347 971.354167,404.296875 C963.107639,412.543403 953.342014,416.666667 942.057292,416.666667 L650.390625,416.666667 C639.105903,416.666667 629.340278,412.543403 621.09375,404.296875 C612.847222,396.050347 608.723958,386.284722 608.723958,375 C608.723958,363.715278 612.847222,353.949653 621.09375,345.703125 L710.286458,256.510417 C679.470486,227.864583 644.53125,205.729167 605.46875,190.104167 C566.40625,174.479167 525.824653,166.666667 483.723958,166.666667 C425.564236,166.666667 371.310764,180.772569 320.963542,208.984375 C270.616319,237.196181 230.251736,276.041667 199.869792,325.520833 C195.095486,332.899306 183.59375,358.289931 165.364583,401.692708 C161.892361,411.675347 155.381944,416.666667 145.833333,416.666667 L20.8333333,416.666667 C15.1909722,416.666667 10.3081597,414.605035 6.18489583,410.481771 C2.06163194,406.358507 0,401.475694 0,395.833333 L0,395.833333 Z M11.71875,916.666667 L11.71875,625 C11.71875,613.715278 15.8420139,603.949653 24.0885417,595.703125 C32.3350694,587.456597 42.1006944,583.333333 53.3854167,583.333333 L345.052083,583.333333 C356.336806,583.333333 366.102431,587.456597 374.348958,595.703125 C382.595486,603.949653 386.71875,613.715278 386.71875,625 C386.71875,636.284722 382.595486,646.050347 374.348958,654.296875 L284.505208,744.140625 C348.741319,803.602431 424.479167,833.333333 511.71875,833.333333 C569.878472,833.333333 624.131944,819.227431 674.479167,791.015625 C724.826389,762.803819 765.190972,723.958333 795.572917,674.479167 C800.347222,667.100694 811.848958,641.710069 830.078125,598.307292 C833.550347,588.324653 840.060764,583.333333 849.609375,583.333333 L979.166667,583.333333 C984.809028,583.333333 989.69184,585.394965 993.815104,589.518229 C997.938368,593.641493 1000,598.524306 1000,604.166667 L1000,608.723958 C971.788194,725.043403 913.194444,819.335938 824.21875,891.601562 C735.243056,963.867188 631.076389,1000 511.71875,1000 C448.350694,1000 386.71875,987.955729 326.822917,963.867188 C266.927083,939.778646 213.758681,905.815972 167.317708,861.979167 L82.6822917,945.963542 C74.4357639,954.210069 64.6701389,958.333333 53.3854167,958.333333 C42.1006944,958.333333 32.3350694,954.210069 24.0885417,945.963542 C15.8420139,937.717014 11.71875,927.951389 11.71875,916.666667 L11.71875,916.666667 Z" />
325
+ </symbol>
326
+ </defs>
327
+ </svg>
328
+ <header>
329
+ <div>
330
+ <% if !Omniboard::document_at_head? %>
331
+ <% if !Omniboard.document_can_reach_head? %>
332
+ <div class="alert error" data-modal-title="Document detached from head." data-modal-body="I cannot update this document to its most recent state. You might want to reset the local database by running omnigrab with the <code>--reset</code> flag.">Document detached from head.</div>
333
+ <% else %>
334
+ <div class="alert warn" data-modal-title="Document not at head" data-modal-body="This is not the most recent version of the Omnifocus database. Please run omnigrab to update.">Document not at head.</div>
335
+ <% end %>
336
+ <% end %>
337
+ </div>
338
+ <div>
339
+ <% if Omniboard::Column.refresh_link %>
340
+ <a href="<%= Omniboard::Column.refresh_link %>"><svg class="refresh-link" xmlns:xlink="http://www.w3.org/1999/xlink"><use xlink:href="#refresh" /></svg></a>
341
+ <% end %>
342
+ <input type="search" id="search" placeholder="Hit / to filter" />
343
+ <h1>看板 Kanban</h1>
344
+ </div>
345
+ </header>
346
+ <section id="kanban">