@skillcdn/console 0.3.1 → 0.4.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.
Files changed (179) hide show
  1. package/README.md +4 -4
  2. package/dist/api.d.ts +4 -4
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +4 -4
  5. package/dist/api.js.map +1 -1
  6. package/dist/cli/cli.d.ts +4 -0
  7. package/dist/cli/cli.d.ts.map +1 -1
  8. package/dist/cli/cli.js +201 -25
  9. package/dist/cli/cli.js.map +1 -1
  10. package/dist/cli/format.d.ts.map +1 -1
  11. package/dist/cli/format.js +15 -11
  12. package/dist/cli/format.js.map +1 -1
  13. package/dist/cli/help.d.ts.map +1 -1
  14. package/dist/cli/help.js +59 -26
  15. package/dist/cli/help.js.map +1 -1
  16. package/dist/cli/main.js +1 -0
  17. package/dist/cli/main.js.map +1 -1
  18. package/dist/cli/pack.d.ts +7 -2
  19. package/dist/cli/pack.d.ts.map +1 -1
  20. package/dist/cli/pack.js +16 -2
  21. package/dist/cli/pack.js.map +1 -1
  22. package/dist/client.d.ts +20 -10
  23. package/dist/client.d.ts.map +1 -1
  24. package/dist/client.js +17 -3
  25. package/dist/client.js.map +1 -1
  26. package/dist/compat.d.ts +28 -6
  27. package/dist/compat.d.ts.map +1 -1
  28. package/dist/compat.js +55 -14
  29. package/dist/compat.js.map +1 -1
  30. package/dist/components/board.d.ts +12 -1
  31. package/dist/components/board.d.ts.map +1 -1
  32. package/dist/components/board.js +57 -7
  33. package/dist/components/board.js.map +1 -1
  34. package/dist/components/calendar.d.ts +17 -7
  35. package/dist/components/calendar.d.ts.map +1 -1
  36. package/dist/components/calendar.js +89 -38
  37. package/dist/components/calendar.js.map +1 -1
  38. package/dist/components/decision-form.d.ts +12 -4
  39. package/dist/components/decision-form.d.ts.map +1 -1
  40. package/dist/components/decision-form.js +24 -6
  41. package/dist/components/decision-form.js.map +1 -1
  42. package/dist/components/decision-list.d.ts +4 -0
  43. package/dist/components/decision-list.d.ts.map +1 -1
  44. package/dist/components/decision-list.js +9 -6
  45. package/dist/components/decision-list.js.map +1 -1
  46. package/dist/components/documents.d.ts.map +1 -1
  47. package/dist/components/documents.js +3 -3
  48. package/dist/components/documents.js.map +1 -1
  49. package/dist/components/event-feed.d.ts.map +1 -1
  50. package/dist/components/event-feed.js +63 -8
  51. package/dist/components/event-feed.js.map +1 -1
  52. package/dist/components/my-decisions.d.ts +20 -0
  53. package/dist/components/my-decisions.d.ts.map +1 -0
  54. package/dist/components/my-decisions.js +36 -0
  55. package/dist/components/my-decisions.js.map +1 -0
  56. package/dist/components/people.d.ts.map +1 -1
  57. package/dist/components/people.js +1 -1
  58. package/dist/components/people.js.map +1 -1
  59. package/dist/components/picker.d.ts +39 -0
  60. package/dist/components/picker.d.ts.map +1 -0
  61. package/dist/components/picker.js +155 -0
  62. package/dist/components/picker.js.map +1 -0
  63. package/dist/components/projects.d.ts.map +1 -1
  64. package/dist/components/projects.js +3 -3
  65. package/dist/components/projects.js.map +1 -1
  66. package/dist/components/roadmap.js +1 -1
  67. package/dist/components/roadmap.js.map +1 -1
  68. package/dist/components/runs.d.ts.map +1 -1
  69. package/dist/components/runs.js +1 -1
  70. package/dist/components/runs.js.map +1 -1
  71. package/dist/components/shell.d.ts.map +1 -1
  72. package/dist/components/shell.js +36 -1
  73. package/dist/components/shell.js.map +1 -1
  74. package/dist/components/task-form.d.ts +18 -4
  75. package/dist/components/task-form.d.ts.map +1 -1
  76. package/dist/components/task-form.js +56 -13
  77. package/dist/components/task-form.js.map +1 -1
  78. package/dist/components/task-list.js +2 -2
  79. package/dist/components/task-list.js.map +1 -1
  80. package/dist/components/task-view.d.ts +7 -1
  81. package/dist/components/task-view.d.ts.map +1 -1
  82. package/dist/components/task-view.js +7 -6
  83. package/dist/components/task-view.js.map +1 -1
  84. package/dist/components/ui.d.ts +17 -0
  85. package/dist/components/ui.d.ts.map +1 -1
  86. package/dist/components/ui.js +26 -11
  87. package/dist/components/ui.js.map +1 -1
  88. package/dist/components/work.d.ts.map +1 -1
  89. package/dist/components/work.js +5 -3
  90. package/dist/components/work.js.map +1 -1
  91. package/dist/console.css +1794 -433
  92. package/dist/console.d.ts.map +1 -1
  93. package/dist/console.js +93 -10
  94. package/dist/console.js.map +1 -1
  95. package/dist/data.d.ts +21 -1
  96. package/dist/data.d.ts.map +1 -1
  97. package/dist/data.js +24 -1
  98. package/dist/data.js.map +1 -1
  99. package/dist/i18n/en.d.ts +45 -3
  100. package/dist/i18n/en.d.ts.map +1 -1
  101. package/dist/i18n/en.js +52 -10
  102. package/dist/i18n/en.js.map +1 -1
  103. package/dist/i18n/ko.d.ts.map +1 -1
  104. package/dist/i18n/ko.js +54 -12
  105. package/dist/i18n/ko.js.map +1 -1
  106. package/dist/index.d.ts +5 -3
  107. package/dist/index.d.ts.map +1 -1
  108. package/dist/index.js +5 -3
  109. package/dist/index.js.map +1 -1
  110. package/dist/limits.d.ts +2 -0
  111. package/dist/limits.d.ts.map +1 -1
  112. package/dist/limits.js +2 -0
  113. package/dist/limits.js.map +1 -1
  114. package/dist/router.d.ts +9 -1
  115. package/dist/router.d.ts.map +1 -1
  116. package/dist/router.js +12 -0
  117. package/dist/router.js.map +1 -1
  118. package/dist/routes.d.ts +3 -1
  119. package/dist/routes.d.ts.map +1 -1
  120. package/dist/routes.js +3 -1
  121. package/dist/routes.js.map +1 -1
  122. package/dist/rules.js +1 -1
  123. package/dist/rules.js.map +1 -1
  124. package/dist/schemas.d.ts +252 -12
  125. package/dist/schemas.d.ts.map +1 -1
  126. package/dist/schemas.js +27 -10
  127. package/dist/schemas.js.map +1 -1
  128. package/dist/vocabulary.d.ts +2 -0
  129. package/dist/vocabulary.d.ts.map +1 -1
  130. package/dist/vocabulary.js +2 -0
  131. package/dist/vocabulary.js.map +1 -1
  132. package/docs/specs/cli.md +14 -13
  133. package/docs/specs/rest.md +24 -19
  134. package/package.json +2 -2
  135. package/skills/building-a-console/SKILL.md +4 -4
  136. package/skills/building-a-console/references/the-default-console.md +4 -4
  137. package/skills/building-a-console/references/the-look-from-a-website.md +5 -5
  138. package/skills/deploying-a-console/SKILL.md +2 -2
  139. package/skills/deploying-a-console/references/resources.md +1 -1
  140. package/skills/working-the-board/SKILL.md +8 -6
  141. package/skills/working-the-board/references/terms.md +3 -3
  142. package/src/api.ts +7 -0
  143. package/src/cli/cli.ts +254 -25
  144. package/src/cli/format.ts +22 -11
  145. package/src/cli/help.ts +63 -26
  146. package/src/cli/main.ts +1 -0
  147. package/src/cli/pack.ts +17 -1
  148. package/src/client.ts +44 -11
  149. package/src/compat.ts +63 -14
  150. package/src/components/board.tsx +124 -25
  151. package/src/components/calendar.tsx +258 -117
  152. package/src/components/decision-form.tsx +71 -23
  153. package/src/components/decision-list.tsx +30 -3
  154. package/src/components/documents.tsx +27 -11
  155. package/src/components/event-feed.tsx +116 -33
  156. package/src/components/my-decisions.tsx +138 -0
  157. package/src/components/people.tsx +3 -1
  158. package/src/components/picker.tsx +297 -0
  159. package/src/components/projects.tsx +7 -5
  160. package/src/components/roadmap.tsx +1 -1
  161. package/src/components/runs.tsx +5 -1
  162. package/src/components/shell.tsx +36 -1
  163. package/src/components/task-form.tsx +149 -63
  164. package/src/components/task-list.tsx +3 -3
  165. package/src/components/task-view.tsx +71 -56
  166. package/src/components/ui.tsx +89 -11
  167. package/src/components/work.tsx +6 -4
  168. package/src/console.tsx +157 -29
  169. package/src/data.ts +61 -1
  170. package/src/i18n/en.ts +54 -10
  171. package/src/i18n/ko.ts +56 -12
  172. package/src/index.ts +19 -1
  173. package/src/limits.ts +2 -0
  174. package/src/router.ts +18 -1
  175. package/src/routes.ts +3 -1
  176. package/src/rules.ts +1 -1
  177. package/src/schemas.ts +29 -9
  178. package/src/styles/console.css +1794 -433
  179. package/src/vocabulary.ts +3 -0
@@ -20,7 +20,6 @@ import { useWords } from "../i18n/index.js";
20
20
  import type { BoardFilter } from "../router.js";
21
21
  import { isPlainClick } from "./shell.js";
22
22
  import {
23
- Avatar,
24
23
  Badge,
25
24
  Button,
26
25
  CategoryBadge,
@@ -28,6 +27,7 @@ import {
28
27
  DueBadge,
29
28
  PriorityBadge,
30
29
  personName,
30
+ Workers,
31
31
  } from "./ui.js";
32
32
 
33
33
  // The board: one column per state, the tasks in it, the sooner due first and the newest after.
@@ -59,8 +59,17 @@ export interface BoardProps {
59
59
  readonly onEdit?: ((task: RestTask) => void) | undefined;
60
60
  /** Called when a task is put away from its card's menu (ADR-0029, ADR-0030). Left out, the menu offers nothing of it. */
61
61
  readonly onArchive?: ((task: RestTask) => void) | undefined;
62
+ /**
63
+ * The columns folded to a strip until a person opens them, with their count and the way to open
64
+ * them; a folded column still takes a card dragged onto it. What is finished, by default, so that
65
+ * the work at hand has the room; `[]` folds none.
66
+ */
67
+ readonly folded?: readonly TaskState[] | undefined;
62
68
  }
63
69
 
70
+ /** The columns a board folds unless told otherwise: the work that is over. */
71
+ export const FOLDED_STATES: readonly TaskState[] = ["done", "dropped"];
72
+
64
73
  /** How far a mouse travels on a card before the press is a drag and not a click. */
65
74
  const DRAG_SLACK = 4;
66
75
  /**
@@ -160,12 +169,12 @@ export function TaskCard(props: TaskCardProps) {
160
169
  <DueBadge dueOn={task.dueOn} state={task.state} today={props.today} />
161
170
  )}
162
171
  {task.openDecisions > 0 && (
163
- <Badge tone="warning" title={words.board.decisionsTitle}>
172
+ <Badge tone="warning" kind="count" title={words.board.decisionsTitle}>
164
173
  {words.board.decisions(task.openDecisions)}
165
174
  </Badge>
166
175
  )}
167
176
  <span className="sc-card-spacer" />
168
- {task.assignee !== null && <Avatar person={task.assignee} size="sm" />}
177
+ <Workers workers={task.workers} size="sm" />
169
178
  </div>
170
179
  </article>
171
180
  );
@@ -364,6 +373,19 @@ export function Board(props: BoardProps) {
364
373
  const { onMove } = props;
365
374
  const board = useRef<HTMLDivElement>(null);
366
375
  const [drag, setDrag] = useState<Drag | null>(null);
376
+ // The foldable columns a person has opened on this board; the others of them stay folded.
377
+ const [opened, setOpened] = useState<ReadonlySet<TaskState>>(() => new Set());
378
+ const foldable = props.folded ?? FOLDED_STATES;
379
+ const toggle = (state: TaskState) =>
380
+ setOpened((before) => {
381
+ const after = new Set(before);
382
+ if (after.has(state)) {
383
+ after.delete(state);
384
+ } else {
385
+ after.add(state);
386
+ }
387
+ return after;
388
+ });
367
389
  // What the pointer's release reads, so that the move is made outside a state update.
368
390
  const current = useRef<Drag | null>(null);
369
391
  current.current = drag;
@@ -511,38 +533,65 @@ export function Board(props: BoardProps) {
511
533
  >
512
534
  {TASK_STATES.map((state) => {
513
535
  const tasks = props.tasks.filter((task) => task.state === state).sort(byDue);
536
+ const name = words.vocabulary.state[state];
537
+ const canFold = foldable.includes(state);
538
+ const folded = canFold && !opened.has(state);
514
539
  return (
515
540
  <section
516
541
  key={state}
517
542
  className={cx(
518
543
  "sc-column",
519
544
  `sc-column-${state}`,
545
+ folded && "sc-column-folded",
520
546
  dragging?.over === state && "sc-column-over",
521
547
  )}
522
- aria-label={words.vocabulary.state[state]}
548
+ aria-label={name}
523
549
  data-column={state}
524
550
  >
525
551
  <header className="sc-column-header">
526
- <h2 className="sc-column-title">{words.vocabulary.state[state]}</h2>
552
+ <h2 className="sc-column-title">{name}</h2>
527
553
  <span className="sc-column-count">{tasks.length}</span>
554
+ {canFold && (
555
+ <button
556
+ type="button"
557
+ className="sc-column-toggle"
558
+ aria-expanded={!folded}
559
+ aria-label={folded ? words.board.unfold(name) : words.board.fold(name)}
560
+ title={folded ? words.board.unfold(name) : words.board.fold(name)}
561
+ onClick={() => toggle(state)}
562
+ >
563
+ <svg viewBox="0 0 16 16" aria-hidden="true">
564
+ <path
565
+ d={folded ? "M6 4l4 4-4 4" : "M10 4l-4 4 4 4"}
566
+ fill="none"
567
+ stroke="currentColor"
568
+ strokeWidth="1.5"
569
+ strokeLinecap="round"
570
+ strokeLinejoin="round"
571
+ />
572
+ </svg>
573
+ </button>
574
+ )}
528
575
  </header>
529
- <div className="sc-column-body">
530
- {tasks.map((task) => (
531
- <TaskCard
532
- key={task.id}
533
- task={task}
534
- href={props.taskHref(task)}
535
- onOpen={props.onOpen}
536
- today={props.today}
537
- onPress={onMove === undefined ? undefined : press}
538
- dragging={dragging?.task.id === task.id}
539
- editHref={props.editHref?.(task)}
540
- onEdit={props.onEdit}
541
- onArchive={props.onArchive}
542
- />
543
- ))}
544
- {tasks.length === 0 && state === "idea" && props.empty}
545
- </div>
576
+ {!folded && (
577
+ <div className="sc-column-body">
578
+ {tasks.map((task) => (
579
+ <TaskCard
580
+ key={task.id}
581
+ task={task}
582
+ href={props.taskHref(task)}
583
+ onOpen={props.onOpen}
584
+ today={props.today}
585
+ onPress={onMove === undefined ? undefined : press}
586
+ dragging={dragging?.task.id === task.id}
587
+ editHref={props.editHref?.(task)}
588
+ onEdit={props.onEdit}
589
+ onArchive={props.onArchive}
590
+ />
591
+ ))}
592
+ {tasks.length === 0 && state === "idea" && props.empty}
593
+ </div>
594
+ )}
546
595
  </section>
547
596
  );
548
597
  })}
@@ -571,23 +620,59 @@ export function Board(props: BoardProps) {
571
620
  );
572
621
  }
573
622
 
574
- /** What the filter keeps of the board: a category or none, a goal or none, a person, one's own. */
623
+ /** What a task is found by: its title, its number, its owner and its workers, its category. */
624
+ function wordsOf(task: RestTask): string {
625
+ return [
626
+ task.title,
627
+ `#${task.number}`,
628
+ personName(task.owner),
629
+ task.owner.login,
630
+ ...task.workers.flatMap((worker) => [personName(worker), worker.login]),
631
+ task.category?.name ?? "",
632
+ ]
633
+ .join("\n")
634
+ .toLowerCase();
635
+ }
636
+
637
+ /**
638
+ * What the filter keeps of the board: a category or none, a goal or none, a person, one's own,
639
+ * and the tasks that contain every word typed, whatever the case.
640
+ */
575
641
  export function filterTasks(
576
642
  tasks: readonly RestTask[],
577
643
  filter: BoardFilter,
578
644
  me?: RestPerson | undefined,
579
645
  ): RestTask[] {
646
+ const wanted =
647
+ filter.words === undefined
648
+ ? []
649
+ : filter.words
650
+ .toLowerCase()
651
+ .split(/\s+/)
652
+ .filter((word) => word.length > 0);
580
653
  return tasks.filter((task) => {
654
+ if (wanted.length > 0) {
655
+ const found = wordsOf(task);
656
+ if (!wanted.every((word) => found.includes(word))) {
657
+ return false;
658
+ }
659
+ }
581
660
  if (filter.category !== undefined && (task.category?.key ?? "") !== filter.category) {
582
661
  return false;
583
662
  }
584
663
  if (filter.goal !== undefined && (task.goalId ?? "") !== filter.goal) {
585
664
  return false;
586
665
  }
587
- if (filter.person !== undefined && task.assignee?.id !== filter.person) {
666
+ if (
667
+ filter.person !== undefined &&
668
+ !task.workers.some((worker) => worker.id === filter.person)
669
+ ) {
588
670
  return false;
589
671
  }
590
- if (filter.mine === true && (me === undefined || task.assignee?.id !== me.id)) {
672
+ if (
673
+ filter.mine === true &&
674
+ (me === undefined || !task.workers.some((worker) => worker.id === me.id))
675
+ ) {
591
676
  return false;
592
677
  }
593
678
  return true;
@@ -627,6 +712,20 @@ export function BoardFilters(props: BoardFiltersProps) {
627
712
  aria-label={words.aria}
628
713
  onSubmit={(event) => event.preventDefault()}
629
714
  >
715
+ <label className="sc-field sc-field-inline sc-filters-words">
716
+ <span className="sc-visually-hidden">{words.words}</span>
717
+ <input
718
+ className="sc-input"
719
+ type="search"
720
+ placeholder={words.words}
721
+ value={filter.words ?? ""}
722
+ onChange={(event) =>
723
+ event.target.value.trim() === ""
724
+ ? change({}, "words")
725
+ : change({ words: event.target.value })
726
+ }
727
+ />
728
+ </label>
630
729
  <label className="sc-field sc-field-inline">
631
730
  <span className="sc-visually-hidden">{words.anyCategory}</span>
632
731
  <select