I have encountered a problem while making a personal Gatsby site whereby blog post files are having thier unique folders included in the slug.
For example a file structure of:
data
|
|– blog
|
|– blog-1
| |
| |-blog-1.mdx
| |-img.jpg
|
|– blog-2
| |
| |-blog-2.mdx
| |-img.jpg
|
|– blog-3
| |
| |-blog-3.mdx
| |-img.jpg
Will, for example, produce slugs like this
{
"data": {
"allMdx": {
"edges": [
{
"node": {
"fields": {
"slug": "/blog-1/blog-1/"
},
"frontmatter": {
"title": "Blog 1",
"posttype": "blog"
}
}
},
{
"node": {
"fields": {
"slug": "/blog-2/blog-2/"
},
"frontmatter": {
"title": "Blog 2",
"posttype": "blog"
}
}
},
{
"node": {
"fields": {
"slug": "/blog-3/blog-3/"
},
"frontmatter": {
"title": "Blog 3",
"posttype": "blog"
}
}
},
I expect them to produce a slug like this:
{
"node": {
"fields": {
"slug": "/blog-1/"
},
"frontmatter": {
"title": "Blog 1",
"posttype": "blog"
}
}
},
The path to the parent blog folder is included in my gatsby-config like this:
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/data/blog`,
name: `blog`,
},
},
And then my gatsby-node folder is set up like this:
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
const blogPost = path.resolve(`./src/templates/blog-post.js`)
const portfolioPost = path.resolve(`./src/templates/portfolio-post.js`)
const journeyPost = path.resolve(`./src/templates/journey-post.js`)
return graphql(
`
{
allMdx(
sort: { fields: [frontmatter___date], order: DESC }
limit: 1000
) {
edges {
node {
fields {
slug
}
frontmatter {
title
posttype
}
}
}
}
}
`
).then(result => {
if (result.errors) {
throw result.errors
}
const posts = result.data.allMdx.edges
posts.forEach((post, index) => {
const previous = index === posts.length - 1 ? null : posts[index + 1].node
const next = index === 0 ? null : posts[index - 1].node
if (post.node.frontmatter.posttype == "portfolio") {
createPage({
path: `/portfolio${post.node.fields.slug}`,
component: portfolioPost,
context: {
slug: post.node.fields.slug,
previous,
next,
},
})
} else if (post.node.frontmatter.posttype == "journey") {
createPage({
path: `/journey${post.node.fields.slug}`,
component: journeyPost,
context: {
slug: post.node.fields.slug,
previous,
next,
},
})
} else {
createPage({
path: `/blog${post.node.fields.slug}`,
component: blogPost,
context: {
slug: post.node.fields.slug,
previous,
next,
},
})
}
})
return null
})
}
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `Mdx`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
Note that the journey and portfolio paths in this file are at this point in time doing exactly the same thing as the blog path. They are set up in exactly the same way and are just split out depending on posttype. Pages are created fine but they are all using the unwanted folder/file.mdx slug.
Fixed by looking at other blog examples.
Post filename needs to be index.md or index.mdx
I am sure there is a better way but I was able to solve it by making changes in gatsby-node.js to only take the substring after the last slash (/) in from the file path. If someone knows a better way I will be glad to know that.
Old:
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
New:
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode, trailingSlash:false })
createNodeField({
name: `slug`,
node,
value: `${value.indexOf("/") > -1 ? value.substring(value.lastIndexOf("/")) : value}`,
})
}
}
Related
I am trying to use createRemoteFileNode to create optimised images for an array of nodes that exist on a Product.
I have a Product that has items and on each item, it has a featuredImg. I can create a featuredImg for a Product but as soon as I try to create it for the child nodes (items) then it is not queryable.
I am creating my nodes as such:
const products = [
{
id: "product_1",
imageUrl: "https://images.unsplash.com/photo-1665081661649-8656335a6cbb?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1738&q=80",
items: [
{
id: 'item_1',
imageUrl: "https://images.unsplash.com/photo-1666120565124-7e763880444a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80"
}
]
}
]
const sourceNodes = async ({ actions, createNodeId, createContentDigest }, options) => {
products.forEach((testNode) => {
const node = {
...testNode,
id: createNodeId(`Product-${testNode.id}`),
}
actions.createNode({
...node,
internal: {
type: 'Product',
contentDigest: createContentDigest(node),
},
});
testNode.items.forEach(item => {
const itemNode = {
...item,
id: createNodeId(`Item-${item.id}`),
}
actions.createNode({
...itemNode,
parent: node.id,
internal: {
type: 'Item',
contentDigest: createContentDigest(itemNode),
},
});
})
})
};
module.exports = sourceNodes;
Then on the node creation, I am running the onCreateNode function which should create the remote file node for each item featuredImg.
const { createRemoteFileNode } = require(`gatsby-source-filesystem`);
const onCreateNode = async ({ node, cache, store, getCache, actions: { createNode, createNodeField }, createNodeId }) => {
if( node.internal.type === 'Item') {
const fileNode = await createRemoteFileNode({
url: node.imageUrl,
parentNodeId: node.parent,
createNode,
createNodeId,
getCache,
})
if (fileNode) {
createNodeField({ node, name: "localFile", value: fileNode.id })
}
}
if( node.internal.type === 'Product') {
const fileNode = await createRemoteFileNode({
url: node.imageUrl,
parentNodeId: node.id,
createNode,
createNodeId,
getCache,
})
if (fileNode) {
createNodeField({ node, name: "localFile", value: fileNode.id })
}
}
};
module.exports = onCreateNode
I have defined my types here:
module.exports = ({ actions }) => {
const { createTypes } = actions;
const typeDefs = `
type Product implements Node {
id: String!
imageUrl: String!
featuredImg: File #link(from: "fields.localFile")
items: [Item]
}
type Item implements Node {
id: String!
imageUrl: String!
featuredImg: File #link(from: "fields.localFile")
}
`;
createTypes(typeDefs);
};
For some reason, when I query Products.items[i].featuredImg it always returns null. However, I can see the node is generated because I can query item.featuredImg and it returns the gatsbyImageData.
I have created a simple example here and included a read me on how to replicate it: https://github.com/stretch0/gatsby-sandbox
I have also noticed that this post is a similar issue of not being able to create remote file nodes within a loop but because they have a different file structure, I can't figure out how their solution to use createSchemaCustomization or createResolvers would apply to my setup.
I am trying to build sub-pages for a projects category in Gatsby, each project parent page already generates the way it should but the sub-pages do not.
Each project can have zero to many sub-pages, I only want a sub-page to be generated if it exists. Data is coming from a headless CMS through GraphQL
My loop for generating these pages in gatsby-node.js currently looks like this:
result.data.allSanityProjects.edges.forEach(({ node }) => {
node.projectChildPages.map(childPage => {
if (node && node.projectChildPages.length > 0 && node.projectChildPages.slug) {
createPage({
path: childPage.slug + "/" + node.projectChildPages.slug,
component: projectsSubPages,
context: {
slug: childPage.slug + "/" + node.projectChildPages.slug,
},
});
}
});
});
});
This loops through the "allSanityProjects" part of this GrapQL query
{
allSanityDefaultPage {
edges {
node {
slug
}
}
}
allSanityProjects {
edges {
node {
slug
projectChildPages {
slug
}
}
}
}
}
The results of running just the allSanityProjects-query looks like this
{
"data": {
"allSanityProjects": {
"edges": [
{
"node": {
"slug": "project-3",
"projectChildPages": []
}
},
{
"node": {
"slug": "project-1",
"projectChildPages": [
{
"slug": "project-1"
},
{
"slug": "Doggolicious"
},
{
"slug": "no-cats"
}
]
}
},
{
"node": {
"slug": "Project-2",
"projectChildPages": []
}
}
]
}
}
}
Gatsby fails building the project child pages with the following error.
warn The GraphQL query in the non-page component
Exported queries are only executed for Page components. It's possible you're
trying to create pages in your gatsby-node.js and that's failing for some
reason.
If the failing component(s) is a regular component and not intended to be a page
component, you generally want to use a <StaticQuery> (https://gatsbyjs.org/docs/static-query)
instead of exporting a page query.
If you're more experienced with GraphQL, you can also export GraphQL
fragments from components and compose the fragments in the Page component
query and pass data down into the child component — https://graphql.org/learn/queries/#fragments
My template looks like this:
import React from "react";
import { useStaticQuery, graphql } from "gatsby";
import Layout from "../components/layout";
const BlockContent = require("#sanity/block-content-to-react");
const projectsSubPages = ({ data }) => {
const pageData = data.sanityProjects.projectChildPages;
return (
<Layout>
<BlockContent blocks={pageData._rawBlockContent} />
</Layout>
);
};
export const query = graphql`
query($slug: String!) {
sanityProjects(slug: { eq: $slug }) {
projectChildPages {
_rawBlockContent
slug
title
}
}
}
`;
export default projectsSubPages;
As far as I can tell my error is in my gatsby-node.js file, not in my template even though gatsby tells me my error is in my template. I've tried running the exact same templates as the others I use (just with different queries in them) and still get the same error.
My full gatsby-node.js file:
exports.createPages = ({ actions, graphql }) => {
const path = require(`path`);
const { createPage } = actions;
const projects = path.resolve("src/templates/projects.js");
const defaultPage = path.resolve("src/templates/defaultPage.js");
const projectsSubPages = path.resolve("src/templates/projectsSubPages.js");
return graphql(`
{
allSanityDefaultPage {
edges {
node {
slug
}
}
}
allSanityProjects {
edges {
node {
slug
projectChildPages {
slug
}
}
}
}
}
`).then((result) => {
if (result.errors) {
reporter.panic("failed to create pages ", result.errors);
}
result.data.allSanityDefaultPage.edges.forEach(({ node }) => {
createPage({
path: node.slug,
component: defaultPage,
context: {
slug: node.slug,
},
});
});
result.data.allSanityProjects.edges.forEach(({ node }) => {
createPage({
path: node.slug,
component: projects,
context: {
slug: node.slug,
},
});
});
result.data.allSanityProjects.edges.forEach(({ node }) => {
node.projectChildPages.map(childPage => {
if (node && node.projectChildPages.length > 0 && node.projectChildPages.slug) {
createPage({
path: childPage.slug + "/" + node.projectChildPages.slug,
component: projectsSubPages,
context: {
slug: childPage.slug + "/" + node.projectChildPages.slug,
},
});
}
});
});
});
};
code
result.data.allSanityProjects.edges.forEach(({ node }) => {
node.projectChildPages.map(childPage => {
if (node && node.projectChildPages.length > 0 && node.projectChildPages.slug) {
Condition doesn't have much sense there:
if you're in .map() then for sure node and node.projectChildPages.length > 0 are true
projectChildPages is an array so no projectChildPages.slug here
query
Your fetched data (source) doesn't contain _rawBlockContent so you can't query for this in page component.
Using the Flexible Gatsby starter (https://github.com/wangonya/flexible-gatsby/), exact versions of Gatsby dependencies are:
"gatsby": "2.15.36",
"gatsby-image": "2.2.27",
"gatsby-plugin-feed": "2.3.15",
"gatsby-plugin-google-analytics": "2.1.20",
"gatsby-plugin-manifest": "2.2.20",
"gatsby-plugin-offline": "3.0.12",
"gatsby-plugin-react-helmet": "3.1.10",
"gatsby-plugin-sass": "2.1.17",
"gatsby-plugin-sharp": "2.2.28",
"gatsby-remark-images": "3.1.25",
"gatsby-remark-prismjs": "3.3.17",
"gatsby-source-filesystem": "2.1.30",
"gatsby-transformer-remark": "2.6.27",
"gatsby-transformer-sharp": "2.2.20",
I can run gatsby develop to get a watch on the files in the folder that refreshes UI changes and such. However, as soon as I save any changes to any of the files in content/blog develop hits the following error:
info added file at /Users/mattsi/dev/me/newsite/gatsby/content/blog/conference-on-javascript/index.md
ERROR #11321 PLUGIN
"gatsby-node.js" threw an error while running the createPages lifecycle:
The "path" argument must be of type string. Received type undefined
GraphQL request:14:17
13 | title
14 | img {
| ^
15 | childImageSharp {
After that, the local server responds with an error page saying TypeError: Cannot read property 'page' of undefined and a stack trace pointing to rootjs:44. If I kill the watch and do gatsby develop again, it works fine. But that feedback loop is obviously much slower.
My Gatsby Config (some details omitted with ...):
module.exports = {
siteMetadata: {
title: `...`,
description: `...`,
author: `...`,
siteUrl: `...`,
social: {
twitter: `...`,
facebook: ``,
github: `...`,
linkedin: `...`,
email: `...`,
},
},
plugins: [
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/content/blog`,
name: `blog`,
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
path: `${__dirname}/content/assets`,
name: `assets`,
},
},
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
{
resolve: `gatsby-remark-images`,
options: {
maxWidth: 970,
},
},
`gatsby-remark-prismjs`,
],
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-google-analytics`,
options: {
//trackingId: `ADD YOUR TRACKING ID HERE`,
},
},
`gatsby-plugin-feed`,
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `...`,
short_name: `...`,
start_url: `/`,
background_color: `#663399`,
theme_color: `#663399`,
display: `minimal-ui`,
icon: `./static/gatsby-icon.png`, // This path is relative to the root of the site.
},
},
// `gatsby-plugin-offline`,
`gatsby-plugin-react-helmet`,
`gatsby-plugin-sass`,
],
}
My gatsby-node.js file:
const path = require(`path`)
const { createFilePath } = require(`gatsby-source-filesystem`)
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
const blogPost = path.resolve(`./src/templates/blog-post.js`)
return graphql(
`
{
allMarkdownRemark(
sort: { fields: [frontmatter___date], order: DESC }
limit: 1000
) {
edges {
node {
fields {
slug
}
frontmatter {
title
img {
childImageSharp {
fluid(maxWidth: 3720) {
aspectRatio
base64
sizes
src
srcSet
}
}
}
}
}
}
}
}
`
).then(result => {
if (result.errors) {
throw result.errors
}
// Create blog posts pages.
const posts = result.data.allMarkdownRemark.edges
posts.forEach((post, index) => {
const previous = index === posts.length - 1 ? null : posts[index + 1].node
const next = index === 0 ? null : posts[index - 1].node
createPage({
path: post.node.fields.slug,
component: blogPost,
context: {
slug: post.node.fields.slug,
previous,
next,
},
})
})
// Create blog post list pages
const postsPerPage = 10
const numPages = Math.ceil(posts.length / postsPerPage)
Array.from({ length: numPages }).forEach((_, i) => {
createPage({
path: i === 0 ? `/` : `/${i + 1}`,
component: path.resolve("./src/templates/blog-list.js"),
context: {
limit: postsPerPage,
skip: i * postsPerPage,
numPages,
currentPage: i + 1,
},
})
})
})
}
exports.onCreateNode = ({ node, actions, getNode }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const value = createFilePath({ node, getNode })
createNodeField({
name: `slug`,
node,
value,
})
}
}
Looking into the stack trace, it seems as if the post.node.fields.slug field on the modified post from the allMarkdownRemark GraphQL DB is coming back as undefined. But using the built-in GraphQL browser it seems to always be populated. Perhaps it's very briefly unpopulated while refreshing, faster than I can catch, and this causes the watch to crash? Any ideas how I can fix Gatsby's watch functionality?
I have this issue too and I believe it will be fixed by this PR - it was for me at least. Perhaps you could test it out and comment on the PR if it helps you.
Subscriptions with Nexus are undocumented but I searched Github and tried every example in the book. It's just not working for me.
I have cloned Prisma2 GraphQL boilerplate project & my files are as follows:
prisma/schema.prisma
datasource db {
provider = "sqlite"
url = "file:dev.db"
default = true
}
generator photon {
provider = "photonjs"
}
generator nexus_prisma {
provider = "nexus-prisma"
}
model Pokemon {
id String #default(cuid()) #id #unique
number Int #unique
name String
attacks PokemonAttack?
}
model PokemonAttack {
id Int #id
special Attack[]
}
model Attack {
id Int #id
name String
damage String
}
src/index.js
const { GraphQLServer } = require('graphql-yoga')
const { join } = require('path')
const { makeSchema, objectType, idArg, stringArg, subscriptionField } = require('#prisma/nexus')
const Photon = require('#generated/photon')
const { nexusPrismaPlugin } = require('#generated/nexus-prisma')
const photon = new Photon()
const nexusPrisma = nexusPrismaPlugin({
photon: ctx => ctx.photon,
})
const Attack = objectType({
name: "Attack",
definition(t) {
t.model.id()
t.model.name()
t.model.damage()
}
})
const PokemonAttack = objectType({
name: "PokemonAttack",
definition(t) {
t.model.id()
t.model.special()
}
})
const Pokemon = objectType({
name: "Pokemon",
definition(t) {
t.model.id()
t.model.number()
t.model.name()
t.model.attacks()
}
})
const Query = objectType({
name: 'Query',
definition(t) {
t.crud.findManyPokemon({
alias: 'pokemons'
})
t.list.field('pokemon', {
type: 'Pokemon',
args: {
name: stringArg(),
},
resolve: (parent, { name }, ctx) => {
return ctx.photon.pokemon.findMany({
where: {
name
}
})
},
})
},
})
const Mutation = objectType({
name: 'Mutation',
definition(t) {
t.crud.createOnePokemon({ alias: 'addPokemon' })
},
})
const Subscription = subscriptionField('newPokemon', {
type: 'Pokemon',
subscribe: (parent, args, ctx) => {
return ctx.photon.$subscribe.pokemon()
},
resolve: payload => payload
})
const schema = makeSchema({
types: [Query, Mutation, Subscription, Pokemon, Attack, PokemonAttack, nexusPrisma],
outputs: {
schema: join(__dirname, '/schema.graphql')
},
typegenAutoConfig: {
sources: [
{
source: '#generated/photon',
alias: 'photon',
},
],
},
})
const server = new GraphQLServer({
schema,
context: request => {
return {
...request,
photon,
}
},
})
server.start(() => console.log(`🚀 Server ready at http://localhost:4000`))
The related part is the Subscription which I don't know why it's not working or how it's supposed to work.
I searched Github for this query which results in all projects using Subscriptions.
I also found out this commit in this project to be relevant to my answer. Posting the related code here for brevity:
import { subscriptionField } from 'nexus';
import { idArg } from 'nexus/dist/core';
import { Context } from './types';
export const PollResultSubscription = subscriptionField('pollResult', {
type: 'AnswerSubscriptionPayload',
args: {
pollId: idArg(),
},
subscribe(_: any, { pollId }: { pollId: string }, context: Context) {
// Subscribe to changes on answers in the given poll
return context.prisma.$subscribe.answer({
node: { poll: { id: pollId } },
});
},
resolve(payload: any) {
return payload;
},
});
Which is similar to what I do. But they do have AnswerSubscriptionPayload & I don't get any generated type that contains Subscription in it.
How do I solve this? I think I am doing everything right but it's still not working. Every example on GitHub is similar to above & even I am doing the same thing.
Any suggestions?
Edit: Subscriptions aren't implemented yet :(
I seem to have got this working despite subscriptions not being implemented. I have a working pubsub proof of concept based off the prisma2 boilerplate and Ben Awad's video tutorial https://youtu.be/146AypcFvAU . Should be able to get this up and running with redis and websockets to handle subscriptions until the prisma2 version is ready.
https://github.com/ryanking1809/prisma2_subscriptions
Subscriptions aren't implemented yet.
I've opened up an issue to track it.
I'll edit this answer as soon as it's implemented in Prisma 2.
I want to create a tree in VS code, but my problem is how to manually add a node to my tree. I am not sure from where to start. I tried to review all the projects that created a tree for VScode as an extension.
My problem is that I am not an expert in Typescript and the examples are not so clear or I am not sure how it is working.
Would you mind helping me to understand how to create the tree in VS code? My problem is with creating a node and then adding the node to tree.
I reviewed these projects:
vscode-code-outline
vscode-extension-samples
vscode-git-tree-compare
vscode-html-languageserver-bin
vscode-mock-debug
vscode-tree-view
Update1:
I managed to use "vscode-extension-samples" and generate the below code examples; now I don't know what I should do, or in other words, how to fill the tree. I tried to use mytree class to fill the data but it didn't work. Would you mind advising me what is next?
extension.ts
'use strict';
import * as vscode from 'vscode';
import { DepNodeProvider } from './nodeDependencies'
import { JsonOutlineProvider } from './jsonOutline'
import { FtpExplorer } from './ftpExplorer.textDocumentContentProvider'
import { FileExplorer } from './fileExplorer';
//mycode
import { SCCExplorer } from './sccExplorer';
export function activate(context: vscode.ExtensionContext) {
// Complete Tree View Sample
new FtpExplorer(context);
new FileExplorer(context);
//mycode
new SCCExplorer(context);
// Following are just data provider samples
const rootPath = vscode.workspace.rootPath;
const nodeDependenciesProvider = new DepNodeProvider(rootPath);
const jsonOutlineProvider = new JsonOutlineProvider(context);
vscode.window.registerTreeDataProvider('nodeDependencies', nodeDependenciesProvider);
vscode.commands.registerCommand('nodeDependencies.refreshEntry', () => nodeDependenciesProvider.refresh());
vscode.commands.registerCommand('nodeDependencies.addEntry', node => vscode.window.showInformationMessage('Successfully called add entry'));
vscode.commands.registerCommand('nodeDependencies.deleteEntry', node => vscode.window.showInformationMessage('Successfully called delete entry'));
vscode.commands.registerCommand('extension.openPackageOnNpm', moduleName => vscode.commands.executeCommand('vscode.open', vscode.Uri.parse(`https://www.npmjs.com/package/${moduleName}`)));
vscode.window.registerTreeDataProvider('jsonOutline', jsonOutlineProvider);
vscode.commands.registerCommand('jsonOutline.refresh', () => jsonOutlineProvider.refresh());
vscode.commands.registerCommand('jsonOutline.refreshNode', offset => jsonOutlineProvider.refresh(offset));
vscode.commands.registerCommand('jsonOutline.renameNode', offset => jsonOutlineProvider.rename(offset));
vscode.commands.registerCommand('extension.openJsonSelection', range => jsonOutlineProvider.select(range));
}
sccExplorer.ts
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import * as mkdirp from 'mkdirp';
import * as rimraf from 'rimraf';
//#region Utilities
interface Entry {
uri: vscode.Uri,
type: vscode.FileType
}
//#endregion
export class FileSystemProvider implements vscode.TreeDataProvider<Entry> {
getTreeItem(element: Entry): vscode.TreeItem | Thenable<vscode.TreeItem> {
throw new Error("Method not implemented.");
}
onDidChangeTreeData?: vscode.Event<Entry>;
getChildren(element?: Entry): vscode.ProviderResult<Entry[]> {
throw new Error("Method not implemented.");
}
getParent?(element: Entry): vscode.ProviderResult<Entry> {
throw new Error("Method not implemented.");
}
private _onDidChangeFile: vscode.EventEmitter<vscode.FileChangeEvent[]>;
constructor() {
this._onDidChangeFile = new vscode.EventEmitter<vscode.FileChangeEvent[]>();
}
}
export class SCCExplorer {
private fileExplorer: vscode.TreeView<any>;
constructor(context: vscode.ExtensionContext) {
const treeDataProvider = new myTree().directories;
this.fileExplorer = vscode.window.createTreeView('scc_Explorer', { treeDataProvider });
vscode.commands.registerCommand('scc_Explorer.openFile', (resource) => this.openResource(resource));
}
private openResource(resource: vscode.Uri): void {
vscode.window.showTextDocument(resource);
}
}
export class myTree{
directories: any;
constructor()
{
this.directories = [
{
name: 'parent1',
child: [{
name: 'child1',
child: []
},
{
name: 'child2',
child: []
}]
},
{
name: 'parent2',
child: {
name: 'child1',
child: []
}
},
{
name: 'parent2',
child: [{
name: 'child1',
child: []
},
{
name: 'child2',
child: []
}]
}];
}
}
I finally got it working. It took me very long and I had it right all the time. My issue was I never did explicitly expand the items, so I would never see nested results and only the top level.
Basic working example
import * as vscode from "vscode";
export class OutlineProvider
implements vscode.TreeDataProvider<any> {
constructor(private outline: any) {
console.log(outline);
}
getTreeItem(item: any): vscode.TreeItem {
return new vscode.TreeItem(
item.label,
item.children.length > 0
? vscode.TreeItemCollapsibleState.Expanded
: vscode.TreeItemCollapsibleState.None
);
}
getChildren(element?: any): Thenable<[]> {
if (element) {
return Promise.resolve(element.children);
} else {
return Promise.resolve(this.outline);
}
}
}
export function activate(context: vscode.ExtensionContext) {
let disposable = vscode.commands.registerCommand(
"outliner.outline",
async () => {
vscode.window.registerTreeDataProvider(
"documentOutline",
new OutlineProvider([dataObject])
);
}
);
context.subscriptions.push(disposable);
}
const dataObject = {
label: "level one",
children: [
{
label: "level two a",
children: [
{
label: "level three",
children: [],
},
],
},
{
label: "level two b",
children: [],
},
],
}
And of course in package.json
"contributes": {
"commands": [
{
"command": "outliner.outline",
"title": "Outline"
}
],
"views": {
"explorer": [
{
"id": "documentOutline",
"name": "Document Outline"
}
]
}
},
Types
note the type for treeDataProvider is not neccecarly what you return. Only the getTree item has to return a tree item or a class that extends it.
interface CustomType {
label: string
children?: CustomType[]
}
export class TypeExample
implements vscode.TreeDataProvider<CustomType> {
constructor(private data: CustomType[]) { }
getTreeItem(element: CustomType): vscode.TreeItem {
return new vscode.TreeItem(
element.label,
(element.children?.length ?? 0) > 0
? vscode.TreeItemCollapsibleState.Expanded
: vscode.TreeItemCollapsibleState.None
);
}
getChildren(element?: CustomType): Thenable<CustomType[]> {
return element && Promise.resolve(element.children ?? [])
|| Promise.resolve(this.data);
}
}
I thought at first the type of the data provider should be the return type of the tree item, this doesnt make much sense of course and I was trying to wrap my head around the reasoning. Now I understand that you pass your custom type in and all other methods inherit this type and expect this type as its argument. Only the getTreeItem method has to return a valid tree item that can be rendered.