Skip to content

Access lesson URL from collection entry #410

Open
@eric-burel

Description

@eric-burel

Is your feature request related to a problem?

Collection entries are the way you can access to a single lesson using tutorialkit:store or Astro built-in API.

However collection are tad barebone in the fields they provide. A lot of additional logic exists in the getTutorial method used in the main [...slug] route.

The biggest missing point is accessing the lesson URL based on slugs.

Describe the solution you'd like.

Have a way to access the lesson complete URL more easily, using collections.

Describe alternatives you've considered.

Here is some logic I've crafted while building an RSS feed:

/**
 * Dictionary of lesson id to actual slug
 * Not super efficient but will be statically rendered anyway so don't bother
 * { "1_patterns/2_chapter/3_lesson/content.mdx": "part-slug/chapter-slug/lesson-slug" }
 * The key is "lesson.id" in the collection
 * The value is the slug used as URL in the [...slug] page of tutorial kit
 * @returns
 */
async function getSlugs() {
  const chapters = await getCollection(
    "tutorial",
    ({ data }) => data.type === "chapter"
  );
  const parts = await getCollection(
    "tutorial",
    ({ data }) => data.type === "part"
  );
  const lessons = await getCollection(
    "tutorial",
    ({ data }) => data.type === "lesson"
  );
  // p/1_foobar/2_baz/3_qux
  // => we want to turn that into tutorial-slug/part-slug/chapter-slug/lesson-slug
  let ids: Array<string> = lessons.map((l) => l.id);
  const idsToSlugs = new Map<string, string>(ids.map((id) => [id, id]));
  idsToSlugs.forEach((slug, id) => {
    idsToSlugs.set(id, slug.replace("1-patterns", "p"));
    // remove the dangling "/content.mdx"
    idsToSlugs.set(id, slug.replace("/content.mdx", ""));
  });
  parts.forEach((pt) => {
    // id is the actual filename, we want the slug
    // 1_patterns/meta.md => 1_patterns
    const partId = pt.id.split("/")[0];
    idsToSlugs.forEach((slug, id) => {
      // 3_part => part-3-slug
      idsToSlugs.set(id, slug.replace(partId, pt.slug));
    });
  });
  chapters.forEach((ch) => {
    // 1_patterns/2_chapter/meta.md => 2_chapter
    const chapterId = ch.id.split("/")[1];
    idsToSlugs.forEach((slug, id, m) => {
      // 2_chapter => chapter-2-slug
      idsToSlugs.set(id, slug.replace(chapterId, ch.slug));
    });
  });
  lessons.forEach((l) => {
    // 1_patterns/2_chapter/meta.md => 2_chapter
    const lessonId = l.id.split("/")[2];
    idsToSlugs.forEach((slug, id, m) => {
      // 2_chapter => chapter-2-slug
      idsToSlugs.set(id, slug.replace(lessonId, l.slug));
    });
  });
  return Object.fromEntries(idsToSlugs.entries());
}

Not super efficient but does the job: now idsToSlug[lesson.id] gives you the final URL slug of that lesson.

Additional context

I don't know how we can compute derived fields in an Astro entry, if it's possible, nor how we can handle a hierarchy of content, if it's possible.
It might not be possible to rely only on Astro collection system, in which case it could be interesting to expose the getTutorial method or part of it.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions