ydim-html 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +29 -0
- data/Gemfile +12 -0
- data/History.txt +11 -0
- data/LICENSE.txt +339 -0
- data/Manifest.txt +47 -0
- data/Rakefile +21 -0
- data/bin/ydim-htmld +38 -0
- data/doc/favicon.ico +0 -0
- data/doc/index.rbx +16 -0
- data/doc/resources/javascript/dojo.js +5940 -0
- data/doc/resources/javascript/iframe_history.html +53 -0
- data/doc/resources/javascript/ydim.js +67 -0
- data/doc/resources/ydim/ydim.css +113 -0
- data/lib/ydim/html.rb +10 -0
- data/lib/ydim/html/config.rb +37 -0
- data/lib/ydim/html/state/ajax_values.rb +17 -0
- data/lib/ydim/html/state/autoinvoice.rb +94 -0
- data/lib/ydim/html/state/confirm.rb +16 -0
- data/lib/ydim/html/state/debitor.rb +82 -0
- data/lib/ydim/html/state/debitors.rb +24 -0
- data/lib/ydim/html/state/global.rb +100 -0
- data/lib/ydim/html/state/global_predefine.rb +14 -0
- data/lib/ydim/html/state/init.rb +21 -0
- data/lib/ydim/html/state/invoice.rb +180 -0
- data/lib/ydim/html/state/invoices.rb +70 -0
- data/lib/ydim/html/state/pdf.rb +17 -0
- data/lib/ydim/html/util/lookandfeel.rb +133 -0
- data/lib/ydim/html/util/server.rb +37 -0
- data/lib/ydim/html/util/session.rb +29 -0
- data/lib/ydim/html/util/validator.rb +63 -0
- data/lib/ydim/html/version.rb +7 -0
- data/lib/ydim/html/view/ajax_values.rb +27 -0
- data/lib/ydim/html/view/autoinvoice.rb +80 -0
- data/lib/ydim/html/view/autoinvoices.rb +42 -0
- data/lib/ydim/html/view/confirm.rb +28 -0
- data/lib/ydim/html/view/debitor.rb +118 -0
- data/lib/ydim/html/view/debitors.rb +45 -0
- data/lib/ydim/html/view/htmlgrid.rb +104 -0
- data/lib/ydim/html/view/init.rb +30 -0
- data/lib/ydim/html/view/invoice.rb +218 -0
- data/lib/ydim/html/view/invoices.rb +159 -0
- data/lib/ydim/html/view/navigation.rb +29 -0
- data/lib/ydim/html/view/pdf.rb +24 -0
- data/lib/ydim/html/view/template.rb +71 -0
- data/readme.md +28 -0
- data/spec/smoketest_spec.rb +234 -0
- data/spec/spec_helper.rb +117 -0
- data/spec/stub/http_server.rb +157 -0
- data/test/selenium.rb +1690 -0
- data/test/selenium/selenium-server.jar +0 -0
- data/test/selenium/test_autoinvoice.rb +319 -0
- data/test/selenium/test_debitor.rb +344 -0
- data/test/selenium/test_debitors.rb +47 -0
- data/test/selenium/test_init.rb +105 -0
- data/test/selenium/test_invoice.rb +296 -0
- data/test/selenium/test_invoices.rb +176 -0
- data/test/selenium/unit.rb +125 -0
- data/test/stub/http_server.rb +141 -0
- data/test/suite.rb +15 -0
- data/ydim-html.gemspec +36 -0
- metadata +302 -0
@@ -0,0 +1,53 @@
|
|
1
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
2
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
3
|
+
|
4
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
5
|
+
<head>
|
6
|
+
<title>The Dojo Toolkit</title>
|
7
|
+
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
|
8
|
+
<script type="text/javascript">
|
9
|
+
// <!--
|
10
|
+
var noInit = false;
|
11
|
+
var domain = "";
|
12
|
+
// document.domain = "localhost";
|
13
|
+
function init(){
|
14
|
+
// parse the query string if there is one to try to get args that
|
15
|
+
// we can act on
|
16
|
+
var sparams = document.location.search;
|
17
|
+
if(sparams.length >= 0){
|
18
|
+
if(sparams.charAt(0) == "?"){
|
19
|
+
sparams = sparams.substring(1);
|
20
|
+
}
|
21
|
+
var ss = (sparams.indexOf("&") >= 0) ? "&" : "&";
|
22
|
+
sparams = sparams.split(ss);
|
23
|
+
for(var x=0; x<sparams.length; x++){
|
24
|
+
var tp = sparams[x].split("=");
|
25
|
+
if(typeof window[tp[0]] != "undefined"){
|
26
|
+
window[tp[0]] = ((tp[1]=="true")||(tp[1]=="false")) ? eval(tp[1]) : tp[1];
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
if(noInit){ return; }
|
32
|
+
if(domain.length > 0){
|
33
|
+
document.domain = domain;
|
34
|
+
}
|
35
|
+
if((window.parent != window)&&(window.parent["dojo"])){
|
36
|
+
var pdj = window.parent.dojo;
|
37
|
+
if((pdj["io"])&&(pdj["io"]["XMLHTTPTransport"])){
|
38
|
+
pdj.io.XMLHTTPTransport.iframeLoaded(null, window.location);
|
39
|
+
}
|
40
|
+
}
|
41
|
+
}
|
42
|
+
// -->
|
43
|
+
</script>
|
44
|
+
</head>
|
45
|
+
<!--
|
46
|
+
<body onload="try{ init(); }catch(e){ alert(e); }">
|
47
|
+
-->
|
48
|
+
<body>
|
49
|
+
<h4>The Dojo Toolkit -- iframe_history.html</h4>
|
50
|
+
|
51
|
+
<p>This file is used in Dojo's back/fwd button management.</p>
|
52
|
+
</body>
|
53
|
+
</html>
|
@@ -0,0 +1,67 @@
|
|
1
|
+
function sbsm_encode(value)
|
2
|
+
{
|
3
|
+
// this function provides an Encoding-Hack: because encodeURIComponent returns
|
4
|
+
// UTF-8 encoded values and because for some reasone we need to encode twice
|
5
|
+
// anyway, we first encode in native charset.
|
6
|
+
value = dojo.string.encodeAscii(value);
|
7
|
+
value = value.replace(/\//g, '%2f')
|
8
|
+
value = encodeURIComponent(value);
|
9
|
+
return value;
|
10
|
+
}
|
11
|
+
|
12
|
+
function reload_form(form_id, server_event)
|
13
|
+
{
|
14
|
+
document.body.style.cursor = 'wait';
|
15
|
+
var form;
|
16
|
+
var evt_field;
|
17
|
+
if((form = dojo.byId(form_id)) && (evt_field = dojo.byId("event")))
|
18
|
+
{
|
19
|
+
evt_field.value = server_event;
|
20
|
+
dojo.io.bind({
|
21
|
+
url: form.action,
|
22
|
+
formNode: form,
|
23
|
+
load: function(type, data, event) {
|
24
|
+
form.parentNode.innerHTML = data;
|
25
|
+
document.body.style.cursor = 'auto';
|
26
|
+
},
|
27
|
+
mimetype: "text/html"
|
28
|
+
});
|
29
|
+
}
|
30
|
+
}
|
31
|
+
function reload_list(list_id, url)
|
32
|
+
{
|
33
|
+
var list;
|
34
|
+
if(list = dojo.byId(list_id))
|
35
|
+
{
|
36
|
+
dojo.io.bind({
|
37
|
+
url: url,
|
38
|
+
load: function(type, data, event) {
|
39
|
+
list.parentNode.innerHTML = data;
|
40
|
+
},
|
41
|
+
mimetype: "text/html"
|
42
|
+
});
|
43
|
+
}
|
44
|
+
}
|
45
|
+
function reload_data(url)
|
46
|
+
{
|
47
|
+
dojo.io.bind({
|
48
|
+
url: url,
|
49
|
+
load: function(type, data, event) {
|
50
|
+
var key, val, item;
|
51
|
+
for(key in ajaxResponse) {
|
52
|
+
if(item = dojo.byId(key))
|
53
|
+
{
|
54
|
+
if(item.value)
|
55
|
+
{
|
56
|
+
item.value = ajaxResponse[key];
|
57
|
+
}
|
58
|
+
else
|
59
|
+
{
|
60
|
+
item.innerHTML = ajaxResponse[key];
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
},
|
65
|
+
mimetype: "text/javascript"
|
66
|
+
});
|
67
|
+
}
|
@@ -0,0 +1,113 @@
|
|
1
|
+
body, textarea {
|
2
|
+
font-family: Frutiger, Helvetica, Verdana, Arial, sans-serif;
|
3
|
+
}
|
4
|
+
body {
|
5
|
+
color: #306;
|
6
|
+
background-color: #FFC;
|
7
|
+
}
|
8
|
+
label, th {
|
9
|
+
font-weight: bold;
|
10
|
+
text-align: left;
|
11
|
+
}
|
12
|
+
td.standard-width {
|
13
|
+
width: 150px;
|
14
|
+
}
|
15
|
+
label {
|
16
|
+
width: 150px;
|
17
|
+
display: block;
|
18
|
+
}
|
19
|
+
label.error {
|
20
|
+
color: #C00;
|
21
|
+
}
|
22
|
+
td, th {
|
23
|
+
padding: 4px;
|
24
|
+
}
|
25
|
+
td.unpadded {
|
26
|
+
padding: 0px;
|
27
|
+
}
|
28
|
+
td.processingerror {
|
29
|
+
color: #C00;
|
30
|
+
font-weight: bold;
|
31
|
+
}
|
32
|
+
td.top {
|
33
|
+
vertical-align: top;
|
34
|
+
}
|
35
|
+
textarea {
|
36
|
+
width: 500px;
|
37
|
+
height: 400px;
|
38
|
+
}
|
39
|
+
th {
|
40
|
+
text-align: left;
|
41
|
+
background-color: #FC6;
|
42
|
+
}
|
43
|
+
tr.is_due, div.is_due {
|
44
|
+
background-color: #FAA;
|
45
|
+
}
|
46
|
+
tr.is_paid, div.is_paid {
|
47
|
+
background-color: #AFA;
|
48
|
+
}
|
49
|
+
tr.is_open, div.is_open {
|
50
|
+
background-color: #FFA;
|
51
|
+
}
|
52
|
+
tr.is_trash, div.is_trash {
|
53
|
+
background-color: #DADADA;
|
54
|
+
}
|
55
|
+
a {
|
56
|
+
text-decoration: none;
|
57
|
+
color: #40A;
|
58
|
+
}
|
59
|
+
div#subnavigation {
|
60
|
+
float: right;
|
61
|
+
}
|
62
|
+
div#subnavigation a, div#navigation a {
|
63
|
+
padding: 0px 4px;
|
64
|
+
border-left: thin solid #306;
|
65
|
+
}
|
66
|
+
div#subnavigation > a:first-child, div#navigation > a:first-child {
|
67
|
+
border-left: 0px;
|
68
|
+
}
|
69
|
+
input.extralarge {
|
70
|
+
width: 500px;
|
71
|
+
}
|
72
|
+
input.large {
|
73
|
+
width: 240px;
|
74
|
+
}
|
75
|
+
input.medium {
|
76
|
+
width: 120px;
|
77
|
+
}
|
78
|
+
input.small {
|
79
|
+
width: 60px;
|
80
|
+
}
|
81
|
+
div#confirm {
|
82
|
+
font-weight: bold;
|
83
|
+
font-size: 14px;
|
84
|
+
padding: 8px;
|
85
|
+
}
|
86
|
+
div.head {
|
87
|
+
border-color: #A00;
|
88
|
+
border-style: solid;
|
89
|
+
border-width: thin 0px 0px thin;
|
90
|
+
background-color: #F88;
|
91
|
+
padding: 4px 0px;
|
92
|
+
min-height: 15px;
|
93
|
+
}
|
94
|
+
div.foot {
|
95
|
+
background-color: #F88;
|
96
|
+
padding: 4px;
|
97
|
+
border-color: #A00;
|
98
|
+
border-style: solid;
|
99
|
+
border-width: 0px thin thin 0px;
|
100
|
+
}
|
101
|
+
div.padded {
|
102
|
+
padding: 4px;
|
103
|
+
}
|
104
|
+
td.right {
|
105
|
+
text-align: right;
|
106
|
+
}
|
107
|
+
span#total_brutto {
|
108
|
+
font-weight: bold;
|
109
|
+
}
|
110
|
+
span#version {
|
111
|
+
display: inline;
|
112
|
+
float: right;
|
113
|
+
}
|
data/lib/ydim/html.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
require 'rclconf'
|
5
|
+
require 'ydim/html'
|
6
|
+
|
7
|
+
module YDIM
|
8
|
+
module Html
|
9
|
+
ydim_default_dir = '/var/ydim/'
|
10
|
+
if(home = ENV['HOME'])
|
11
|
+
ydim_default_dir = File.join(home, '.ydim')
|
12
|
+
end
|
13
|
+
default_config_files = [
|
14
|
+
'etc/ydim-htmld.yml',
|
15
|
+
File.join(ydim_default_dir, 'ydim-htmld.yml'),
|
16
|
+
'/etc/ydim/ydim-htmld.yml',
|
17
|
+
]
|
18
|
+
defaults = {
|
19
|
+
'client_url' => 'druby://localhost:0',
|
20
|
+
'config' => default_config_files,
|
21
|
+
'currency' => 'CHF',
|
22
|
+
'email' => nil,
|
23
|
+
'html_url' => 'druby://localhost:12376',
|
24
|
+
'http_server' => 'http://localhost',
|
25
|
+
'log_file' => $stdout,
|
26
|
+
'log_level' => 'DEBUG',
|
27
|
+
'md5_pass' => nil,
|
28
|
+
'proxy_url' => 'druby://localhost:0',
|
29
|
+
'server_url' => 'druby://localhost:12375',
|
30
|
+
'root_key' => nil,
|
31
|
+
'user' => nil,
|
32
|
+
'ydim_dir' => ydim_default_dir,
|
33
|
+
}
|
34
|
+
@config = RCLConf::RCLConf.new(ARGV, defaults)
|
35
|
+
@config.load(@config.config)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# Html::State::AjaxValues -- ydim -- 18.01.2006 -- hwyss@ywesee.com
|
4
|
+
|
5
|
+
require 'sbsm/state'
|
6
|
+
require 'ydim/html/view/ajax_values'
|
7
|
+
|
8
|
+
module YDIM
|
9
|
+
module Html
|
10
|
+
module State
|
11
|
+
class AjaxValues < SBSM::State
|
12
|
+
VOLATILE = true
|
13
|
+
VIEW = Html::View::AjaxValues
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# Html::State::AutoInvoice -- ydim -- 13.12.2006 -- hwyss@ywesee.com
|
4
|
+
|
5
|
+
require 'ydim/html/state/invoice'
|
6
|
+
require 'ydim/html/view/autoinvoice'
|
7
|
+
|
8
|
+
module YDIM
|
9
|
+
module Html
|
10
|
+
module State
|
11
|
+
class AjaxAutoInvoice < SBSM::State
|
12
|
+
VOLATILE = true
|
13
|
+
VIEW = Html::View::AutoInvoiceComposite
|
14
|
+
end
|
15
|
+
module AutoInvoiceKeys
|
16
|
+
def invoice_key
|
17
|
+
:autoinvoice
|
18
|
+
end
|
19
|
+
def invoice_keys
|
20
|
+
super + [ :date, :reminder_body, :reminder_subject ]
|
21
|
+
end
|
22
|
+
def invoice_mandatory
|
23
|
+
[ :description, :currency, :invoice_interval ]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
class CreateAutoInvoice < CreateInvoice
|
27
|
+
include AutoInvoiceKeys
|
28
|
+
VIEW = Html::View::AutoInvoice
|
29
|
+
def update
|
30
|
+
_update(AutoInvoice)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
class AutoInvoice < Invoice
|
34
|
+
include AutoInvoiceKeys
|
35
|
+
VIEW = Html::View::AutoInvoice
|
36
|
+
def ajax_invoice
|
37
|
+
_do_update
|
38
|
+
AjaxAutoInvoice.new(@session, @model)
|
39
|
+
end
|
40
|
+
def format_invoice
|
41
|
+
lnf = @session.lookandfeel
|
42
|
+
total = lnf.lookup(:total_netto)
|
43
|
+
item_widths = [total.length]
|
44
|
+
qty_widths = []
|
45
|
+
float = false
|
46
|
+
@model.items.each do |item|
|
47
|
+
item_widths.push item.text.length
|
48
|
+
qty = item.quantity
|
49
|
+
qty_widths.push qty.to_i.to_s.length
|
50
|
+
float ||= (qty.to_i != qty.to_f)
|
51
|
+
end
|
52
|
+
item_width = item_widths.max.to_i.next
|
53
|
+
qty_width = qty_widths.max.to_i
|
54
|
+
total_width = ("%3.2f" % @model.total_netto).length
|
55
|
+
fmt = if float
|
56
|
+
qty_width += 3
|
57
|
+
"%#{qty_width}.2f x %-#{item_width}s %s %#{total_width}.2f\n"
|
58
|
+
else
|
59
|
+
"%#{qty_width}i x %-#{item_width}s %s %#{total_width}.2f\n"
|
60
|
+
end
|
61
|
+
invoice = "<invoice>\n"
|
62
|
+
currency = @model.currency
|
63
|
+
@model.items.each { |item|
|
64
|
+
invoice << sprintf(fmt, item.quantity.to_f, item.text,
|
65
|
+
currency, item.total_netto)
|
66
|
+
}
|
67
|
+
fmt = "%#{qty_width + 2}s %-#{item_width}s %s %#{total_width}.2f\n"
|
68
|
+
invoice << sprintf(fmt, '', total,
|
69
|
+
currency, @model.total_netto)
|
70
|
+
invoice << "</invoice>"
|
71
|
+
end
|
72
|
+
def generate_invoice
|
73
|
+
_do_update
|
74
|
+
if((id = @session.user_input(:unique_id)) \
|
75
|
+
&& @model.unique_id == id.to_i)
|
76
|
+
Invoice.new(@session, @session.generate_invoice(id))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
def _do_update_invoice(input)
|
80
|
+
ptrn = %r{<invoice>.*</invoice>}m
|
81
|
+
if(body = input[:reminder_body])
|
82
|
+
test = body.gsub(ptrn, "").strip
|
83
|
+
if(test.empty?)
|
84
|
+
input.store(:reminder_body, nil)
|
85
|
+
else
|
86
|
+
input.store(:reminder_body, body.gsub(ptrn, format_invoice))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
super(input)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# Html::State::Confirm -- ydim -- 18.01.2006 -- hwyss@ywesee.com
|
4
|
+
|
5
|
+
require 'ydim/html/state/global_predefine'
|
6
|
+
require 'ydim/html/view/confirm'
|
7
|
+
|
8
|
+
module YDIM
|
9
|
+
module Html
|
10
|
+
module State
|
11
|
+
class Confirm < Global
|
12
|
+
VIEW = Html::View::Confirm
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
# Html::State::Debitor -- ydim -- 12.01.2006 -- hwyss@ywesee.com
|
4
|
+
|
5
|
+
require 'ydim/html/state/global_predefine'
|
6
|
+
require 'ydim/html/state/ajax_values'
|
7
|
+
require 'ydim/html/state/invoices'
|
8
|
+
require 'ydim/html/view/debitor'
|
9
|
+
|
10
|
+
module YDIM
|
11
|
+
module Html
|
12
|
+
module State
|
13
|
+
class AjaxAutoInvoices < SBSM::State
|
14
|
+
VOLATILE = true
|
15
|
+
VIEW = Html::View::AutoInvoiceList
|
16
|
+
end
|
17
|
+
class Debitor < Global
|
18
|
+
include AjaxInvoiceMethods
|
19
|
+
attr_reader :model, :autoinvoice_infos, :invoice_infos
|
20
|
+
VIEW = Html::View::Debitor
|
21
|
+
def init
|
22
|
+
super
|
23
|
+
load_autoinvoices
|
24
|
+
load_invoices
|
25
|
+
end
|
26
|
+
def ajax_collect_garbage
|
27
|
+
@session.collect_garbage(@model.unique_id)
|
28
|
+
AjaxInvoices.new(@session, [])
|
29
|
+
end
|
30
|
+
def ajax_delete_autoinvoice
|
31
|
+
if(id = @session.user_input(:unique_id))
|
32
|
+
@session.delete_autoinvoice(id)
|
33
|
+
end
|
34
|
+
AjaxAutoInvoices.new(@session, load_autoinvoices)
|
35
|
+
end
|
36
|
+
def ajax_invoices
|
37
|
+
super(@invoice_infos)
|
38
|
+
end
|
39
|
+
def generate_invoice
|
40
|
+
if(id = @session.user_input(:unique_id))
|
41
|
+
Invoice.new(@session, @session.generate_invoice(id.to_i))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
def update
|
45
|
+
mandatory = [ :contact, :debitor_type, :emails,
|
46
|
+
:location, :name, ]
|
47
|
+
defaults = {}
|
48
|
+
keys = mandatory.dup.push(:address_lines, :contact_firstname,
|
49
|
+
:contact_title, :country, :salutation, :phone)
|
50
|
+
input = defaults.update(user_input(keys, mandatory))
|
51
|
+
mails = input[:emails]
|
52
|
+
if mails && mails.size > 3
|
53
|
+
@errors.store :emails, create_error('e_too_many_emails',
|
54
|
+
:emails, mails.join(', '))
|
55
|
+
input[:emails] = mails[0,3]
|
56
|
+
end
|
57
|
+
unless(error? || @model.unique_id)
|
58
|
+
@model = @session.create_debitor
|
59
|
+
end
|
60
|
+
update_model(input)
|
61
|
+
self
|
62
|
+
end
|
63
|
+
private
|
64
|
+
def load_autoinvoices
|
65
|
+
invoices = @model.autoinvoice_infos
|
66
|
+
@autoinvoice_infos = sort_invoices(currency_convert(invoices))
|
67
|
+
end
|
68
|
+
def load_invoices
|
69
|
+
invoices = @model.invoice_infos(@session.user_input(:status) \
|
70
|
+
|| 'is_open')
|
71
|
+
@invoice_infos = sort_invoices(currency_convert(invoices))
|
72
|
+
end
|
73
|
+
def update_model(input)
|
74
|
+
input.each { |key, val|
|
75
|
+
@model.send("#{key}=".to_sym, val)
|
76
|
+
}
|
77
|
+
@model.odba_store if(@model.unique_id)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|