Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .vuepress/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@

import { defineClientConfig } from 'vuepress/client';
import BlogPosts from './components/BlogPosts.vue';
import JumpToc from './components/JumpToc.vue';
import PrBy from './components/PrBy.vue';
import ReleaseToc from './components/ReleaseToc.vue';

export default defineClientConfig({
enhance({ app }) {
app.component('BlogPosts', BlogPosts);
app.component('JumpToc', JumpToc);
app.component('PrBy', PrBy);
app.component('ReleaseToc', ReleaseToc);
},
});
1 change: 1 addition & 0 deletions .vuepress/components/JumpToc.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<template>[<a href="#table-of-contents">toc</a>]</template>
28 changes: 28 additions & 0 deletions .vuepress/components/NestedList.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<template>
<ul>
<li v-for="(item, i) in items" :key="i">
<span v-html="item.label"></span>

<NestedList
v-if="item.children && item.children.length"
:items="item.children"
/>
</li>
</ul>
</template>

<script setup lang="ts">
defineOptions({ name: 'NestedList' });

defineProps({
items: {
type: Array<Item>,
required: true,
},
});

export interface Item {
label: String;
children: Item[];
}
</script>
29 changes: 29 additions & 0 deletions .vuepress/components/PrBy.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<template>
<p style="font-size: 0.8rem">
PR <a :href="prLink" target="_blank">#{{ pr }}</a> by
<a :href="userLink" target="_blank">@{{ user }}</a>
</p>
</template>

<style scoped>
p {
font-size: 0.8rem;
margin: 0;
}
</style>

<script setup lang="ts">
const props = defineProps({
pr: {
type: Number,
required: true,
},
user: {
type: String,
required: true,
},
});

const prLink = 'https://github.com/nushell/nushell/pull/' + props.pr;
const userLink = 'https://github.com/' + props.user;
</script>
95 changes: 95 additions & 0 deletions .vuepress/components/ReleaseToc.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<template>
<NestedList :items="items"></NestedList>
</template>

<script setup lang="ts">
import NestedList, { Item } from './NestedList.vue';
import { ref, onMounted, nextTick } from 'vue';
import { onContentUpdated } from 'vuepress/client';

const items = ref([] as Item[]);

function getHeaders(): NodeListOf<HTMLHeadingElement> {
return document.querySelectorAll('h1, h2, h3');
}

type OrganizedHeader = {
element?: HTMLHeadingElement;
children: OrganizedHeader[];
};
function organizeHeaders(
headers: HTMLHeadingElement[],
index: [number] = [0],
level: number = 1,
): OrganizedHeader {
const node: OrganizedHeader = { children: [] };

while (index[0] < headers.length) {
const header = headers[index[0]];
const headerLevel = Number(header.tagName.slice(1)); // "H2" -> 2

// if we hit a header above our current level, we are done here
if (headerLevel < level) break;

// if we hit a header deeper than expected, let the caller handle it as children
if (headerLevel > level) break;

// headerLevel === level: consume this header and attach its children.
index[0]++;

const entry: OrganizedHeader = { element: header, children: [] };

// children are the following headers with level+1 (and their descendants)
const childrenTree = organizeHeaders(headers, index, level + 1);
entry.children = childrenTree.children;

node.children.push(entry);
}

return node;
}

function filterHeaders(root: OrganizedHeader): OrganizedHeader {
const wanted = [
'Highlights and themes of this release',
'Changes',
'Notes for plugin developers',
'Hall of fame',
'Full changelog',
].map((section) => section.toLowerCase());

return {
...root,
children: root.children.filter((section) =>
wanted.some((wanted) =>
section.element!.innerText.toLowerCase().startsWith(wanted),
),
),
};
}

function generateItem(header: OrganizedHeader): Item {
let slug = '#' + header.element?.id;
let content = header.element?.querySelector('span')?.innerHTML;

const i = content?.lastIndexOf('[');
if (i !== -1) content = content?.slice(0, i).trim();

return {
label: `<em><a href="${slug}">${content}</a></em>`,
children: header.children.map(generateItem),
};
}

async function refresh() {
await nextTick();
const headers = Array.from(getHeaders());
const organized = organizeHeaders(headers);
const filtered = filterHeaders(organized);
const item = generateItem(filtered);
items.value = item.children;
}

onMounted(refresh);
onContentUpdated(refresh);
</script>
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,8 @@
"engines": {
"npm": ">=9.0.0",
"node": ">=18.12.0"
},
"volta": {
"node": "22.22.0"
}
}