Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sortable tables #639

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
141 changes: 141 additions & 0 deletions docs/.vitepress/components/GenericTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<template>
<div>
<table>
<thead>
<tr
v-for="headerGroup in table.getHeaderGroups()"
:key="headerGroup.id"
>
<th
v-for="header in headerGroup.headers"
:key="header.id"
:colSpan="header.colSpan"
:class="
header.column.getCanSort() ? 'cursor-pointer select-none' : ''
"
@click="header.column.getToggleSortingHandler()?.($event)"
>
<template v-if="!header.isPlaceholder">
<FlexRender
:render="header.column.columnDef.header"
:props="header.getContext()"
/>
{{
{ asc: " ↑", desc: " ↓" }[header.column.getIsSorted() as string]
}}
</template>
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in table.getRowModel().rows" :key="row.id">
<td v-for="cell in row.getVisibleCells()" :key="cell.id">
<FlexRender
:render="cell.column.columnDef.cell"
:props="cell.getContext()"
/>
</td>
</tr>
</tbody>
</table>
</div>
</template>

<script setup lang="ts">
import { ref, computed, PropType, h } from "vue";
import {
FlexRender,
useVueTable,
getSortedRowModel,
getCoreRowModel,
SortingState,
} from "@tanstack/vue-table";

type FormatConfig =
| {
type: "highlight";
tokens: string[];
}
| ({
type: "numeric";
} & Intl.NumberFormatOptions);
const props = defineProps({
tableData: {
type: Object as PropType<{
fields: Array<{
key: string;
label: string;
sortable?: boolean;
format?: FormatConfig;
}>;
items: Array<Record<string, unknown>>;
}>,
required: true,
},
});

// Extract columns from the 'fields' array
const columns = computed(() => {
return props.tableData.fields.map((field) => ({
accessorKey: field.key,
header: field.label,
// Enable sorting only if the field has 'sortable: true'
enableSorting: field.sortable === true,
cell: (c) => {
const value = c.getValue();

// Handle numeric formatting
if (field.format?.type === "numeric" && !isNaN(value)) {
return new Intl.NumberFormat("en-US", field.format).format(value);
}

// Handle highlight formatting
if (field.format?.type === "highlight" && field.format.tokens) {
// Split the text by tokens and create spans for highlighted parts
const text = String(value);
const tokens = field.format.tokens;

// Create array of text parts and highlighted tokens
const parts = text.split(new RegExp(`(${tokens.join("|")})\\s`));

return h(
"span",
{},
parts.map((part) => {
if (tokens.includes(part)) {
return h("span", { class: "highlight" }, `${part} `);
}
return part;
}),
);
}

return value;
},
}));
});

const data = ref(props.tableData.items || []);
const sorting = ref<SortingState>([]);

// Create and configure the table
const table = useVueTable({
columns: columns.value,
get data() {
return data.value;
},
state: {
get sorting() {
return sorting.value;
},
},
onSortingChange: (updaterOrValue) => {
sorting.value =
typeof updaterOrValue === "function"
? updaterOrValue(sorting.value)
: updaterOrValue;
},
getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(),
});
</script>
6 changes: 6 additions & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineConfig } from 'vitepress'
import { SearchPlugin } from 'kagi-sidekick-vitepress'
import { markdownItJsonTablePlugin } from './custom_scripts/markdownItJsonTablePlugin'

// https://vitepress.dev/reference/site-config
export default defineConfig({
Expand All @@ -12,6 +13,11 @@ export default defineConfig({
['link', { rel: "mask-icon", href: "/safari-pinned-tab.svg", color: "#5bbad5" }],
['meta', { name: "msapplication-TileColor", content: "#ffffff" }],
],
markdown: {
config: (md) => {
md.use(markdownItJsonTablePlugin)
},
},
themeConfig: {
// https://vitepress.dev/reference/default-theme-config
nav: [
Expand Down
35 changes: 35 additions & 0 deletions docs/.vitepress/custom_scripts/markdownItJsonTablePlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* markdownItJsonTablePlugin
* A markdown-it plugin that looks for code blocks labeled as "json:table"
* and renders a <GenericTable> component with the parsed JSON as props.
*/
export function markdownItJsonTablePlugin(md) {
// Capture the original fence renderer
const defaultFenceRenderer = md.renderer.rules.fence ||
function(tokens, idx, options, env, self) {
return self.renderToken(tokens, idx, options);
};

md.renderer.rules.fence = function(tokens, idx, options, env, self) {
const token = tokens[idx];
// Check if it's labeled as "json:table"
const info = token.info.trim();
if (info === 'json:table') {
try {
// Attempt to parse the code block content as JSON
const tableData = JSON.parse(token.content.trim());

// Return a string of HTML that includes the <GenericTable> component
// with the JSON data passed as a prop.
// You must ensure <GenericTable> is globally registered in VitePress.
return `<GenericTable :tableData='${JSON.stringify(tableData)}' />`;
} catch (error) {
// If JSON parsing fails, fall back to default fence rendering
console.error('Error parsing JSON for json:table:', error);
}
}

return defaultFenceRenderer(tokens, idx, options, env, self);
};
}

14 changes: 13 additions & 1 deletion docs/.vitepress/theme/custom.scss
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
@import "theme";
@import "custom_code";
@import "custom_code";

.cursor-pointer {
cursor: pointer;
}

.select-none {
user-select: none;
}

.highlight {
font-weight: bold;
}
2 changes: 2 additions & 0 deletions docs/.vitepress/theme/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import DefaultTheme from "vitepress/theme";
import queryHack from "../custom_scripts/search_query_hack";
import GenericTable from '../components/GenericTable.vue'
import { onMounted, watch, nextTick } from 'vue';
import { useRoute } from 'vitepress';
import mediumZoom from 'medium-zoom';
Expand All @@ -9,6 +10,7 @@ export default {
...DefaultTheme,
enhanceApp({ app }) {
queryHack();
app.component("GenericTable", GenericTable)
},
setup() {
const route = useRoute();
Expand Down
Loading