项目代码预览

Reading List

下面是本文引用的完整示例项目。源码在构建时嵌入页面,目录可以折叠,选择文件后可直接查看对应内容。

reading-list
import { createReadingItem, type ReadingItem } from "./model/reading-item";
import { loadItems, saveItems } from "./store";
import "./styles.css";

const form = document.querySelector<HTMLFormElement>("#reading-form")!;
const list = document.querySelector<HTMLUListElement>("#reading-list")!;
let items = loadItems();

function render(): void {
  list.replaceChildren(
    ...items.map((item) => {
      const row = document.createElement("li");
      const link = document.createElement("a");
      const toggle = document.createElement("button");

      link.href = item.url;
      link.textContent = item.title;
      link.target = "_blank";
      link.rel = "noreferrer";

      toggle.type = "button";
      toggle.textContent = item.completed ? "已读" : "待读";
      toggle.setAttribute("aria-pressed", String(item.completed));
      toggle.addEventListener("click", () => updateItem(item.id));

      row.classList.toggle("completed", item.completed);
      row.append(link, toggle);
      return row;
    }),
  );
}

function updateItem(id: ReadingItem["id"]): void {
  items = items.map((item) =>
    item.id === id ? { ...item, completed: !item.completed } : item,
  );
  saveItems(items);
  render();
}

form.addEventListener("submit", (event) => {
  event.preventDefault();
  const data = new FormData(form);
  const title = String(data.get("title") ?? "").trim();
  const url = String(data.get("url") ?? "").trim();
  if (!title || !url) return;

  items = [createReadingItem(title, url), ...items];
  saveItems(items);
  form.reset();
  render();
});

render();