Initial Push

This commit is contained in:
Jonathan Deates 2023-01-09 22:42:18 -06:00
parent 41d2e6ae77
commit bdf28d2d73
16 changed files with 4646 additions and 0 deletions

134
.gitignore vendored Normal file
View file

@ -0,0 +1,134 @@
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.idea
.DS_Store

24
README.md Normal file
View file

@ -0,0 +1,24 @@
# Obsidian Pivotal Tracker Integration Plugin
This is a plugin for Obsidian (https://obsidian.md), for the intergration between Pivotal Tracker (https://www.pivotaltracker.com/)
This project uses Typescript to provide type checking and documentation.
## Setup the Plugin
Once the plugin is installed, you will need to configure a few things.
- First you need to get your Tracker API Key
- This is inside your account settings
- Then You need your Tracker Project ID
- This can be found at the url of your tracker project.
- Finally you need to specify a folder location for the stories to be pulled to
## How to use
- Enter settings within the plugin.
-
- Clone this repo.
- `npm i` or `yarn` to install dependencies
- `npm run dev` to start compilation in watch mode.
## Manually installing the plugin
- Copy over `main.js`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`.

42
esbuild.config.mjs Normal file
View file

@ -0,0 +1,42 @@
import esbuild from "esbuild";
import process from "process";
import builtins from 'builtin-modules'
const banner =
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
`;
const prod = (process.argv[2] === 'production');
esbuild.build({
banner: {
js: banner,
},
entryPoints: ['main.ts'],
bundle: true,
external: [
'obsidian',
'electron',
'@codemirror/autocomplete',
'@codemirror/collab',
'@codemirror/commands',
'@codemirror/language',
'@codemirror/lint',
'@codemirror/search',
'@codemirror/state',
'@codemirror/view',
'@lezer/common',
'@lezer/highlight',
'@lezer/lr',
...builtins],
format: 'cjs',
watch: !prod,
target: 'es2018',
logLevel: "info",
sourcemap: prod ? false : 'inline',
treeShaking: true,
outfile: 'main.js',
}).catch(() => process.exit(1));

258
main.js Normal file
View file

@ -0,0 +1,258 @@
/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// main.ts
var main_exports = {};
__export(main_exports, {
default: () => MyPlugin,
retrieveStories: () => retrieveStories
});
module.exports = __toCommonJS(main_exports);
var import_obsidian3 = require("obsidian");
// src/mappers/storyMapper.ts
function mapStory(data) {
return {
id: data.id,
name: data.name.replace("/", " "),
points: data.estimate,
description: data.description,
type: data.story_type,
url: data.url,
state: data.current_state
};
}
function mapStories(data) {
return data.map((d) => mapStory(d));
}
// src/apiClient.ts
var import_obsidian = require("obsidian");
var TRACKER_URL = "https://www.pivotaltracker.com/services/v5";
function generateConfig(trackerToken, projectId, queryParam) {
return {
method: "GET",
url: `${TRACKER_URL}/projects/${projectId}/stories?${queryParam}`,
headers: {
"X-TrackerToken": trackerToken
},
contentType: "application/json"
};
}
async function getStories(trackerId, trackerToken, included) {
try {
const projectId = parseInt(trackerId);
const response = [];
if (included.includeStories) {
let unstartedStories = JSON.parse(await (0, import_obsidian.request)(generateConfig(trackerToken, projectId, "with_state=unstarted")));
let startedStories = JSON.parse(await (0, import_obsidian.request)(generateConfig(trackerToken, projectId, "with_state=started")));
response.push(...unstartedStories, ...startedStories);
}
if (included.includeChores) {
let unstartedChores = JSON.parse(await (0, import_obsidian.request)(generateConfig(trackerToken, projectId, "with_story_type=chore&with_state=unstarted")));
let startedChores = JSON.parse(await (0, import_obsidian.request)(generateConfig(trackerToken, projectId, "with_story_type=chore&with_state=started")));
response.push(...unstartedChores, ...startedChores);
}
if (included.includeBugs) {
let unstartedBugs = JSON.parse(await (0, import_obsidian.request)(generateConfig(trackerToken, projectId, "with_story_type=bug&with_state=unstarted")));
let startedBugs = JSON.parse(await (0, import_obsidian.request)(generateConfig(trackerToken, projectId, "with_story_type=bug&with_state=started")));
response.push(...unstartedBugs, ...startedBugs);
}
let stories = mapStories(response);
return stories.filter((story) => {
const onlyNonAccepted = story.state !== "accepted";
if (included.pointed) {
return onlyNonAccepted && typeof story.points === "number";
}
return onlyNonAccepted;
});
} catch (e) {
return Promise.reject(e.message);
}
}
// src/writeOutputToFile.ts
async function writeOutputToFile(folderPath, markdown, fileName) {
const modifiedFileName = fileName.replace("/", " ");
const readmePath = `${folderPath}/${modifiedFileName}.md`;
return this.app.vault.create(readmePath, markdown);
}
// src/generateMarkdown.ts
var import_obsidian2 = require("obsidian");
function storyToMarkdown(story) {
let currentStory = appendLine(`### ${story.name} [#${story.id}](${story.url})`, newLine("## Pivotal Tracker"));
if (story.type === "feature" && story.points) {
currentStory = appendLine(newLine("#### Points: " + story.points), currentStory);
}
if (story.description) {
currentStory = appendLine("### Description", currentStory);
currentStory = appendLine(story.description, currentStory);
}
currentStory = appendLine(generateTags(story.type), currentStory);
return currentStory;
}
function newLine(content) {
return `${content}\r
`;
}
function appendLine(content, destination) {
return newLine(`${destination + content}`);
}
function generateTags(type) {
return newLine("") + newLine("---") + newLine("tags") + newLine(" - pivotal-tracker") + newLine(" - " + type) + newLine("---");
}
function generateMarkdown(folderPath, stories) {
stories.forEach(async (feature) => {
await writeOutputToFile(folderPath, appendLine(storyToMarkdown(feature), ""), feature.name).then(() => {
new import_obsidian2.Notice(`${feature.name} Created`);
}).catch(() => {
});
});
new import_obsidian2.Notice("Stories Created");
}
// main.ts
var DEFAULT_SETTINGS = {
folderPath: "./stories",
includeBugs: true,
includeChores: true,
includeStories: true,
trackerUserId: "default",
trackerAppId: "default",
onlyPointedStories: false
};
var retrieveStories = async (settings) => {
const { trackerUserId, trackerAppId, folderPath, onlyPointedStories, includeStories, includeChores, includeBugs } = settings;
const inclusion = { includeStories, includeChores, includeBugs, pointed: onlyPointedStories };
const stories = await getStories(trackerAppId, trackerUserId, inclusion);
generateMarkdown(folderPath, stories);
};
var MyPlugin = class extends import_obsidian3.Plugin {
async onload() {
await this.loadSettings();
const ribbonIconEl = this.addRibbonIcon("book-open", "Pull Tracker Stories", (evt) => {
retrieveStories(this.settings).then(() => {
new import_obsidian3.Notice("Retrieving Stories");
}).catch((e) => {
new import_obsidian3.Notice(e + this.settings.trackerAppId + this.settings.trackerUserId);
});
});
ribbonIconEl.addClass("my-plugin-ribbon-class");
const statusBarItemEl = this.addStatusBarItem();
statusBarItemEl.setText("Status Bar Text");
this.addCommand({
id: "open-sample-modal-simple",
name: "Open sample modal (simple)",
callback: () => {
new SampleModal(this.app).open();
}
});
this.addCommand({
id: "sample-editor-command",
name: "Sample editor command",
editorCallback: (editor, view) => {
console.log(editor.getSelection());
editor.replaceSelection("Sample Editor Command");
}
});
this.addCommand({
id: "open-sample-modal-complex",
name: "Open sample modal (complex)",
checkCallback: (checking) => {
const markdownView = this.app.workspace.getActiveViewOfType(import_obsidian3.MarkdownView);
if (markdownView) {
if (!checking) {
new SampleModal(this.app).open();
}
return true;
}
}
});
this.addSettingTab(new TrackerIntegrationSettingTab(this.app, this));
this.registerDomEvent(document, "click", (evt) => {
console.log("click", evt);
});
this.registerInterval(window.setInterval(() => console.log("setInterval"), 5 * 60 * 1e3));
}
onunload() {
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
};
var SampleModal = class extends import_obsidian3.Modal {
constructor(app) {
super(app);
}
onOpen() {
const { contentEl } = this;
contentEl.setText("Woah!");
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
};
var TrackerIntegrationSettingTab = class extends import_obsidian3.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h2", { text: "Settings for Tracker Integration" });
new import_obsidian3.Setting(containerEl).setName("Tracker User Token").setDesc('To retrieve this, it can be found in your profile settings under "API KEY"').addText((text) => text.setPlaceholder("Enter your API Key").setValue(this.plugin.settings.trackerUserId).onChange(async (value) => {
this.plugin.settings.trackerUserId = value;
await this.plugin.saveSettings();
}));
new import_obsidian3.Setting(containerEl).setName("Tracker App ID").setDesc("This can be found in the project url bar https://www.pivotaltracker.com/n/projects/<your_project_id>").addText((text) => text.setPlaceholder("Enter your Apps Id").setValue(this.plugin.settings.trackerAppId).onChange(async (value) => {
this.plugin.settings.trackerAppId = value;
await this.plugin.saveSettings();
}));
new import_obsidian3.Setting(containerEl).setName("Folder Path").setDesc("This is the folder path to dump the stories it, currently they do NOT get created automatically.").addText((text) => text.setPlaceholder("./Path/Name").setValue(this.plugin.settings.folderPath).onChange(async (value) => {
this.plugin.settings.folderPath = value;
await this.plugin.saveSettings();
}));
new import_obsidian3.Setting(containerEl).setName("Only Pointed Stories").setDesc("Should only grab pointed stories if it is on.").addToggle((ev) => ev.setValue(this.plugin.settings.onlyPointedStories).onChange(async (value) => {
this.plugin.settings.onlyPointedStories = value;
await this.plugin.saveSettings();
}));
new import_obsidian3.Setting(containerEl).setName("Include Stories").setDesc("Should include stories if it is on.").addToggle((ev) => ev.setValue(this.plugin.settings.includeStories).onChange(async (value) => {
this.plugin.settings.includeStories = value;
await this.plugin.saveSettings();
}));
new import_obsidian3.Setting(containerEl).setName("Include Chores").setDesc("Should include chores if it is on.").addToggle((ev) => ev.setValue(this.plugin.settings.includeChores).onChange(async (value) => {
this.plugin.settings.includeChores = value;
await this.plugin.saveSettings();
}));
new import_obsidian3.Setting(containerEl).setName("Include Bugs").setDesc("Should include bugs if it is on.").addToggle((ev) => ev.setValue(this.plugin.settings.includeBugs).onChange(async (value) => {
this.plugin.settings.includeBugs = value;
await this.plugin.saveSettings();
}));
}
};

223
main.ts Normal file
View file

@ -0,0 +1,223 @@
import {addIcon, App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting} from 'obsidian';
import getStories from "./src/apiClient";
import generateMarkdown from "./src/generateMarkdown";
// Remember to rename these classes and interfaces!
interface MyPluginSettings {
folderPath: string;
trackerUserId: string;
trackerAppId: string;
onlyPointedStories: boolean;
includeChores: boolean;
includeBugs: boolean;
includeStories: boolean;
}
const DEFAULT_SETTINGS: MyPluginSettings = {
folderPath: './stories',
includeBugs: true,
includeChores: true,
includeStories: true,
trackerUserId: 'default',
trackerAppId: 'default',
onlyPointedStories: false
}
export const retrieveStories = async (settings: MyPluginSettings) => {
const {trackerUserId, trackerAppId, folderPath, onlyPointedStories, includeStories, includeChores, includeBugs} = settings;
const inclusion = {includeStories, includeChores, includeBugs, pointed: onlyPointedStories};
const stories = await getStories(trackerAppId, trackerUserId, inclusion);
generateMarkdown(folderPath, stories);
};
export default class MyPlugin extends Plugin {
settings: MyPluginSettings;
async onload() {
await this.loadSettings();
// This creates an icon in the left ribbon.
const ribbonIconEl = this.addRibbonIcon('book-open', 'Pull Tracker Stories', (evt: MouseEvent) => {
// Called when the user clicks the icon.
retrieveStories(this.settings).then(()=> {
new Notice('Retrieving Stories');
}).catch((e)=>{
new Notice(e + this.settings.trackerAppId + this.settings.trackerUserId);
});
});
// Perform additional things with the ribbon
ribbonIconEl.addClass('my-plugin-ribbon-class');
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
const statusBarItemEl = this.addStatusBarItem();
statusBarItemEl.setText('Status Bar Text');
// This adds a simple command that can be triggered anywhere
this.addCommand({
id: 'open-sample-modal-simple',
name: 'Open sample modal (simple)',
callback: () => {
new SampleModal(this.app).open();
},
});
// This adds an editor command that can perform some operation on the current editor instance
this.addCommand({
id: 'sample-editor-command',
name: 'Sample editor command',
editorCallback: (editor: Editor, view: MarkdownView) => {
console.log(editor.getSelection());
editor.replaceSelection('Sample Editor Command');
}
});
// This adds a complex command that can check whether the current state of the app allows execution of the command
this.addCommand({
id: 'open-sample-modal-complex',
name: 'Open sample modal (complex)',
checkCallback: (checking: boolean) => {
// Conditions to check
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (markdownView) {
// If checking is true, we're simply "checking" if the command can be run.
// If checking is false, then we want to actually perform the operation.
if (!checking) {
new SampleModal(this.app).open();
}
// This command will only show up in Command Palette when the check function returns true
return true;
}
}
});
// This adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new TrackerIntegrationSettingTab(this.app, this));
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
// Using this function will automatically remove the event listener when this plugin is disabled.
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
console.log('click', evt);
});
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
}
onunload() {
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
}
class SampleModal extends Modal {
constructor(app: App) {
super(app);
}
onOpen() {
const {contentEl} = this;
contentEl.setText('Woah!');
}
onClose() {
const {contentEl} = this;
contentEl.empty();
}
}
class TrackerIntegrationSettingTab extends PluginSettingTab {
plugin: MyPlugin;
constructor(app: App, plugin: MyPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const {containerEl} = this;
containerEl.empty();
containerEl.createEl('h2', {text: 'Settings for Tracker Integration'});
new Setting(containerEl)
.setName('Tracker User Token')
.setDesc('To retrieve this, it can be found in your profile settings under "API KEY"')
.addText(text => text
.setPlaceholder('Enter your API Key')
.setValue(this.plugin.settings.trackerUserId)
.onChange(async (value) => {
this.plugin.settings.trackerUserId = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Tracker App ID')
.setDesc('This can be found in the project url bar https://www.pivotaltracker.com/n/projects/<your_project_id>')
.addText(text => text
.setPlaceholder('Enter your Apps Id')
.setValue(this.plugin.settings.trackerAppId)
.onChange(async (value) => {
this.plugin.settings.trackerAppId = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Folder Path')
.setDesc('This is the folder path to dump the stories it, currently they do NOT get created automatically.')
.addText(text => text
.setPlaceholder('./Path/Name')
.setValue(this.plugin.settings.folderPath)
.onChange(async (value) => {
this.plugin.settings.folderPath = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Only Pointed Stories')
.setDesc('Should only grab pointed stories if it is on.')
.addToggle(ev => ev
.setValue(this.plugin.settings.onlyPointedStories)
.onChange(async (value) => {
this.plugin.settings.onlyPointedStories = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Include Stories')
.setDesc('Should include stories if it is on.')
.addToggle(ev => ev
.setValue(this.plugin.settings.includeStories)
.onChange(async (value) => {
this.plugin.settings.includeStories = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Include Chores')
.setDesc('Should include chores if it is on.')
.addToggle(ev => ev
.setValue(this.plugin.settings.includeChores)
.onChange(async (value) => {
this.plugin.settings.includeChores = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Include Bugs')
.setDesc('Should include bugs if it is on.')
.addToggle(ev => ev
.setValue(this.plugin.settings.includeBugs)
.onChange(async (value) => {
this.plugin.settings.includeBugs = value;
await this.plugin.saveSettings();
}))
}
}

11
manifest.json Normal file
View file

@ -0,0 +1,11 @@
{
"id": "obsidian-pivotal-tracker-integration-plugin",
"name": "Pivotal Tracker Integration",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "This is an unofficial pivotal tracker integration plugin for Obsidian. This plugin allows the user to pull stories, chores, bugs from their pivotal counterpart.",
"author": "Obsidian",
"authorUrl": "https://github.com/JonnyDeates",
"fundingUrl": "https://www.buymeacoffee.com/jondeates",
"isDesktopOnly": false
}

3739
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

24
package.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "obsidian-pivotal-tracker-integration-plugin",
"version": "1.0.0",
"description": "This is an unofficial pivotal tracker integration plugin for Obsidian. (https://obsidian.md)",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.14.47",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
}
}

64
src/apiClient.ts Normal file
View file

@ -0,0 +1,64 @@
import mapStories from './mappers/storyMapper';
import Story from './types/story';
import {request} from "obsidian";
const TRACKER_URL = 'https://www.pivotaltracker.com/services/v5';
function generateConfig(
trackerToken: string,
projectId: number,
queryParam: string
): { headers: { "X-TrackerToken": string }; method: string; contentType: string; url: string } {
return {
method: 'GET',
url: `${TRACKER_URL}/projects/${projectId}/stories?${queryParam}`,
headers: {
'X-TrackerToken': trackerToken,
},
contentType: "application/json",
};
}
export default async function getStories(
trackerId: string,
trackerToken: string,
included: {includeStories: boolean, includeChores:boolean, includeBugs:boolean, pointed: boolean}
): Promise<Story[]> {
try {
const projectId = parseInt(trackerId);
const response: any[] = [];
if(included.includeStories) {
let unstartedStories = JSON.parse(await request(generateConfig(trackerToken, projectId, 'with_state=unstarted')));
let startedStories = JSON.parse(await request(generateConfig(trackerToken, projectId, 'with_state=started')));
response.push(...unstartedStories, ...startedStories);
}
if(included.includeChores) {
let unstartedChores = JSON.parse(await request(generateConfig(trackerToken, projectId, 'with_story_type=chore&with_state=unstarted')));
let startedChores = JSON.parse(await request(generateConfig(trackerToken, projectId, 'with_story_type=chore&with_state=started')));
response.push(...unstartedChores, ...startedChores);
}
if(included.includeBugs) {
let unstartedBugs = JSON.parse(await request(generateConfig(trackerToken, projectId, 'with_story_type=bug&with_state=unstarted')));
let startedBugs = JSON.parse(await request(generateConfig(trackerToken, projectId, 'with_story_type=bug&with_state=started')));
response.push(...unstartedBugs, ...startedBugs);
}
let stories = mapStories(response as any[]);
return stories.filter((story) => {
const onlyNonAccepted = story.state !== 'accepted';
if(included.pointed){
return onlyNonAccepted && typeof story.points === 'number'
}
return onlyNonAccepted
});
} catch (e: any) {
return Promise.reject(e.message);
}
}

49
src/generateMarkdown.ts Normal file
View file

@ -0,0 +1,49 @@
import Story from './types/story';
import writeOutputToFile from './writeOutputToFile';
import {Notice} from "obsidian";
function storyToMarkdown(story: Story): string {
let currentStory = appendLine(`### ${story.name} [#${story.id}](${story.url})`, newLine("## Pivotal Tracker"),);
if(story.type === "feature" && story.points ){
currentStory = appendLine(newLine("#### Points: "+story.points), currentStory)
}
if (story.description) {
currentStory = appendLine("### Description", currentStory);
currentStory = appendLine(story.description, currentStory);
}
currentStory = appendLine(generateTags(story.type), currentStory);
return currentStory;
}
function newLine(content: string) {
return `${content}\r\n`
}
function appendLine(content: string, destination: string): string {
return newLine(`${destination + content}`);
}
function generateTags(type: "feature" | "bug" | "chore"){
return newLine("") + newLine("---") + newLine("tags") + newLine(" - pivotal-tracker") + newLine(" - " + type) +newLine("---")
}
export default function generateMarkdown(folderPath: string, stories: Story[]) {
stories.forEach(async (feature) => {
await writeOutputToFile(folderPath, appendLine(storyToMarkdown(feature), ''), feature.name)
.then(() => {
new Notice(`${feature.name} Created`)
})
.catch(()=>{});
});
new Notice('Stories Created')
}

View file

@ -0,0 +1,17 @@
import Story from '../types/story';
function mapStory(data: any): Story {
return {
id: data.id,
name: data.name.replace("/", " "),
points: data.estimate,
description: data.description,
type: data.story_type,
url: data.url,
state: data.current_state,
};
}
export default function mapStories(data: any[]): Story[] {
return data.map((d) => mapStory(d));
}

10
src/types/story.ts Normal file
View file

@ -0,0 +1,10 @@
export default interface Story {
id: number;
name: string;
points: number | undefined;
description: string;
type: 'feature' | 'bug' | 'chore';
url: string;
state: 'accepted' | 'delivered' | 'started' | 'unstarted';
}

10
src/writeOutputToFile.ts Normal file
View file

@ -0,0 +1,10 @@
export default async function writeOutputToFile(
folderPath: string,
markdown: string,
fileName: string,
): Promise<any> {
const modifiedFileName = fileName.replace("/", " ");
const readmePath = `${folderPath}/${modifiedFileName}.md`;
return this.app.vault.create(readmePath, markdown);
}

24
tsconfig.json Normal file
View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
}

14
version-bump.mjs Normal file
View file

@ -0,0 +1,14 @@
import { readFileSync, writeFileSync } from "fs";
const targetVersion = process.env.npm_package_version;
// read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion } = manifest;
manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
// update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
versions[targetVersion] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));

3
versions.json Normal file
View file

@ -0,0 +1,3 @@
{
"1.0.0": "0.15.0"
}