sora 24.02.25 → 24.02.26

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 595fdec94ad7aa62fa2490265ffd6633b039773570518115afd13446cf75f5aa
4
- data.tar.gz: bab57e34ef9eb436ea590c8e5a3988cdd780e4ae657a254c5ecaadff82ef7239
3
+ metadata.gz: 488c9fe33a89000965087732638fafd292137467592fb99dc5341b45665a8b8a
4
+ data.tar.gz: 28c98019e9d9c51260264aec86c558fbc53538ae0aab46da688cd11a5623ee91
5
5
  SHA512:
6
- metadata.gz: 221d63074168d433bbff41cb5507e381fa683b9e6ba8c5a46234b3c6e0764d38e0097582eda7981eb156098783058ef968a8612509813767bf8535d3fd2ff4af
7
- data.tar.gz: 6c70324213cbf7a197939305b3c8892d3a4a7656ad6b689773fdc836354545f460ca06a257e899e034c374b22bf338c317426c0f2414e0115d2bb419df6c728c
6
+ metadata.gz: 022c2e1247b89595b5e0679739d4d99980d049a782b9ed1f4eaebedd89f92ea765cfc31afa5322dad4697ce45621502d376ed8d126d346f147257a935dd44b25
7
+ data.tar.gz: '0798d5c364a33d00dfd422badd7f544f1175ed09ce158294c11f5e881d1a3eae743e169301d2be4a7973da1920d8dab69a57018928f110184a80917d4e3edcfb'
data/.rubocop.yml CHANGED
@@ -17,3 +17,6 @@ Layout/EndOfLine:
17
17
 
18
18
  Metrics/MethodLength:
19
19
  Enabled: False
20
+
21
+ Metrics/AbcSize:
22
+ Enabled: False
data/README.md CHANGED
@@ -2,3 +2,19 @@
2
2
  sora is an http server in Ruby that can send and receive strings and files.
3
3
 
4
4
  sora (Server Of Ruby for Access) can send and receive files, and transmit text strings to communicate information.
5
+
6
+ - https://rubygems.org/gems/sora
7
+
8
+ ## Installation
9
+ This gem is supported on Windows.
10
+
11
+ ### How to Install
12
+ ```ps1
13
+ gem install sora
14
+ ```
15
+
16
+ or
17
+
18
+ ```ps1
19
+ rake install # Install from repository
20
+ ```
data/cgi-bin/get.rb ADDED
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cgi"
4
+ require "json"
5
+ require "securerandom"
6
+
7
+ def get_content_type(filename)
8
+ content_types = {
9
+ ".txt" => "text/plain",
10
+ ".json" => "application/json",
11
+ ".js" => "text/javascript",
12
+ ".css" => "text/css",
13
+ ".csv" => "text/csv",
14
+ ".pdf" => "application/pdf",
15
+ ".xls" => "application/vnd.ms-excel",
16
+ ".xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
17
+ ".doc" => "application/msword",
18
+ ".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
19
+ ".ppt" => "application/vnd.ms-powerpoint",
20
+ ".pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",
21
+ ".gif" => "image/gif",
22
+ ".bmp" => "image/bmp",
23
+ ".svg" => "image/svg+xml",
24
+ ".zip" => "application/zip",
25
+ ".lzh" => "application/x-lzh",
26
+ ".tar" => "application/x-tar",
27
+ ".mp3" => "audio/mpeg",
28
+ ".mp4" => "video/mp4",
29
+ ".mpeg" => "video/mpeg",
30
+ ".jpg" => "image/jpeg",
31
+ ".jpeg" => "image/jpeg",
32
+ ".png" => "image/png",
33
+ ".html" => "text/html"
34
+ }
35
+
36
+ ext = File.extname(filename)
37
+ content_types[ext] || "text/html"
38
+ end
39
+
40
+ def file
41
+ cgi = CGI.new
42
+ params = cgi.params
43
+ unless params.key?("filename")
44
+ print cgi.header("text/plain")
45
+ return
46
+ end
47
+
48
+ file_path = File.join(File.expand_path("../data"), params["filename"][0])
49
+ File.open(file_path, "rb") do |f|
50
+ cgi.out(
51
+ {
52
+ "type" => get_content_type(file_path),
53
+ "content-disposition" => "inline; filename=#{File.basename(file_path)}"
54
+ }
55
+ ) do
56
+ f.read
57
+ end
58
+ end
59
+ end
60
+
61
+ file
data/cgi-bin/main.rb CHANGED
@@ -7,8 +7,15 @@ require "securerandom"
7
7
  cgi = CGI.new
8
8
  print cgi.header("application/json")
9
9
  params = cgi.params
10
+
10
11
  @json_file_path = "../data/data.json"
11
12
 
13
+ def log(text)
14
+ open("tmp", "a") do |f|
15
+ f.puts(text)
16
+ end
17
+ end
18
+
12
19
  def read_json(filename)
13
20
  unless File.exist? filename
14
21
  File.open(filename, "w") do |f|
@@ -57,5 +64,54 @@ def delete(params)
57
64
  print JSON.pretty_generate(response)
58
65
  end
59
66
 
67
+ def save_file(params)
68
+ return unless params.key?("file")
69
+
70
+ file = params["file"][0]
71
+ response = {
72
+ filename: file.original_filename
73
+ }
74
+
75
+ return if [".gitignore", "data.json"].include?(response[:filename])
76
+
77
+ file_path = File.join(File.expand_path("../data"), response[:filename])
78
+
79
+ File.open(file_path, "wb") do |f|
80
+ f.write file.read
81
+ end
82
+
83
+ print JSON.pretty_generate(response)
84
+ end
85
+
86
+ def remove_file(params)
87
+ return unless params.key?("removefile")
88
+
89
+ removefile = params["removefile"][0]
90
+ response = {
91
+ filename: removefile,
92
+ status: "NG"
93
+ }
94
+
95
+ return if [".gitignore", "data.json"].include?(response[:filename])
96
+
97
+ file_path = File.join(File.expand_path("../data"), response[:filename])
98
+
99
+ response[:status] = "OK" if File.exist?(file_path) && File.delete(file_path)
100
+
101
+ print JSON.pretty_generate(response)
102
+ end
103
+
104
+ def get_files(params)
105
+ return unless params.key?("files")
106
+
107
+ response = Dir.glob(File.join(File.expand_path("../data"), "*"))
108
+ response.map! { |e| File.basename(e) }
109
+ response.filter! { |e| !["data.json", ".gitignore"].include?(e) }
110
+ print JSON.pretty_generate(response)
111
+ end
112
+
60
113
  get_json(params)
61
114
  delete(params)
115
+ save_file(params)
116
+ get_files(params)
117
+ remove_file(params)
data/data/.gitignore CHANGED
@@ -1 +1,2 @@
1
- data.json
1
+ !.gitignore
2
+ *
data/exe/sora CHANGED
@@ -6,9 +6,9 @@ require "optparse"
6
6
 
7
7
  opt = OptionParser.new
8
8
 
9
- params = { port: 8080, document_root: File.expand_path("../../www", __FILE__) }
9
+ params = { port: 8080, document_root: File.expand_path("../www", __dir__) }
10
10
 
11
- opt.on("-p [PORT]", "--port [PORT]") { |v| params[:port] = v.to_i }
11
+ opt.on("-p [PORT]", "--port [PORT]", "Port number, default: 8080") { |v| params[:port] = v.to_i }
12
12
 
13
13
  opt.parse!(ARGV)
14
14
 
data/lib/sora/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Sora
4
- VERSION = "24.02.25"
4
+ VERSION = "24.02.26"
5
5
  end
data/lib/sora.rb CHANGED
@@ -19,6 +19,7 @@ module Sora
19
19
  }
20
20
  )
21
21
  srv.mount("/cgi-bin", WEBrick::HTTPServlet::CGIHandler, "#{document_root}/../cgi-bin/main.rb")
22
+ srv.mount("/get", WEBrick::HTTPServlet::CGIHandler, "#{document_root}/../cgi-bin/get.rb")
22
23
  srv.mount("/", WEBrick::HTTPServlet::FileHandler, "#{document_root}/index.html")
23
24
  srv.mount("/style", WEBrick::HTTPServlet::FileHandler, "#{document_root}/style.css")
24
25
  srv.mount("/js", WEBrick::HTTPServlet::FileHandler, "#{document_root}/main.js")
data/www/index.html CHANGED
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html lang="ja">
3
3
 
4
4
  <head>
5
5
  <meta charset="utf-8">
@@ -10,18 +10,43 @@
10
10
 
11
11
  <body>
12
12
  <main id="main">
13
- <h1>sora</h1>
14
- <div class="Grid Text-SendButton">
15
- <input id="content" placeholder="Text..." class="TextBox" />
16
- <input type="button" id="send-button" value="Send" class="Accent Button SendButton" />
13
+ <div id="wrap">
14
+ <h1 id="title-sora">sora</h1>
15
+ <div class="Grid Text-SendButton">
16
+ <input id="content" placeholder="Text..." class="TextBox">
17
+ <input type="button" id="send-button" value="Send" class="Accent Button SendButton">
18
+ <input value="Select a File" type="button" id="select-file-button" class="Button">
19
+ <form id="upload-form" class="Hidden">
20
+ <input placeholder="none" type="file" name="file" id="select-file-button-pre">
21
+ </form>
22
+ </div>
23
+
24
+ <div id="file-list-wrap" class="Hidden">
25
+ <h2 id="file-list-title">Files</h2>
26
+ <div id="file-list">
27
+ <ul id="file-list-ul"></ul>
28
+ </div>
29
+ </div>
30
+
31
+ <div id="data-list-wrap" class="Hidden">
32
+ <h2 id="data-list-title">Texts</h2>
33
+ <div id="data-list">
34
+ </div>
35
+ </div>
17
36
  </div>
18
- <div id="data-list"></div>
19
37
  </main>
20
- <div id="contextmenu">
38
+ <div id="contextmenu" class="contextmenu">
21
39
  <ul>
40
+ <li id="right-click-copy" class="menu-top">Copy</li>
22
41
  <li id="right-click-delete">Delete</li>
23
42
  </ul>
24
43
  </div>
44
+ <div id="contextmenu-file" class="contextmenu">
45
+ <ul>
46
+ <li id="right-click-download-file" class="menu-top Hidden">Download</li>
47
+ <li id="right-click-delete-file">Delete</li>
48
+ </ul>
49
+ </div>
25
50
  <script src="/js"></script>
26
51
  </body>
27
52
 
data/www/main.js CHANGED
@@ -11,14 +11,27 @@ function EncodeHTMLForm(data) {
11
11
 
12
12
  const content = document.getElementById("content");
13
13
  const sendButton = document.getElementById("send-button");
14
+ const selectFileButton = document.getElementById("select-file-button");
15
+ const selectFileButtonPre = document.getElementById("select-file-button-pre");
14
16
  const rightClickDelete = document.getElementById("right-click-delete");
17
+ const rightClickDeleteFile = document.getElementById("right-click-delete-file");
18
+ const rightClickCopy = document.getElementById("right-click-copy");
19
+ const rightClickDownloadFile = document.getElementById("right-click-download-file");
20
+ const contextmenuFile = document.getElementById('contextmenu-file');
21
+ const fileListWrap = document.getElementById('file-list-wrap');
22
+ const dataListWrap = document.getElementById('data-list-wrap');
23
+ const fileListTitle = document.getElementById('file-list-title');
24
+ const dataListTitle = document.getElementById('data-list-title');
25
+
15
26
  let rightClickTargetId = "";
27
+ let fileTarget;
16
28
  let data = [];
17
29
 
18
30
  const refresh = () => {
19
31
  if(data == null) return;
20
32
  const dataList = document.getElementById("data-list");
21
33
  dataList.innerHTML = "";
34
+ dataListWrap.style.display = data.length == 0 ? "none" : "block";
22
35
  for(const text of data){
23
36
  const div = document.createElement("div");
24
37
  div.innerText = text['content'];
@@ -27,15 +40,14 @@ const refresh = () => {
27
40
  dataList.prepend(div);
28
41
 
29
42
  const contextmenu = document.getElementById('contextmenu');
30
- const main = div;
31
- main.addEventListener('contextmenu', (e) => {
43
+ div.addEventListener('contextmenu', (e) => {
32
44
  e.preventDefault();
33
45
  contextmenu.style.left = e.pageX + 'px';
34
46
  contextmenu.style.top = e.pageY + 'px';
35
47
  contextmenu.style.display = 'block';
36
48
  rightClickTargetId = e.target.id
37
49
  });
38
- main.addEventListener('click', () => {
50
+ div.addEventListener('click', () => {
39
51
  contextmenu.style.display = 'none';
40
52
  });
41
53
  }
@@ -45,23 +57,44 @@ const send = (write) => {
45
57
  const xhr = new XMLHttpRequest();
46
58
  xhr.open("POST", "/cgi-bin");
47
59
  write ? xhr.send(EncodeHTMLForm({ "data": content.value })) : xhr.send(EncodeHTMLForm({ "data": "" }));
60
+ if(write && content.value != ""){
61
+ const dataList = document.getElementById("data-list");
62
+ const div = document.createElement("div");
63
+ div.innerText = content.value;
64
+ div.className = "message";
65
+ dataList.prepend(div);
66
+ }
48
67
  xhr.onreadystatechange = () => {
49
68
  let texts = JSON.parse(xhr.responseText || "null");
50
69
  if(texts == null) return;
51
70
  data = texts;
52
71
  refresh();
72
+ sendButton.disabled = false;
73
+ sendButton.classList.remove("disabled")
53
74
  }
54
75
  }
55
76
 
56
77
  const i18n = () => {
57
78
  const lang = window.navigator.language;
58
- if(lang == 'ja'){
79
+ if(lang == 'ja' || lang == 'ja-JP'){
59
80
  content.placeholder = "テキストを入力...";
60
81
  sendButton.value = "送信";
61
- rightClickDelete.innerText = "削除"
82
+ rightClickDelete.innerText = "削除";
83
+ rightClickDeleteFile.innerText = "削除";
84
+ rightClickDownloadFile.innerText = "ダウンロード";
85
+ rightClickCopy.innerText = "コピー";
86
+ selectFileButton.value = "ファイルを選択";
87
+ fileListTitle.innerText = "ファイル一覧";
88
+ dataListTitle.innerText = "テキスト一覧";
62
89
  }
63
90
  }
64
91
 
92
+ rightClickCopy.addEventListener('click', () => {
93
+ const targetDOM = document.getElementById(rightClickTargetId);
94
+ navigator.clipboard.writeText(targetDOM.textContent);
95
+ contextmenu.style.display = 'none';
96
+ });
97
+
65
98
  rightClickDelete.addEventListener('click', () => {
66
99
  const targetDOM = document.getElementById(rightClickTargetId);
67
100
  targetDOM.remove();
@@ -79,6 +112,90 @@ rightClickDelete.addEventListener('click', () => {
79
112
  contextmenu.style.display = 'none';
80
113
  });
81
114
 
115
+ document.body.addEventListener('click', () => {
116
+ contextmenu.style.display = 'none';
117
+ contextmenuFile.style.display = 'none';
118
+ });
119
+
120
+ sendButton.addEventListener("click", () => {
121
+ if(content.value != ""){
122
+ sendButton.disabled = true;
123
+ sendButton.classList.add("disabled")
124
+ send(true)
125
+ }
126
+ });
127
+
128
+ selectFileButton.addEventListener('click', () => {
129
+ selectFileButtonPre.click();
130
+ });
131
+
132
+ selectFileButtonPre.addEventListener('change', (e) => {
133
+ const form = document.getElementById("upload-form");
134
+ const formData = new FormData(form);
135
+
136
+ const xhr = new XMLHttpRequest();
137
+ xhr.open("POST", "/cgi-bin");
138
+ xhr.upload.addEventListener('loadstart', () => {
139
+ console.log("Upload: start")
140
+ });
141
+
142
+ xhr.upload.addEventListener('load', () => {
143
+ // アップロード正常終了
144
+ console.log('Upload: done');
145
+ getFiles();
146
+ });
147
+
148
+ xhr.send(formData);
149
+ });
150
+
151
+ const getFiles = () => {
152
+ const xhr = new XMLHttpRequest();
153
+ xhr.open("POST", "/cgi-bin");
154
+ xhr.send(EncodeHTMLForm({ "files": "" }));
155
+ xhr.onreadystatechange = () => {
156
+ let texts = JSON.parse(xhr.responseText || "null");
157
+ if (texts == null)
158
+ return;
159
+
160
+ const fileListUl = document.getElementById('file-list-ul');
161
+ fileListUl.innerHTML = "";
162
+
163
+ const fileList = document.getElementById('file-list');
164
+ fileList.style.display = texts.length == 0 ? "none" : "block";
165
+ fileListWrap.style.display = texts.length == 0 ? "none" : "block";
166
+
167
+ for(const fileName of texts){
168
+ const div = document.createElement('li');
169
+ div.textContent = fileName;
170
+
171
+ div.addEventListener('contextmenu', (e) => {
172
+ e.preventDefault();
173
+ contextmenuFile.style.left = e.pageX + 'px';
174
+ contextmenuFile.style.top = e.pageY + 'px';
175
+ contextmenuFile.style.display = 'block';
176
+ fileTarget = e.target;
177
+ });
178
+ div.addEventListener('click', () => {
179
+ contextmenuFile.style.display = 'none';
180
+ location.href = `/get?filename=${fileName}`
181
+ });
182
+
183
+ fileListUl.append(div);
184
+ }
185
+ };
186
+ };
187
+
188
+ rightClickDeleteFile.addEventListener("click", () => {
189
+ const fileName = fileTarget.textContent;
190
+ fileTarget.remove();
191
+ const xhr = new XMLHttpRequest();
192
+ xhr.open("POST", "/cgi-bin");
193
+ xhr.send(EncodeHTMLForm({ "removefile": fileName }));
194
+ xhr.onreadystatechange = () => {
195
+ getFiles();
196
+ };
197
+ })
198
+
82
199
  i18n();
83
200
  send(false);
84
- sendButton.addEventListener("click", () => { send(true) });
201
+ getFiles();
data/www/style.css CHANGED
@@ -1,27 +1,42 @@
1
- body{
1
+ html {
2
+ height: 100%;
3
+ }
4
+
5
+ body {
2
6
  background-color: #F3F3F3;
3
7
  font-family: Segoe UI, Meiryo UI;
8
+ min-height: 100%;
9
+ margin: 0;
4
10
  }
5
11
 
6
- main{
12
+ main {
7
13
  max-width: 980px;
8
14
  margin: 0 auto;
9
15
  }
10
16
 
11
- .Grid{
17
+ #wrap {
18
+ margin: 0 16px;
19
+ padding-top: 8px;
20
+ }
21
+
22
+ .Grid {
12
23
  display: grid;
13
24
  }
14
25
 
15
- .Text-SendButton{
16
- grid-template-columns: 1fr auto;
26
+ .Text-SendButton {
27
+ grid-template-columns: 1fr auto auto;
17
28
  margin-bottom: 8px;
18
29
  }
19
30
 
20
- .Text-SendButton .TextBox{
31
+ .Text-SendButton .TextBox {
21
32
  margin-right: 8px;
22
33
  }
23
34
 
24
- .message{
35
+ #send-button{
36
+ margin-right: 8px;
37
+ }
38
+
39
+ .message {
25
40
  margin-bottom: 8px;
26
41
  padding: 16px;
27
42
  border: solid 1px #E5E5E5;
@@ -30,8 +45,49 @@ main{
30
45
  font-size: 14px;
31
46
  }
32
47
 
48
+ #contextmenu ul li {
49
+ margin-top: 4px;
50
+ }
51
+
52
+ #contextmenu ul li.menu-top {
53
+ margin-top: 0;
54
+ }
55
+
56
+ /* ファイルリスト */
57
+ #file-list{
58
+ background-color: #F9F9F9;
59
+ padding: 8px;
60
+ margin-bottom: 8px;
61
+ border: solid 1px #E3E3E3;
62
+ border-radius: 8px;
63
+ display: none;
64
+ }
65
+
66
+ #file-list-ul{
67
+ padding: 0;
68
+ margin: 0;
69
+ list-style-type: none;
70
+ user-select: none;
71
+ }
72
+
73
+ #file-list-ul li{
74
+ border-radius: 4px;
75
+ padding: 4.75px 12px 4.75px 12px;
76
+ cursor: pointer;
77
+ margin-top: 4px;
78
+ font-size: 14px;
79
+ }
80
+
81
+ #file-list-ul li:nth-child(1){
82
+ margin-top: 0;
83
+ }
84
+
85
+ #file-list-ul li:hover{
86
+ background-color: #F0F0F0;
87
+ }
88
+
33
89
  /* 右クリックメニュー */
34
- #contextmenu {
90
+ .contextmenu {
35
91
  display: none;
36
92
  position: fixed;
37
93
  left: 0;
@@ -43,12 +99,12 @@ main{
43
99
  width: 192px;
44
100
  }
45
101
 
46
- #contextmenu ul{
102
+ .contextmenu ul {
47
103
  padding-left: 0;
48
104
  margin: 0;
49
105
  }
50
106
 
51
- #contextmenu li {
107
+ .contextmenu li {
52
108
  cursor: pointer;
53
109
  margin: 0;
54
110
  padding: 4.75px 0 4.75px 16px;
@@ -58,12 +114,16 @@ main{
58
114
  user-select: none;
59
115
  }
60
116
 
61
- #contextmenu li:hover {
117
+ .contextmenu li:hover {
62
118
  background-color: #F0F0F0;
63
119
  }
64
120
 
65
121
  /* 共通コンポーネント */
66
- .TextBox{
122
+ .Hidden {
123
+ display: none;
124
+ }
125
+
126
+ .TextBox {
67
127
  height: 27px;
68
128
  border: solid 1px #E5E5E5;
69
129
  border-bottom-color: #868686;
@@ -73,11 +133,11 @@ main{
73
133
  background-color: #FBFBFB;
74
134
  }
75
135
 
76
- .TextBox:hover{
136
+ .TextBox:hover {
77
137
  background-color: #F6F6F6;
78
138
  }
79
139
 
80
- .TextBox:focus{
140
+ .TextBox:focus {
81
141
  background-color: white;
82
142
  height: 27px;
83
143
  border-bottom-color: #0067C0;
@@ -86,7 +146,8 @@ main{
86
146
  padding-bottom: 1px;
87
147
  }
88
148
 
89
- .Button{
149
+ .Button {
150
+ cursor: pointer;
90
151
  height: 32px;
91
152
  border: solid 1px #E5E5E5;
92
153
  border-bottom-color: #CCCCCC;
@@ -95,32 +156,40 @@ main{
95
156
  padding: 1px 12px;
96
157
  }
97
158
 
98
- .Button:hover{
159
+ .Button:hover {
99
160
  background-color: #F6F6F6;
100
161
  }
101
162
 
102
- .Button:active{
163
+ .Button:active {
103
164
  background-color: #F5F5F5;
104
165
  color: #5D5D5D;
105
166
  border-color: #E5E5E5;
106
167
  }
107
168
 
108
- .Button.Accent{
169
+ .Button.Accent {
109
170
  background-color: #0067C0;
110
171
  border: solid 1px #1473C5;
111
172
  border-bottom-color: #003E73;
112
173
  color: white;
113
174
  }
114
175
 
115
- .Button.Accent:hover{
176
+ .Button.Accent:hover {
116
177
  background-color: #1975C5;
117
178
  border: solid 1px #2B80CA;
118
179
  border-bottom-color: #0F4676;
119
180
  }
120
181
 
121
- .Button.Accent:active{
182
+ .Button.Accent:active {
122
183
  background-color: #3183CA;
123
184
  border: solid 1px #3183CA;
124
185
  border-bottom-color: #3183CA;
125
186
  color: #C2DAEF;
126
187
  }
188
+
189
+ .Button.disabled, .Button.disabled:hover {
190
+ cursor: not-allowed;
191
+ background-color: #F5F5F5 !important;
192
+ color: #9D9D9D;
193
+ border: solid 1px #E5E5E5 !important;
194
+ border-bottom-color: #E5E5E5 !important;
195
+ }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sora
3
3
  version: !ruby/object:Gem::Version
4
- version: 24.02.25
4
+ version: 24.02.26
5
5
  platform: ruby
6
6
  authors:
7
7
  - MURATA Mitsuharu
@@ -9,7 +9,21 @@ autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
11
  date: 2024-02-25 00:00:00.000000000 Z
12
- dependencies: []
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: webrick
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
13
27
  description: sora (Server Of Ruby for Access) can send and receive files, and transmit
14
28
  text strings to communicate information.
15
29
  email:
@@ -25,6 +39,7 @@ files:
25
39
  - LICENSE.txt
26
40
  - README.md
27
41
  - Rakefile
42
+ - cgi-bin/get.rb
28
43
  - cgi-bin/main.rb
29
44
  - data/.gitignore
30
45
  - exe/sora