geminabox 3.1.1 → 4.0.0

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.
data/public/master.css CHANGED
@@ -1,161 +1,431 @@
1
- @import "https://fonts.googleapis.com/css?family=Droid+Sans+Mono";
1
+ @import "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap";
2
2
 
3
- body {
4
- background: #ddd;
5
- color: #444;
6
- font-size: 1.2em;
7
- font-family: "helvetica neue", sans-serif;
8
- margin: 0;
3
+ * {
4
+ box-sizing: border-box;
9
5
  }
10
6
 
11
- small {
12
- color: #999;
13
- font-size: 60%;
7
+ body {
8
+ margin: 0;
9
+ padding: 36px 20px;
10
+ background: #e7e7ea;
11
+ color: #444;
12
+ font-family:
13
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
14
+ -webkit-font-smoothing: antialiased;
14
15
  }
15
16
 
16
17
  a {
18
+ color: #1a4fd6;
17
19
  text-decoration: none;
18
- color: #00a;
19
20
  }
20
21
 
21
22
  a:hover {
22
- color: #00f;
23
+ color: #123aa3;
24
+ text-decoration: underline;
23
25
  }
24
26
 
25
- a#more_link {
26
- font-size: 0.7em;
27
- line-height: 1.2;
28
- vertical-align: bottom;
27
+ code {
28
+ font-family: "JetBrains Mono", ui-monospace, monospace;
29
+ /* JetBrains Mono otherwise draws "~>" as an arrow glyph. */
30
+ font-variant-ligatures: none;
29
31
  }
30
32
 
31
33
  form {
32
34
  margin: 0;
33
35
  }
34
36
 
35
- code {
36
- font-size: 0.7em;
37
- font-family: "Droid Sans Mono", monospace;
38
- }
39
-
40
37
  #content {
41
- background: #fff;
42
- border: 1px solid #ccc;
43
- width: 600px;
38
+ max-width: 940px;
44
39
  margin: 0 auto;
45
- overflow: hidden;
46
- padding: 0.5em 0 1em 0;
47
- text-align: center;
40
+ background: #fff;
41
+ border-radius: 12px;
42
+ box-shadow:
43
+ 0 1px 3px rgba(0, 0, 0, 0.06),
44
+ 0 10px 30px rgba(0, 0, 0, 0.05);
45
+ padding: 52px 56px;
48
46
  }
49
47
 
50
48
  #content h1 {
51
49
  margin: 0;
52
- color: #a00;
53
- font-weight: 600;
54
- text-shadow: #aaa 1px 1px 1px;
50
+ text-align: center;
51
+ font-size: 46px;
52
+ font-weight: 800;
53
+ letter-spacing: -0.02em;
54
+ color: #a11212;
55
+ }
56
+
57
+ .intro {
58
+ max-width: 640px;
59
+ margin: 36px auto 0;
60
+ text-align: center;
61
+ }
62
+
63
+ .hint {
64
+ margin: 26px 0 10px;
65
+ font-size: 13px;
66
+ color: #8a8a8f;
67
+ }
68
+
69
+ .intro .hint:first-child {
70
+ margin-top: 0;
71
+ }
72
+
73
+ code.command {
74
+ display: block;
75
+ font-size: 14px;
76
+ color: #2a2a2e;
77
+ background: #f5f5f6;
78
+ border: 1px solid #ececee;
79
+ border-radius: 8px;
80
+ padding: 12px 16px;
81
+ line-height: 1.9;
82
+ overflow-x: auto;
83
+ }
84
+
85
+ code.command.snippet {
86
+ text-align: left;
87
+ white-space: pre;
88
+ }
89
+
90
+ .upload-link {
91
+ text-align: center;
92
+ margin: 40px 0 8px;
93
+ }
94
+
95
+ .upload-link a {
96
+ font-size: 24px;
97
+ font-weight: 700;
98
+ }
99
+
100
+ nav#alpha_index {
101
+ display: flex;
102
+ flex-wrap: wrap;
103
+ justify-content: center;
104
+ gap: 6px 14px;
105
+ margin: 28px 0 40px;
106
+ font-size: 16px;
107
+ text-transform: uppercase;
108
+ }
109
+
110
+ nav#alpha_index a {
111
+ font-weight: 700;
55
112
  }
56
113
 
57
- h2 {
58
- font-weight: 200;
59
- font-size: 1em;
60
- color: #000;
61
- margin: 0 0 0.5em 0;
62
- padding: 0.3em 0;
63
- background: #aaa;
64
- border-radius: 0.5em 0.5em 0 0;
65
- -moz-border-radius: 0.5em 0.5em 0 0;
66
- -webkit-border-radius: 0.5em 0.5em 0 0;
114
+ nav#alpha_index span {
115
+ color: #b7b7bc;
67
116
  }
68
117
 
69
118
  ul.gemlist {
70
119
  list-style: none;
71
- margin-top: 0em;
72
- margin-bottom: 2em;
73
- margin-left: 0;
74
- margin-right: 0;
120
+ margin: 0;
75
121
  padding: 0;
122
+ display: flex;
123
+ flex-direction: column;
124
+ gap: 22px;
76
125
  }
77
126
 
78
- ul.gemlist li {
79
- margin: 1em;
80
- background: #eee;
81
- border-radius: 0.5em;
82
- -moz-border-radius: 0.5em;
83
- -webkit-border-radius: 0.5em;
84
- position: relative;
127
+ .gem-card {
128
+ border: 1px solid #ececee;
129
+ border-radius: 10px;
85
130
  overflow: hidden;
86
131
  }
87
132
 
88
- ul.gemlist li strong {
89
- display: block;
90
- font-size: 1.2em;
91
- }
92
-
93
- ul.gemlist li .delete-form {
94
- position: relative;
95
- margin: 2px 0;
96
- }
97
- ul.gemlist li button,
98
- ul.gemlist li a.download {
99
- display: none;
100
- position: absolute;
101
- right: 0;
102
- bottom: 0;
103
- border-radius: 0.5em 0 0 0.5em;
104
- -moz-border-radius: 0.5em 0 0 0.5em;
105
- -webkit-border-radius: 0.5em 0 0 0.5em;
106
- background: #c55;
107
- border: 0;
108
- color: #fff;
133
+ .card-header {
134
+ background: #f6f6f7;
135
+ color: #1a1a1a;
136
+ border-bottom: 1px solid #ececee;
137
+ padding: 24px;
138
+ text-align: center;
139
+ scroll-margin-top: 12px;
140
+ }
141
+
142
+ .card-header h2 {
109
143
  margin: 0;
110
- font-size: 8pt;
111
- padding: 3px;
144
+ font-size: 22px;
145
+ font-weight: 700;
146
+ display: inline-flex;
147
+ align-items: center;
148
+ gap: 8px;
149
+ }
150
+
151
+ .card-header .permalink {
152
+ display: inline-flex;
153
+ align-items: center;
154
+ justify-content: center;
155
+ color: #b5b5ba;
156
+ opacity: 0;
157
+ transition:
158
+ opacity 0.15s ease,
159
+ color 0.15s ease;
160
+ }
161
+
162
+ .gem-card:hover .permalink,
163
+ .card-header .permalink:focus-visible {
164
+ opacity: 1;
165
+ }
166
+
167
+ .card-header .permalink:hover {
168
+ color: #333;
169
+ }
170
+
171
+ .card-body {
172
+ padding: 24px;
173
+ background: #fbfbfc;
174
+ display: flex;
175
+ flex-direction: column;
176
+ gap: 8px;
177
+ }
178
+
179
+ .version-row {
180
+ display: flex;
181
+ align-items: center;
182
+ justify-content: space-between;
183
+ gap: 14px;
184
+ background: #f4f4f5;
185
+ border: 1px solid #ececed;
186
+ border-radius: 8px;
187
+ padding: 9px 12px;
188
+ }
189
+
190
+ .version-row:hover {
191
+ background: #eef0f4;
192
+ }
193
+
194
+ .version-row code {
195
+ font-size: 13.5px;
196
+ color: #333;
197
+ white-space: nowrap;
198
+ overflow: hidden;
199
+ text-overflow: ellipsis;
200
+ }
201
+
202
+ .version-row small.platform {
203
+ flex-shrink: 0;
204
+ margin-left: auto;
205
+ font-size: 12px;
206
+ color: #8a8a8f;
207
+ }
208
+
209
+ .version-row .actions {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 7px;
213
+ flex-shrink: 0;
214
+ }
215
+
216
+ .version-row .actions button.copy-command {
217
+ display: inline-flex;
218
+ align-items: center;
219
+ justify-content: center;
220
+ width: 30px;
221
+ height: 28px;
222
+ padding: 0;
223
+ border: 1px solid #d9d9dc;
224
+ background: #fff;
225
+ border-radius: 6px;
112
226
  cursor: pointer;
227
+ color: #6b6b70;
113
228
  }
114
229
 
115
- ul.gemlist li a.download {
116
- background: #5c5;
117
- border-radius: 0.5em 0 0 0.5em;
118
- -moz-border-radius: 0.5em 0 0 0.5em;
119
- -webkit-border-radius: 0 0.5em 0.5em 0;
120
- left: 0;
121
- right: auto;
230
+ .version-row .actions button.copy-command:hover {
231
+ background: #eef0f4;
232
+ color: #333;
122
233
  }
123
234
 
124
- ul.gemlist li:hover button,
125
- ul.gemlist li:hover a.download {
126
- margin: 0;
127
- display: block;
235
+ .version-row .actions button.copy-command.copied {
236
+ color: #3fa34d;
237
+ border-color: #bfe3c5;
238
+ }
239
+
240
+ .version-row .actions a.download {
241
+ font-size: 12px;
242
+ font-weight: 600;
243
+ color: #fff;
244
+ background: #3fa34d;
245
+ padding: 5px 12px;
246
+ border-radius: 6px;
128
247
  }
129
248
 
130
- .details {
249
+ .version-row .actions a.download:hover {
250
+ background: #358841;
251
+ color: #fff;
252
+ text-decoration: none;
253
+ }
254
+
255
+ .version-row .actions button[type="submit"] {
256
+ font-size: 12px;
257
+ font-weight: 600;
258
+ font-family: inherit;
259
+ color: #fff;
260
+ background: #d94f4f;
261
+ border: 0;
262
+ padding: 5px 12px;
263
+ border-radius: 6px;
264
+ cursor: pointer;
265
+ }
266
+
267
+ .version-row .actions button[type="submit"]:hover {
268
+ background: #c23e3e;
269
+ }
270
+
271
+ .more-link {
272
+ text-align: center;
273
+ padding: 6px 0 2px;
274
+ }
275
+
276
+ .more-link a {
277
+ font-weight: 600;
278
+ }
279
+
280
+ .card-footer {
281
+ padding: 24px;
282
+ background: #f0f0f2;
283
+ border-top: 1px solid #ececee;
284
+ text-align: center;
285
+ }
286
+
287
+ .card-footer p {
131
288
  margin: 0;
289
+ font-size: 14px;
290
+ line-height: 1.5;
291
+ color: #4a4a4e;
292
+ }
293
+
294
+ .card-footer p.author {
295
+ margin-top: 8px;
296
+ color: #666;
297
+ }
298
+
299
+ .card-footer p.author a {
300
+ font-weight: 500;
301
+ }
302
+
303
+ .back-link {
304
+ margin: 0 0 24px;
305
+ }
306
+
307
+ .upload-form {
308
+ text-align: center;
309
+ margin-top: 24px;
310
+ }
311
+
312
+ .upload-form input[type="file"] {
313
+ font-size: 14px;
314
+ }
315
+
316
+ .upload-form input[type="submit"] {
317
+ font-size: 14px;
318
+ font-weight: 600;
319
+ font-family: inherit;
320
+ color: #fff;
321
+ background: #1a4fd6;
322
+ border: 0;
323
+ padding: 9px 18px;
324
+ border-radius: 8px;
325
+ cursor: pointer;
326
+ margin-left: 10px;
327
+ }
328
+
329
+ .upload-form input[type="submit"]:hover {
330
+ background: #123aa3;
331
+ }
332
+
333
+ @media (max-width: 700px) {
334
+ body {
335
+ padding: 16px 8px;
336
+ }
337
+
338
+ #content {
339
+ padding: 28px 20px;
340
+ }
341
+
342
+ #content h1 {
343
+ font-size: 32px;
344
+ }
345
+ }
346
+
347
+ dialog#delete-confirm {
348
+ width: min(468px, calc(100vw - 48px));
349
+ border: 0;
350
+ border-radius: 14px;
351
+ box-shadow: 0 18px 50px rgba(0, 0, 0, 0.28);
352
+ padding: 0;
132
353
  overflow: hidden;
133
- margin-top: 1em;
134
- font-size: 0.7em;
135
- border-radius: 0 0 0.5em 0.5em;
136
- -moz-border-radius: 0 0 0.5em 0.5em;
137
- -webkit-border-radius: 0 0 0.5em 0.5em;
138
- border-top: 1px dashed #ddd;
139
- background: #ddd;
354
+ font-family:
355
+ -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
356
+ }
357
+
358
+ dialog#delete-confirm::backdrop {
359
+ background: rgba(20, 20, 24, 0.42);
360
+ backdrop-filter: blur(2px);
361
+ }
362
+
363
+ .delete-confirm-body {
364
+ display: flex;
365
+ gap: 16px;
366
+ padding: 28px 28px 22px;
367
+ text-align: left;
140
368
  }
141
369
 
142
- #alpha_index {
143
- font-size: 0.8em;
144
- margin-top: 1em;
145
- color: #aaa;
370
+ .delete-confirm-body .accent {
371
+ flex-shrink: 0;
372
+ width: 4px;
373
+ border-radius: 4px;
374
+ background: #d94f4f;
146
375
  }
147
376
 
148
- .gem-version p {
377
+ .delete-confirm-body h3 {
378
+ margin: 0 0 12px;
379
+ font-size: 18px;
380
+ font-weight: 700;
381
+ color: #1a1a1a;
382
+ line-height: 1.35;
383
+ text-wrap: pretty;
384
+ }
385
+
386
+ .delete-confirm-body p {
149
387
  margin: 0;
150
- padding: 1px 0;
151
- border: 0;
152
- font-weight: normal;
153
- font-style: normal;
154
- font-size: 100%;
155
- line-height: 1;
156
- font-family: inherit;
388
+ font-size: 14px;
389
+ line-height: 1.6;
390
+ color: #55555b;
391
+ text-wrap: pretty;
392
+ }
393
+
394
+ .delete-confirm-actions {
395
+ display: flex;
396
+ justify-content: flex-end;
397
+ gap: 10px;
398
+ padding: 16px 28px;
399
+ background: #f6f6f7;
400
+ border-top: 1px solid #ececee;
401
+ }
402
+
403
+ .delete-confirm-actions button {
404
+ font-size: 14px;
405
+ font-weight: 600;
406
+ padding: 9px 18px;
407
+ border-radius: 8px;
408
+ cursor: pointer;
409
+ }
410
+
411
+ .delete-confirm-actions .cancel {
412
+ color: #333;
413
+ background: #fff;
414
+ border: 1px solid #d3d3d7;
415
+ }
416
+
417
+ .delete-confirm-actions .cancel:hover {
418
+ background: #eef0f4;
419
+ }
420
+
421
+ .delete-confirm-actions .danger {
422
+ color: #fff;
423
+ background: #d94f4f;
424
+ border: 1px solid #d94f4f;
425
+ white-space: nowrap;
157
426
  }
158
427
 
159
- .gem-version small.platform {
160
- padding: 0 0 0 15px;
428
+ .delete-confirm-actions .danger:hover {
429
+ background: #c23e3e;
430
+ border-color: #c23e3e;
161
431
  }
data/public/master.js CHANGED
@@ -1,11 +1,134 @@
1
- [].forEach.call(document.querySelectorAll("form.delete-form"), function (el) {
2
- el.addEventListener(
3
- "submit",
4
- function (ev) {
5
- if (!confirm("Are you sure you want to delete this gem?")) {
6
- ev.preventDefault();
7
- }
8
- },
9
- false
1
+ (function () {
2
+ var dialog = document.getElementById("delete-confirm");
3
+
4
+ function confirmationText(form) {
5
+ var name = form.getAttribute("data-gem-name");
6
+ var version = form.getAttribute("data-version");
7
+ var platform = form.getAttribute("data-platform");
8
+
9
+ if (!name || !version) {
10
+ return {
11
+ title: "Permanently delete this version from the server?",
12
+ body:
13
+ "This can't be undone, and projects that depend on this exact " +
14
+ "version will no longer be able to install it. The version number " +
15
+ "stays available, so it can be uploaded again afterward."
16
+ };
17
+ }
18
+
19
+ var full = name + " " + version;
20
+ if (platform && !/^ruby/i.test(platform)) {
21
+ full += " (" + platform + ")";
22
+ }
23
+ return {
24
+ title: "Permanently delete " + full + " from this server?",
25
+ body:
26
+ "This can't be undone, and projects that depend on this exact " +
27
+ "version will no longer be able to install it. The version number " +
28
+ "itself stays available: you can upload a new " +
29
+ name +
30
+ " " +
31
+ version +
32
+ " afterward."
33
+ };
34
+ }
35
+
36
+ function openDialog(form) {
37
+ var text = confirmationText(form);
38
+ dialog.querySelector("h3").textContent = text.title;
39
+ dialog.querySelector("p").textContent = text.body;
40
+ dialog.showModal();
41
+
42
+ dialog.querySelector("button.danger").onclick = function () {
43
+ dialog.close();
44
+ form.submit();
45
+ };
46
+ }
47
+
48
+ if (dialog) {
49
+ dialog.querySelector("button.cancel").addEventListener(
50
+ "click",
51
+ function () {
52
+ dialog.close();
53
+ },
54
+ false
55
+ );
56
+ // A click on the backdrop targets the dialog element itself.
57
+ dialog.addEventListener(
58
+ "click",
59
+ function (ev) {
60
+ if (ev.target === dialog) {
61
+ dialog.close();
62
+ }
63
+ },
64
+ false
65
+ );
66
+ }
67
+
68
+ var CHECK_ICON =
69
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" ' +
70
+ 'stroke="#3fa34d" stroke-width="2.5" stroke-linecap="round" ' +
71
+ 'stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
72
+
73
+ function copyText(text) {
74
+ if (navigator.clipboard && window.isSecureContext) {
75
+ navigator.clipboard.writeText(text);
76
+ return;
77
+ }
78
+ // Plain-http deployments have no navigator.clipboard.
79
+ var scratch = document.createElement("textarea");
80
+ scratch.value = text;
81
+ scratch.setAttribute("readonly", "");
82
+ scratch.style.position = "absolute";
83
+ scratch.style.left = "-9999px";
84
+ document.body.appendChild(scratch);
85
+ scratch.select();
86
+ try {
87
+ document.execCommand("copy");
88
+ } catch (e) {}
89
+ document.body.removeChild(scratch);
90
+ }
91
+
92
+ [].forEach.call(
93
+ document.querySelectorAll("button.copy-command"),
94
+ function (button) {
95
+ var originalIcon = button.innerHTML;
96
+ button.addEventListener(
97
+ "click",
98
+ function () {
99
+ var code = button.closest(".version-row").querySelector("code");
100
+ copyText(code.textContent.replace(/\s+/g, " ").trim());
101
+ button.innerHTML = CHECK_ICON;
102
+ button.classList.add("copied");
103
+ clearTimeout(button.__copyTimer);
104
+ button.__copyTimer = setTimeout(function () {
105
+ button.innerHTML = originalIcon;
106
+ button.classList.remove("copied");
107
+ }, 1200);
108
+ },
109
+ false
110
+ );
111
+ }
10
112
  );
11
- });
113
+
114
+ [].forEach.call(
115
+ document.querySelectorAll("form.delete-form"),
116
+ function (form) {
117
+ form.addEventListener(
118
+ "submit",
119
+ function (ev) {
120
+ if (dialog && typeof dialog.showModal === "function") {
121
+ ev.preventDefault();
122
+ openDialog(form);
123
+ } else {
124
+ var text = confirmationText(form);
125
+ if (!confirm(text.title + "\n\n" + text.body)) {
126
+ ev.preventDefault();
127
+ }
128
+ }
129
+ },
130
+ false
131
+ );
132
+ }
133
+ );
134
+ })();
@@ -0,0 +1,16 @@
1
+ <div class="version-row">
2
+ <code>gem "<%= h(version.name) %>", "~> <%= h(version.number) %>"</code>
3
+ <% unless version.platform =~ /^ruby/i %>
4
+ <small class="platform"><%= h(version.platform) %></small>
5
+ <% end %>
6
+ <div class="actions">
7
+ <button type="button" class="copy-command" title="Copy Gemfile line" aria-label="Copy Gemfile line"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"></rect><rect x="4" y="4" width="11" height="11" rx="2"></rect></svg></button>
8
+ <a class="download" href="<%= h(url("/gems/#{version.gemfile_name}.gem")) %>">download</a>
9
+ <% if @allow_delete %>
10
+ <form class="delete-form" method="post" action="<%= h(url("/gems/#{version.gemfile_name}.gem")) %>" data-gem-name="<%= h(version.name) %>" data-version="<%= h(version.number) %>" data-platform="<%= h(version.platform) %>">
11
+ <input type="hidden" name="_method" value="DELETE" />
12
+ <button type="submit">delete</button>
13
+ </form>
14
+ <% end %>
15
+ </div>
16
+ </div>