jekyll-dice-tray 0.1.1 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4f49149fc1cd489c751a686785425dc11dc5ce28672b9a296360d08ee14faa2e
4
- data.tar.gz: 9c63fa27e57d9b64b846ec5c5c7a753747b46a98bc9ad61de422c83e10afe7b8
3
+ metadata.gz: d3d407c51d39049511c08f665a5b70a71d0eff1a0ec1522472948ce6350fd670
4
+ data.tar.gz: d2ae3f53ca8018c55bfff796cba3781684076bdeb8ba5bfdc9307c5f3e30c127
5
5
  SHA512:
6
- metadata.gz: b7eee62b2881996cbce8cd8dda45ae0a932a609ac5beb02c4a9edc3b70873517b5f8485789845809137f292efde3a70115eeeb08532120b0764986b526584561
7
- data.tar.gz: f5572b43292713c2194c6a01d63e16d09e285bd16496b1749b6676d2d743a7a90789d1d35cea53d6da637113cf58c572d5fe9c37db58e3c024931f7b57cb3a14
6
+ metadata.gz: e30263b41fc804cb82a2f6ba76a0697bd31e0e6d5ace3550a94fb6d41d20a25eec1c2db6e65048ea94b549b3137de4a079dc57c7c9f6209b6b841d5b20723ac4
7
+ data.tar.gz: 843991fe95b1a12b60550fc6bc462ea547b46e23b740eda47fe3a454dda4d0e34c4757d7ba8f2f3ae68989814c551b3011fbb571b7f022ab1dd404a4f28942fe
data/README.md CHANGED
@@ -4,10 +4,10 @@ A Jekyll plugin gem that:
4
4
 
5
5
  - Adds an overlay dice tray that accepts dice rolling input
6
6
  - Recognizes dice expressions in rendered pages like `d4`, `1d6`, `1d20+1` and turns them into clickable links that roll in the tray
7
- - Recognizes THAC0-style bracket modifiers like `THAC0 18 [+1]` or `18 [+1]` in a THAC0 table row/column; the `+1` inside the brackets is clickable and rolls `d20+1` (other `[+n]` text is left alone)
7
+ - Clicking a lookup-table die in the header row (e.g. `1d12`) rolls and shows the matching row
8
8
  - Persists input history, result history, minimize status
9
- - `/help` - help
10
- - `/clear` - clears results and input history
9
+
10
+ https://github.com/user-attachments/assets/6ef8d788-a1ee-49d4-ab26-f63899bb4a10
11
11
 
12
12
  ## Install
13
13
 
@@ -114,12 +114,67 @@
114
114
  color: rgba(0, 0, 0, 0.45);
115
115
  }
116
116
 
117
+ #jekyll-dice-tray .jdt-success {
118
+ color: #1a7f37;
119
+ }
120
+
121
+ #jekyll-dice-tray .jdt-failure {
122
+ color: #cf222e;
123
+ }
124
+
117
125
  #jekyll-dice-tray .jdt-entry .jdt-details {
118
126
  margin-top: 4px;
119
127
  color: rgba(0, 0, 0, 0.55);
120
128
  font-size: 12px;
121
129
  }
122
130
 
131
+ #jekyll-dice-tray .jdt-table-context {
132
+ margin-top: 6px;
133
+ padding-top: 6px;
134
+ border-top: 1px dashed rgba(0, 0, 0, 0.08);
135
+ }
136
+
137
+ #jekyll-dice-tray .jdt-table-row {
138
+ font-size: 12px;
139
+ line-height: 1.4;
140
+ color: rgba(0, 0, 0, 0.72);
141
+ }
142
+
143
+ #jekyll-dice-tray .jdt-table-row + .jdt-table-row {
144
+ margin-top: 2px;
145
+ }
146
+
147
+ #jekyll-dice-tray .jdt-table-header {
148
+ font-weight: 600;
149
+ color: rgba(0, 0, 0, 0.55);
150
+ }
151
+
152
+ #jekyll-dice-tray .jdt-table-sep {
153
+ color: rgba(0, 0, 0, 0.45);
154
+ }
155
+
156
+ #jekyll-dice-tray .jdt-table-value {
157
+ font-family: inherit;
158
+ }
159
+
160
+ #jekyll-dice-tray .jdt-table-value a,
161
+ #jekyll-dice-tray .jdt-table-header a {
162
+ text-decoration: underline;
163
+ text-underline-offset: 2px;
164
+ }
165
+
166
+ #jekyll-dice-tray .jdt-table-value a.dice-tray-roll {
167
+ color: #ff0000;
168
+ }
169
+
170
+ #jekyll-dice-tray .jdt-table-value a:visited {
171
+ color: inherit;
172
+ }
173
+
174
+ #jekyll-dice-tray .jdt-table-value a.dice-tray-roll:visited {
175
+ color: #ff0000;
176
+ }
177
+
123
178
  a.dice-tray-roll {
124
179
  text-decoration: underline;
125
180
  text-underline-offset: 2px;
@@ -168,6 +223,22 @@ a.dice-tray-roll:visited {
168
223
  color: rgba(255, 255, 255, 0.6);
169
224
  }
170
225
 
226
+ #jekyll-dice-tray .jdt-table-context {
227
+ border-top-color: rgba(255, 255, 255, 0.1);
228
+ }
229
+
230
+ #jekyll-dice-tray .jdt-table-row {
231
+ color: rgba(255, 255, 255, 0.78);
232
+ }
233
+
234
+ #jekyll-dice-tray .jdt-table-header {
235
+ color: rgba(255, 255, 255, 0.62);
236
+ }
237
+
238
+ #jekyll-dice-tray .jdt-table-sep {
239
+ color: rgba(255, 255, 255, 0.48);
240
+ }
241
+
171
242
  #jekyll-dice-tray .jdt-result .jdt-rolls {
172
243
  color: rgba(255, 255, 255, 0.65);
173
244
  }
@@ -179,5 +250,13 @@ a.dice-tray-roll:visited {
179
250
  #jekyll-dice-tray .jdt-result .jdt-vs-sep {
180
251
  color: rgba(255, 255, 255, 0.48);
181
252
  }
253
+
254
+ #jekyll-dice-tray .jdt-success {
255
+ color: #3fb950;
256
+ }
257
+
258
+ #jekyll-dice-tray .jdt-failure {
259
+ color: #f85149;
260
+ }
182
261
  }
183
262
 
@@ -93,6 +93,28 @@
93
93
  };
94
94
  }
95
95
 
96
+ var chance = compact.match(/^(\d{1,3})-in-(\d{1,4})$/i);
97
+ if (chance) {
98
+ var successOn = parseInt(chance[1], 10);
99
+ var sides = parseInt(chance[2], 10);
100
+
101
+ if (!Number.isFinite(successOn) || !Number.isFinite(sides)) {
102
+ return { kind: "invalid", raw: s };
103
+ }
104
+ if (successOn < 1) successOn = 1;
105
+ if (successOn > 100) successOn = 100;
106
+ if (sides < 2) sides = 2;
107
+ if (sides > 10000) sides = 10000;
108
+ if (successOn > sides) return { kind: "invalid", raw: s };
109
+
110
+ return {
111
+ kind: "chance",
112
+ success_on: successOn,
113
+ sides: sides,
114
+ normalized: String(successOn) + "-in-" + String(sides),
115
+ };
116
+ }
117
+
96
118
  var m = compact.match(/^(\d{0,3})d(\d{1,4})([+-]\d{1,5})?$/i);
97
119
  if (!m) return { kind: "invalid", raw: s };
98
120
 
@@ -126,6 +148,136 @@
126
148
  }
127
149
 
128
150
  function mountTray(root) {
151
+ function normalizeCellText(node) {
152
+ return String(node && node.textContent ? node.textContent : "")
153
+ .replace(/\s+/g, " ")
154
+ .trim();
155
+ }
156
+
157
+ function cellInnerHtml(node) {
158
+ return node && node.innerHTML ? node.innerHTML.trim() : "";
159
+ }
160
+
161
+ function findTableHeaderRow(table) {
162
+ if (!table) return null;
163
+ var thead = table.querySelector("thead");
164
+ if (thead) {
165
+ var theadRows = thead.querySelectorAll("tr");
166
+ if (theadRows.length) return theadRows[theadRows.length - 1];
167
+ }
168
+ var rows = table.querySelectorAll("tr");
169
+ for (var i = 0; i < rows.length; i++) {
170
+ if (rows[i].querySelector("th")) return rows[i];
171
+ }
172
+ return table.querySelector("tr");
173
+ }
174
+
175
+ function parseFirstColumnEntry(text) {
176
+ if (!text) return null;
177
+ var normalized = text.replace(/[\u2013\u2014]/g, "-");
178
+ if (/^\d+$/.test(normalized)) {
179
+ return { kind: "exact", value: parseInt(normalized, 10) };
180
+ }
181
+ var rangeMatch = normalized.match(/^(\d+)\s*-\s*(\d+)$/);
182
+ if (rangeMatch) {
183
+ var min = parseInt(rangeMatch[1], 10);
184
+ var max = parseInt(rangeMatch[2], 10);
185
+ if (min <= max) return { kind: "range", min: min, max: max };
186
+ }
187
+ return null;
188
+ }
189
+
190
+ function firstColumnEntry(row) {
191
+ if (!row) return null;
192
+ var cell = row.querySelector("td, th");
193
+ if (!cell) return null;
194
+ return parseFirstColumnEntry(normalizeCellText(cell));
195
+ }
196
+
197
+ function isLookupTable(table, headerRow) {
198
+ if (!table || !headerRow) return false;
199
+ var rows = table.querySelectorAll("tr");
200
+ var matches = 0;
201
+ for (var i = 0; i < rows.length; i++) {
202
+ if (rows[i] === headerRow) continue;
203
+ if (firstColumnEntry(rows[i]) !== null) matches++;
204
+ }
205
+ return matches >= 2;
206
+ }
207
+
208
+ function findLookupRow(table, headerRow, rollTotal) {
209
+ var rows = table.querySelectorAll("tr");
210
+ var rangeRow = null;
211
+ for (var i = 0; i < rows.length; i++) {
212
+ var row = rows[i];
213
+ if (row === headerRow) continue;
214
+ var entry = firstColumnEntry(row);
215
+ if (!entry) continue;
216
+ if (entry.kind === "exact" && entry.value === rollTotal) return row;
217
+ if (entry.kind === "range" && !rangeRow && rollTotal >= entry.min && rollTotal <= entry.max) {
218
+ rangeRow = row;
219
+ }
220
+ }
221
+ return rangeRow;
222
+ }
223
+
224
+ function getLookupTableContext(anchor, rollTotal) {
225
+ if (!anchor || !anchor.closest || rollTotal == null) return null;
226
+ var cell = anchor.closest("td, th");
227
+ if (!cell) return null;
228
+ var clickRow = cell.closest("tr");
229
+ if (!clickRow) return null;
230
+ var table = clickRow.closest("table");
231
+ if (!table) return null;
232
+
233
+ var headerRow = findTableHeaderRow(table);
234
+ if (!headerRow || !headerRow.contains(cell)) return null;
235
+ if (!isLookupTable(table, headerRow)) return null;
236
+
237
+ var matchedRow = findLookupRow(table, headerRow, rollTotal);
238
+ if (!matchedRow) return null;
239
+
240
+ var headerCells = Array.prototype.slice.call(headerRow.querySelectorAll("td, th"));
241
+ var valueCells = Array.prototype.slice.call(matchedRow.querySelectorAll("td, th"));
242
+ var columns = [];
243
+
244
+ for (var c = 0; c < valueCells.length; c++) {
245
+ var headerCell = headerCells[c];
246
+ var valueCell = valueCells[c];
247
+ columns.push({
248
+ header: headerCell ? normalizeCellText(headerCell) : "",
249
+ header_html: headerCell ? cellInnerHtml(headerCell) : "",
250
+ value: normalizeCellText(valueCell),
251
+ value_html: cellInnerHtml(valueCell),
252
+ });
253
+ }
254
+
255
+ return columns.length ? { columns: columns } : null;
256
+ }
257
+
258
+ function appendTableContext(entry, tableContext) {
259
+ if (!tableContext || !tableContext.columns || !tableContext.columns.length) return;
260
+
261
+ var ctx = el("div", { class: "jdt-table-context" });
262
+ tableContext.columns.forEach(function (col) {
263
+ if (!col || (!col.value && !col.value_html)) return;
264
+ var line = el("div", { class: "jdt-table-row" });
265
+ if (col.header || col.header_html) {
266
+ var headerSpan = el("span", { class: "jdt-table-header" });
267
+ if (col.header_html) headerSpan.innerHTML = col.header_html;
268
+ else headerSpan.textContent = col.header;
269
+ line.appendChild(headerSpan);
270
+ line.appendChild(el("span", { class: "jdt-table-sep" }, ": "));
271
+ }
272
+ var valueSpan = el("span", { class: "jdt-table-value" });
273
+ if (col.value_html) valueSpan.innerHTML = col.value_html;
274
+ else valueSpan.textContent = col.value || "";
275
+ line.appendChild(valueSpan);
276
+ ctx.appendChild(line);
277
+ });
278
+ if (ctx.childNodes.length) entry.appendChild(ctx);
279
+ }
280
+
129
281
  var toggle = qs(".jdt-toggle", root);
130
282
  var body = qs(".jdt-body", root);
131
283
  var input = qs(".jdt-input", root);
@@ -237,10 +389,31 @@
237
389
  item.right_label || "",
238
390
  item.mod || 0,
239
391
  item.time || "",
240
- item.mode || "high"
392
+ item.mode || "high",
393
+ item.table_context || null,
394
+ true
395
+ );
396
+ } else if (item.kind === "chance") {
397
+ addChanceEntry(
398
+ item.expr || "",
399
+ item.roll,
400
+ item.success_on,
401
+ item.sides,
402
+ !!item.success,
403
+ item.time || "",
404
+ item.table_context || null,
405
+ true
241
406
  );
242
407
  } else if (item.kind === "roll") {
243
- addRollEntry(item.expr || "", item.total, item.rolls || [], item.mod || 0, item.time || "");
408
+ addRollEntry(
409
+ item.expr || "",
410
+ item.total,
411
+ item.rolls || [],
412
+ item.mod || 0,
413
+ item.time || "",
414
+ item.table_context || null,
415
+ true
416
+ );
244
417
  }
245
418
  });
246
419
  log.scrollTop = log.scrollHeight;
@@ -264,10 +437,41 @@
264
437
  pushHistory({ kind: "system", title: title, body: body, time: timeStr });
265
438
  }
266
439
 
267
- function addRollEntry(expr, total, rolls, mod, timeStr) {
440
+ function addChanceEntry(expr, roll, successOn, sides, success, timeStr, tableContext, skipHistory) {
441
+ var entry = el("div", { class: "jdt-entry", title: expr });
442
+ var colorClass = success ? "jdt-success" : "jdt-failure";
443
+ var result = el("div", { class: "jdt-result" });
444
+
445
+ result.appendChild(el("strong", { class: colorClass }, String(roll)));
446
+
447
+ var bracket = el("span", { class: "jdt-rolls" }, " [");
448
+ bracket.appendChild(el("span", { class: colorClass }, String(roll)));
449
+ bracket.appendChild(el("span", null, "]"));
450
+ result.appendChild(bracket);
451
+
452
+ entry.appendChild(result);
453
+ appendTableContext(entry, tableContext);
454
+
455
+ log.appendChild(entry);
456
+ log.scrollTop = log.scrollHeight;
457
+
458
+ if (skipHistory) return;
459
+
460
+ var item = {
461
+ kind: "chance",
462
+ expr: expr,
463
+ roll: roll,
464
+ success_on: successOn,
465
+ sides: sides,
466
+ success: success,
467
+ time: timeStr,
468
+ };
469
+ if (tableContext) item.table_context = tableContext;
470
+ pushHistory(item);
471
+ }
472
+
473
+ function addRollEntry(expr, total, rolls, mod, timeStr, tableContext, skipHistory) {
268
474
  var entry = el("div", { class: "jdt-entry", title: expr });
269
- // expression
270
- //entry.appendChild(el("div", { class: "jdt-expr" }, expr));
271
475
 
272
476
  var result = el("div", { class: "jdt-result" });
273
477
  result.appendChild(el("strong", null, String(total)));
@@ -277,18 +481,20 @@
277
481
  }
278
482
  result.appendChild(el("span", { class: "jdt-rolls" }, " " + rollsText));
279
483
 
280
- // result
281
484
  entry.appendChild(result);
485
+ appendTableContext(entry, tableContext);
282
486
 
283
- // time
284
- //entry.appendChild(el("div", { class: "jdt-details" }, timeStr));
285
- log.appendChild(entry); // newest at bottom
487
+ log.appendChild(entry);
286
488
  log.scrollTop = log.scrollHeight;
287
489
 
288
- pushHistory({ kind: "roll", expr: expr, total: total, rolls: rolls, mod: mod, time: timeStr });
490
+ if (skipHistory) return;
491
+
492
+ var item = { kind: "roll", expr: expr, total: total, rolls: rolls, mod: mod, time: timeStr };
493
+ if (tableContext) item.table_context = tableContext;
494
+ pushHistory(item);
289
495
  }
290
496
 
291
- function addBestOf2Entry(expr, chosenTotal, leftRolls, rightRolls, leftIsWinner, leftLabel, rightLabel, mod, timeStr, mode) {
497
+ function addBestOf2Entry(expr, chosenTotal, leftRolls, rightRolls, leftIsWinner, leftLabel, rightLabel, mod, timeStr, mode, tableContext, skipHistory) {
292
498
  var entry = el("div", { class: "jdt-entry", title: expr });
293
499
  entry.appendChild(el("div", { class: "jdt-expr" }, expr));
294
500
 
@@ -318,12 +524,15 @@
318
524
  }
319
525
 
320
526
  entry.appendChild(result);
527
+ appendTableContext(entry, tableContext);
321
528
  entry.appendChild(el("div", { class: "jdt-details" }, timeStr));
322
529
 
323
530
  log.appendChild(entry);
324
531
  log.scrollTop = log.scrollHeight;
325
532
 
326
- pushHistory({
533
+ if (skipHistory) return;
534
+
535
+ var item = {
327
536
  kind: "bestof2",
328
537
  expr: expr,
329
538
  chosen_total: chosenTotal,
@@ -335,24 +544,44 @@
335
544
  mode: mode,
336
545
  mod: mod,
337
546
  time: timeStr,
338
- });
547
+ };
548
+ if (tableContext) item.table_context = tableContext;
549
+ pushHistory(item);
339
550
  }
340
551
 
341
552
  function showHelp() {
342
553
  addSystemEntry(
343
- "Usage: 1d6, d4, 2d8+1",
344
- "Click linked dice like 1d20+5 or bracket modifiers like [+1] (rolls d20+1). Commands: /help, /clear",
554
+ "Usage: 1d6, d4, 2d8+1, 2-in-6",
555
+ "Click linked dice like 1d20+5, chance like 2-in-6 (roll d6, 1-2 succeed), or bracket modifiers like [+1] (rolls d20+1). Clicking a table die (e.g. 1d12) shows the matching row (first column: single number or range like 3-12). Commands: /help, /clear",
345
556
  nowTime()
346
557
  );
347
558
  }
348
559
 
349
- function doRoll(raw) {
560
+ function doRoll(raw, meta) {
561
+ meta = meta || {};
562
+ var anchor = meta.anchor || null;
350
563
  var p = parseExpr(raw);
351
564
  if (p.kind === "empty") return;
352
565
  if (p.kind === "help") return showHelp();
353
566
  if (p.kind === "clear") return clearStorageAndUi();
354
- if (p.kind !== "roll" && p.kind !== "bestof2") {
355
- addSystemEntry("Unrecognized roll: " + p.raw, "Try: 1d6, d4, 2d8+1 or /help", nowTime());
567
+ if (p.kind !== "roll" && p.kind !== "bestof2" && p.kind !== "chance") {
568
+ addSystemEntry("Unrecognized roll: " + p.raw, "Try: 1d6, d4, 2-in-6, 2d8+1 or /help", nowTime());
569
+ return;
570
+ }
571
+
572
+ if (p.kind === "chance") {
573
+ var chanceRoll = rollDice(1, p.sides);
574
+ var roll = chanceRoll.rolls[0];
575
+ var success = roll <= p.success_on;
576
+ addChanceEntry(
577
+ p.normalized,
578
+ roll,
579
+ p.success_on,
580
+ p.sides,
581
+ success,
582
+ nowTime(),
583
+ getLookupTableContext(anchor, roll)
584
+ );
356
585
  return;
357
586
  }
358
587
 
@@ -377,14 +606,15 @@
377
606
  rightLabel,
378
607
  p.mod,
379
608
  nowTime(),
380
- mode
609
+ mode,
610
+ getLookupTableContext(anchor, chosenTotal)
381
611
  );
382
612
  return;
383
613
  }
384
614
 
385
615
  var r = rollDice(p.count, p.sides);
386
616
  var total = r.total + p.mod;
387
- addRollEntry(p.normalized, total, r.rolls, p.mod, nowTime());
617
+ addRollEntry(p.normalized, total, r.rolls, p.mod, nowTime(), getLookupTableContext(anchor, total));
388
618
  }
389
619
 
390
620
  toggle.addEventListener("click", function () {
@@ -446,7 +676,7 @@
446
676
  if (!expr) return;
447
677
  e.preventDefault();
448
678
  setExpanded(true);
449
- doRoll(expr);
679
+ doRoll(expr, { anchor: a });
450
680
  });
451
681
 
452
682
  // Public API
@@ -14,6 +14,14 @@ module Jekyll
14
14
  (?![A-Za-z0-9_])
15
15
  /x
16
16
 
17
+ CHANCE_RE = /
18
+ (?<![A-Za-z0-9_])
19
+ \d{1,3}
20
+ \s*-\s*in\s*-\s*
21
+ \d{1,4}
22
+ (?![A-Za-z0-9_])
23
+ /ix
24
+
17
25
  THAC0_WORD_RE = /THAC0/i
18
26
 
19
27
  BRACKET_INNER_RE = /
@@ -105,6 +113,17 @@ module Jekyll
105
113
  }
106
114
  end
107
115
 
116
+ text.to_enum(:scan, CHANCE_RE).each do
117
+ m = Regexp.last_match
118
+ matches << {
119
+ start: m.begin(0),
120
+ end: m.end(0),
121
+ expr: m[0].gsub(/\s+/, ""),
122
+ label_start: m.begin(0),
123
+ label_end: m.end(0),
124
+ }
125
+ end
126
+
108
127
  text.to_enum(:scan, THAC0_INLINE_RE).each do
109
128
  matches << bracket_mod_hit(text, Regexp.last_match)
110
129
  end
@@ -1,6 +1,6 @@
1
1
  module Jekyll
2
2
  module DiceTray
3
- VERSION = "0.1.1"
3
+ VERSION = "0.3.0"
4
4
  end
5
5
  end
6
6
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-dice-tray
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - directsun