import * as vscode from "vscode" ; import { MultiStepInput } from "../../common" ; import { BaseCommand } from "../../common/BaseCommand" ; import { promises as fsPromises , readFileSync , writeFileSync } from "fs" ; import * as path from "path" ; import { slugify } from "../../utils/slugify" ; const { readdir } = fsPromises ; export class NewPost extends BaseCommand { readonly TotalSteps : number = 4 ; async run ( ) { const defaultAuthor = vscode . workspace . getConfiguration ( ) . get ( "rmarkdown.blogdown.author" ) ; const state = { projectDir : vscode . workspace . workspaceFolders ! [ 0 ] . uri . path , author : defaultAuthor } as Partial < NewPostOptions > ; if ( ! ( await MultiStepInput . run ( ( input ) => this . setTitle ( input , state ) ) ) ) { return ; } const slug = slugify ( state . title ! ) ; const newPostPath = path . join ( state . category ! , `${ new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) }-${ slug }.Rmd` ) ; simpleNewPostGenerator ( state . archetype ! , newPostPath , state as NewPostOptions ) ; vscode . workspace . openTextDocument ( newPostPath ) . then ( ( document ) => vscode . window . showTextDocument ( document ) ) ; } async setTitle ( input , state ) { state . title = await input . showInputBox ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , prompt : "The Title to be Displayed" , placeholder : "Hello Blogdown" , ignoreFocusOut : true , value : "" , validate : async ( value ) => ( ! value || ! value . trim ( ) ? "Title is required" : "" ) , } ) ; return ( input ) => this . setAuthor ( input , state ) ; } async setAuthor ( input , state ) { state . author = await input . showInputBox ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , prompt : "Author" , placeholder : "your name goes here" , ignoreFocusOut : true , value : typeof state . author === "string" ? state . author : "" , validate : async ( value ) => ( ! value || ! value . trim ( ) ? "Author is required" : "" ) , } ) ; return ( input ) => this . setCategory ( input , state ) ; } async setCategory ( input , state ) { const categories = await getCategories ( state . projectDir ! ) ; const active = undefined ; state . category = await input . showQuickPick ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , placeholder : 'Category (which subdirectory of "content")' , ignoreFocusOut : true , items : categories , activeItem : active , convert : async ( value ) => value . fullPath , } ) ; return ( input ) => this . setArchetype ( input , state ) ; } async setArchetype ( input , state ) { const archetypes = await getArchetypes ( state . projectDir ! ) ; const active = undefined ; state . archetype = await input . showQuickPick ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , placeholder : 'Archetype (in the "archetypes" directory' , ignoreFocusOut : true , items : archetypes , activeItem : active , convert : async ( value ) => value . fullPath , } ) ; } } interface NewPostOptions { projectDir : string ; title : string ; author : string ; category : string ; archetype : string ; } interface CategoryPickItem extends vscode . QuickPickItem { fullPath : string ; } async function getCategories ( projectDir ) { const categoriesParentDir = path . join ( projectDir , "content" ) ; const categories = await ( await readdir ( categoriesParentDir , { withFileTypes : true } ) ) . filter ( ( p ) => p . isDirectory ( ) ) . map ( ( p ) => p . name ) ; return Array . from ( categories , ( cat ) => { return { label : cat , fullPath : path . join ( categoriesParentDir , cat ) } ; } ) ; } interface ArchetypePickItem extends vscode . QuickPickItem { fullPath : string ; } async function getArchetypes ( projectDir ) { const archetypesDir = path . join ( projectDir , "archetypes" ) ; const archetypes = await ( await readdir ( archetypesDir , { withFileTypes : true } ) ) . filter ( ( p ) => p . isFile ( ) && p . name . slice ( p . name . length - 2 ) === "md" ) . map ( ( p ) => p . name ) ; return Array . from ( archetypes , ( arc ) => { return { label : arc , fullPath : join ( archetypesDir , arc ) } ; } ) ; } function simpleNewPostGenerator ( src , dst , options ) { const archetypeLines = readFileSync ( src , "utf8" ) . split ( /\r?\n/g ) ; if ( archetypeLines [ 0 ] !== "---" ) { throw Error ( "Archetypes files must have YAML frontmatter" ) ; } let n = 0 ; let content = archetypeLines . map ( ( line ) => { if ( n === 2 ) { return line ; } else if ( line . slice ( 0 , 3 ) === "---" ) { n ++ ; return line ; } let m = line . match ( /^[a-z]+/ ) ; if ( m !== null ) { switch ( m [ 0 ] ) { case "title" : { return "title: " + options . title ; } case "author" : { return "author: " + options . author ; } case "date" : { return "date: " + new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) ; } } } return line ; } ) . join ( "\n" ) ; writeFileSync ( dst , content , "utf8" ) ; }