create-rue 0.1.2 → 0.1.4

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.
package/bundle.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- /*! create-rue v0.1.2 | MIT */
2
+ /*! create-rue v0.1.4 | MIT */
3
3
  import { createRequire } from "node:module";
4
4
  import * as fs from "node:fs";
5
5
  import "node:fs";
@@ -2037,7 +2037,7 @@ function inferPackageManager() {
2037
2037
  //#endregion
2038
2038
  //#region package.json
2039
2039
  var name = "create-rue";
2040
- var version = "0.1.2";
2040
+ var version = "0.1.4";
2041
2041
  //#endregion
2042
2042
  //#region index.ts
2043
2043
  const language = await getLanguage(fileURLToPath(new URL("./locales", import.meta.url)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-rue",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "🛠️ The will to start a Vite-powered Rue project",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/hunzhiwange/create-rue#readme",
@@ -1,4 +1,4 @@
1
- import { computed, type FC, ref, watchEffect } from '@rue-js/rue'
1
+ import { computed, type FC, ref, useState, watchEffect } from '@rue-js/rue'
2
2
  import { RouterLink } from '@rue-js/router'
3
3
 
4
4
  const todoStorageKey = 'rue.base.todos'
@@ -211,18 +211,6 @@ const getTodoCreatedAtTime = (item: TodoItem) => {
211
211
  return parsed.getTime()
212
212
  }
213
213
 
214
- const getClosestTodoActionElement = (target: EventTarget | null) => {
215
- if (target instanceof Element) {
216
- return target.closest<HTMLElement>('[data-todo-action]')
217
- }
218
-
219
- if (target instanceof Node) {
220
- return target.parentElement?.closest<HTMLElement>('[data-todo-action]') ?? null
221
- }
222
-
223
- return null
224
- }
225
-
226
214
  const getTodoStorage = () => {
227
215
  if (typeof globalThis === 'undefined' || !('localStorage' in globalThis)) {
228
216
  return null
@@ -364,47 +352,41 @@ const statusMeta: Record<
364
352
  const TodoApp: FC = () => {
365
353
  const persistedState = loadPersistedTodoState()
366
354
  const initialStateTodos = persistedState?.todos ?? initialTodos
367
- const todos = ref<TodoItem[]>(initialStateTodos)
368
- const draft = ref('')
369
- const search = ref(persistedState?.search ?? '')
370
- const activeFilter = ref<TodoFilter>(persistedState?.activeFilter ?? 'all')
371
- const editingId = ref<number | null>(null)
372
- const editingTitle = ref('')
355
+ const initialActiveFilter: TodoFilter = persistedState?.activeFilter ?? 'all'
356
+ const [todos, setTodos] = useState<TodoItem[]>(initialStateTodos)
357
+ const [draft, setDraft] = useState('')
358
+ const [search, setSearch] = useState(persistedState?.search ?? '')
359
+ const activeFilter = ref<TodoFilter>(initialActiveFilter)
360
+ const setActiveFilter = (nextFilter: TodoFilter) => {
361
+ activeFilter.value = nextFilter
362
+ }
363
+ const [editingId, setEditingId] = useState<number | null>(null)
364
+ const [editingTitle, setEditingTitle] = useState('')
373
365
  const nextId = ref(getNextTodoId(initialStateTodos))
374
366
  const nextCreatedOrder = ref(getNextCreatedOrder(initialStateTodos))
375
- const todosVersion = ref(0)
376
-
377
- const syncTodos = (nextTodos: TodoItem[]) => {
378
- todos.value = nextTodos
379
- todosVersion.value += 1
380
- }
381
367
 
382
368
  watchEffect(() => {
383
- void todosVersion.value
384
369
  persistTodoState({
385
- todos: todos.value,
370
+ todos,
386
371
  search: search.value,
387
372
  activeFilter: activeFilter.value,
388
373
  })
389
374
  })
390
375
 
391
376
  const counts = computed(() => {
392
- void todosVersion.value
393
-
394
377
  return {
395
- total: todos.value.filter((item) => !item.archived).length,
396
- todo: todos.value.filter((item) => !item.archived && item.status === 'todo').length,
397
- doing: todos.value.filter((item) => !item.archived && item.status === 'doing').length,
398
- done: todos.value.filter((item) => !item.archived && item.status === 'done').length,
399
- archived: todos.value.filter((item) => item.archived).length,
378
+ total: todos.filter((item) => !item.archived).length,
379
+ todo: todos.filter((item) => !item.archived && item.status === 'todo').length,
380
+ doing: todos.filter((item) => !item.archived && item.status === 'doing').length,
381
+ done: todos.filter((item) => !item.archived && item.status === 'done').length,
382
+ archived: todos.filter((item) => item.archived).length,
400
383
  }
401
384
  })
402
385
 
403
386
  const visibleTodos = computed(() => {
404
- void todosVersion.value
405
387
  const keyword = search.value.trim().toLowerCase()
406
388
 
407
- return todos.value
389
+ return todos
408
390
  .filter((item) => {
409
391
  const matchesKeyword = !keyword || item.title.toLowerCase().includes(keyword)
410
392
  if (!matchesKeyword) {
@@ -435,163 +417,65 @@ const TodoApp: FC = () => {
435
417
  })
436
418
  })
437
419
 
438
- const visibleTodoCards = computed(() => {
439
- const currentEditingId = editingId.value
440
-
441
- return visibleTodos.get().map((item) => ({
442
- item,
443
- meta: statusMeta[item.status],
444
- isEditing: currentEditingId === item.id,
445
- }))
446
- })
447
-
448
420
  const addTodo = () => {
449
421
  const title = draft.value.trim()
450
422
  if (!title) {
451
423
  return
452
424
  }
453
425
 
454
- syncTodos([
455
- {
456
- id: nextId.value++,
457
- title,
458
- status: 'todo',
459
- archived: false,
460
- createdAt: new Date().toISOString(),
461
- createdOrder: nextCreatedOrder.value++,
462
- },
463
- ...todos.value,
464
- ])
465
- draft.value = ''
426
+ const nextTodo: TodoItem = {
427
+ id: nextId.value++,
428
+ title,
429
+ status: 'todo',
430
+ archived: false,
431
+ createdAt: new Date().toISOString(),
432
+ createdOrder: nextCreatedOrder.value++,
433
+ }
434
+
435
+ setTodos((current) => [nextTodo, ...current])
436
+ setDraft('')
466
437
  }
467
438
 
468
439
  const removeTodo = (id: number) => {
469
- syncTodos(todos.value.filter((item) => item.id !== id))
440
+ setTodos((current) => current.filter((item) => item.id !== id))
470
441
  if (editingId.value === id) {
471
- cancelEditing()
442
+ setEditingId(null)
443
+ setEditingTitle('')
472
444
  }
473
445
  }
474
446
 
475
447
  const updateStatus = (id: number, status: TodoStatus) => {
476
- syncTodos(
477
- todos.value.map((item) => (item.id === id ? { ...item, status, archived: false } : item)),
448
+ setTodos((current) =>
449
+ current.map((item) => (item.id === id ? { ...item, status, archived: false } : item)),
478
450
  )
479
451
  }
480
452
 
481
453
  const toggleArchived = (id: number) => {
482
- syncTodos(
483
- todos.value.map((item) => (item.id === id ? { ...item, archived: !item.archived } : item)),
454
+ setTodos((current) =>
455
+ current.map((item) => (item.id === id ? { ...item, archived: !item.archived } : item)),
484
456
  )
485
457
  }
486
458
 
487
459
  const startEditing = (item: TodoItem) => {
488
- editingId.value = item.id
489
- editingTitle.value = item.title
460
+ setEditingId(item.id)
461
+ setEditingTitle(item.title)
490
462
  }
491
463
 
492
464
  const cancelEditing = () => {
493
- editingId.value = null
494
- editingTitle.value = ''
465
+ setEditingId(null)
466
+ setEditingTitle('')
495
467
  }
496
468
 
497
- const saveEditing = (id: number) => {
498
- const title = editingTitle.value.trim()
469
+ const saveEditing = (id: number, titleOverride?: string) => {
470
+ const title = (titleOverride ?? editingTitle.value).trim()
499
471
  if (!title) {
500
472
  return
501
473
  }
502
474
 
503
- syncTodos(todos.value.map((item) => (item.id === id ? { ...item, title } : item)))
475
+ setTodos((current) => current.map((item) => (item.id === id ? { ...item, title } : item)))
504
476
  cancelEditing()
505
477
  }
506
478
 
507
- const handleTodoListClick = (event: MouseEvent) => {
508
- const actionElement = getClosestTodoActionElement(event.target)
509
-
510
- if (!actionElement) {
511
- return
512
- }
513
-
514
- const action = actionElement.dataset.todoAction
515
- const id = Number(actionElement.dataset.todoId)
516
-
517
- if (!Number.isFinite(id)) {
518
- return
519
- }
520
-
521
- if (action === 'status') {
522
- const status = actionElement.dataset.todoStatus
523
- if (isTodoStatus(status)) {
524
- updateStatus(id, status)
525
- }
526
- return
527
- }
528
-
529
- if (action === 'start-editing') {
530
- const item = todos.value.find((candidate) => candidate.id === id)
531
- if (item) {
532
- startEditing(item)
533
- }
534
- return
535
- }
536
-
537
- if (action === 'save-editing') {
538
- saveEditing(id)
539
- return
540
- }
541
-
542
- if (action === 'cancel-editing') {
543
- cancelEditing()
544
- return
545
- }
546
-
547
- if (action === 'toggle-archived') {
548
- toggleArchived(id)
549
- return
550
- }
551
-
552
- if (action === 'remove') {
553
- removeTodo(id)
554
- }
555
- }
556
-
557
- const handleTodoListInput = (event: Event) => {
558
- if (!(event.target instanceof HTMLInputElement)) {
559
- return
560
- }
561
-
562
- const target = event.target
563
- if (!target.matches('[data-todo-edit-input]')) {
564
- return
565
- }
566
-
567
- editingTitle.value = target.value
568
- }
569
-
570
- const handleTodoListKeydown = (event: KeyboardEvent) => {
571
- if (!(event.target instanceof HTMLInputElement)) {
572
- return
573
- }
574
-
575
- const target = event.target
576
- if (!target.matches('[data-todo-edit-input]')) {
577
- return
578
- }
579
-
580
- const id = Number(target.dataset.todoId)
581
- if (!Number.isFinite(id)) {
582
- return
583
- }
584
-
585
- if (event.key === 'Enter' && !event.isComposing) {
586
- event.preventDefault()
587
- saveEditing(id)
588
- }
589
-
590
- if (event.key === 'Escape') {
591
- cancelEditing()
592
- }
593
- }
594
-
595
479
  return (
596
480
  <div className="space-y-6 pb-4">
597
481
  <section className="hero border border-base-300 bg-base-100 shadow-xl">
@@ -637,7 +521,7 @@ const TodoApp: FC = () => {
637
521
  value={draft.value}
638
522
  placeholder="例如:把日报页接入真实 API"
639
523
  onInput={(event: any) => {
640
- draft.value = (event.target as HTMLInputElement).value
524
+ setDraft((event.target as HTMLInputElement).value)
641
525
  }}
642
526
  onKeydown={(event: KeyboardEvent) => {
643
527
  if (event.key === 'Enter' && !event.isComposing) {
@@ -661,7 +545,7 @@ const TodoApp: FC = () => {
661
545
  value={search.value}
662
546
  placeholder="按标题筛选任务"
663
547
  onInput={(event: any) => {
664
- search.value = (event.target as HTMLInputElement).value
548
+ setSearch((event.target as HTMLInputElement).value)
665
549
  }}
666
550
  />
667
551
  </label>
@@ -677,7 +561,7 @@ const TodoApp: FC = () => {
677
561
  : 'btn-ghost border border-base-300'
678
562
  }`}
679
563
  onClick={() => {
680
- activeFilter.value = filter.key
564
+ setActiveFilter(filter.key)
681
565
  }}
682
566
  >
683
567
  {filter.label}
@@ -702,130 +586,145 @@ const TodoApp: FC = () => {
702
586
  </div>
703
587
  </section>
704
588
 
705
- <section
706
- className="grid gap-4"
707
- onClick={handleTodoListClick}
708
- onInput={handleTodoListInput}
709
- onKeydown={handleTodoListKeydown}
710
- >
711
- {visibleTodoCards.get().map((card) => (
712
- <article
713
- key={card.item.id}
714
- className={`card border bg-base-100 shadow-sm transition-all ${card.meta.cardClass} ${
715
- card.item.archived ? 'opacity-75' : 'hover:-translate-y-0.5 hover:shadow-md'
716
- }`}
717
- >
718
- <div className="card-body gap-4">
719
- <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
720
- <div className="flex-1 space-y-3">
721
- <div className="flex flex-wrap items-center gap-2">
722
- <span
723
- className={`inline-block h-2.5 w-2.5 rounded-full ${card.meta.dotClass}`}
724
- ></span>
725
- <span className={card.meta.badgeClass}>{card.meta.label}</span>
726
- {card.item.archived && (
727
- <span className="badge badge-secondary badge-outline">已归档</span>
728
- )}
729
- <span className="text-xs text-base-content/50">
730
- 创建于 {formatTodoCreatedAt(card.item.createdAt)}
731
- </span>
732
- </div>
589
+ <section className="grid gap-4">
590
+ {visibleTodos.get().map((item) => {
591
+ const isEditing = editingId.value === item.id
592
+ const editingValue = isEditing ? editingTitle.value : item.title
593
+ const meta = statusMeta[item.status]
594
+
595
+ const commitEditing = (latestValue: string) => {
596
+ setEditingTitle(latestValue)
597
+ saveEditing(item.id, latestValue)
598
+ }
599
+
600
+ return (
601
+ <article
602
+ key={`${item.id}-${item.title}-${item.status}-${item.archived ? 'archived' : 'active'}-${isEditing ? 'editing' : 'view'}`}
603
+ className={`card border bg-base-100 shadow-sm transition-all ${meta.cardClass} ${
604
+ item.archived ? 'opacity-75' : 'hover:-translate-y-0.5 hover:shadow-md'
605
+ }`}
606
+ >
607
+ <div className="card-body gap-4">
608
+ <div className="flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between">
609
+ <div className="flex-1 space-y-3">
610
+ <div className="flex flex-wrap items-center gap-2">
611
+ <span
612
+ className={`inline-block h-2.5 w-2.5 rounded-full ${meta.dotClass}`}
613
+ ></span>
614
+ <span className={meta.badgeClass}>{meta.label}</span>
615
+ {item.archived && (
616
+ <span className="badge badge-secondary badge-outline">已归档</span>
617
+ )}
618
+ <span className="text-xs text-base-content/50">
619
+ 创建于 {formatTodoCreatedAt(item.createdAt)}
620
+ </span>
621
+ </div>
733
622
 
734
- {!card.isEditing && (
735
623
  <h2
736
624
  className={`text-xl font-semibold ${
737
- card.item.status === 'done'
738
- ? 'text-base-content/50 line-through'
739
- : 'text-base-content'
625
+ isEditing
626
+ ? 'hidden'
627
+ : item.status === 'done'
628
+ ? 'text-base-content/50 line-through'
629
+ : 'text-base-content'
740
630
  }`}
741
631
  >
742
- {card.item.title}
632
+ {item.title}
743
633
  </h2>
744
- )}
745
634
 
746
- {card.isEditing && (
747
- <div className="flex flex-col gap-3 sm:flex-row">
635
+ <div
636
+ data-todo-edit-row="true"
637
+ className={`flex flex-col gap-3 sm:flex-row ${isEditing ? '' : 'hidden'}`}
638
+ >
748
639
  <input
749
640
  className="input input-bordered w-full"
750
- data-todo-edit-input="true"
751
- data-todo-id={String(card.item.id)}
752
- value={editingTitle.value}
641
+ value={editingValue}
642
+ onInput={(event: any) => {
643
+ setEditingTitle((event.target as HTMLInputElement).value)
644
+ }}
645
+ onKeydown={(event: KeyboardEvent) => {
646
+ if (event.key === 'Enter') {
647
+ commitEditing((event.target as HTMLInputElement).value)
648
+ }
649
+ if (event.key === 'Escape') {
650
+ cancelEditing()
651
+ }
652
+ }}
753
653
  />
754
654
  <div className="flex gap-2">
755
655
  <button
756
656
  className="btn btn-primary btn-sm"
757
- data-todo-action="save-editing"
758
- data-todo-id={String(card.item.id)}
657
+ onClick={(event: any) => {
658
+ const editRow = (event.currentTarget as HTMLElement).closest(
659
+ '[data-todo-edit-row="true"]',
660
+ ) as HTMLElement | null
661
+ const input = editRow?.querySelector('input') as HTMLInputElement | null
662
+ commitEditing(input?.value ?? editingValue)
663
+ }}
759
664
  type="button"
760
665
  >
761
666
  保存
762
667
  </button>
763
668
  <button
764
669
  className="btn btn-ghost btn-sm"
765
- data-todo-action="cancel-editing"
766
- data-todo-id={String(card.item.id)}
670
+ onClick={cancelEditing}
767
671
  type="button"
768
672
  >
769
673
  取消
770
674
  </button>
771
675
  </div>
772
676
  </div>
773
- )}
774
677
 
775
- <div className="flex flex-wrap gap-2">
776
- {statusOptions.map((option) => (
678
+ <div className="flex flex-wrap gap-2">
679
+ {statusOptions.map((option) => (
680
+ <button
681
+ key={option.key}
682
+ className={`btn btn-xs ${
683
+ item.status === option.key
684
+ ? 'btn-neutral'
685
+ : 'btn-ghost border border-base-300'
686
+ }`}
687
+ onClick={() => updateStatus(item.id, option.key)}
688
+ type="button"
689
+ >
690
+ {option.actionLabel}
691
+ </button>
692
+ ))}
693
+ </div>
694
+ </div>
695
+
696
+ <div className="flex flex-wrap gap-2 lg:justify-end">
697
+ {!isEditing && (
777
698
  <button
778
- key={option.key}
779
- className={`btn btn-xs ${
780
- card.item.status === option.key
781
- ? 'btn-neutral'
782
- : 'btn-ghost border border-base-300'
783
- }`}
784
- data-todo-action="status"
785
- data-todo-id={String(card.item.id)}
786
- data-todo-status={option.key}
699
+ className="btn btn-sm btn-outline"
700
+ onClick={() => startEditing(item)}
787
701
  type="button"
788
702
  >
789
- {option.actionLabel}
703
+ 改名
790
704
  </button>
791
- ))}
792
- </div>
793
- </div>
794
-
795
- <div className="flex flex-wrap gap-2 lg:justify-end">
796
- {!card.isEditing && (
705
+ )}
706
+ <button
707
+ className="btn btn-sm btn-outline btn-secondary"
708
+ onClick={() => toggleArchived(item.id)}
709
+ type="button"
710
+ >
711
+ {item.archived ? '恢复' : '归档'}
712
+ </button>
797
713
  <button
798
- className="btn btn-sm btn-outline"
799
- data-todo-action="start-editing"
800
- data-todo-id={String(card.item.id)}
714
+ className="btn btn-sm btn-outline btn-error"
715
+ onClick={() => removeTodo(item.id)}
801
716
  type="button"
802
717
  >
803
- 改名
718
+ 删除
804
719
  </button>
805
- )}
806
- <button
807
- className="btn btn-sm btn-outline btn-secondary"
808
- data-todo-action="toggle-archived"
809
- data-todo-id={String(card.item.id)}
810
- type="button"
811
- >
812
- {card.item.archived ? '恢复' : '归档'}
813
- </button>
814
- <button
815
- className="btn btn-sm btn-outline btn-error"
816
- data-todo-action="remove"
817
- data-todo-id={String(card.item.id)}
818
- type="button"
819
- >
820
- 删除
821
- </button>
720
+ </div>
822
721
  </div>
823
722
  </div>
824
- </div>
825
- </article>
826
- ))}
723
+ </article>
724
+ )
725
+ })}
827
726
 
828
- {!visibleTodoCards.get().length && (
727
+ {!visibleTodos.get().length && (
829
728
  <div className="card border border-dashed border-base-300 bg-base-100 shadow-sm">
830
729
  <div className="card-body items-center py-14 text-center">
831
730
  <h2 className="text-xl font-semibold">当前筛选下没有任务</h2>