Initial implementation

This commit is contained in:
Atharva Sawant
2024-02-15 16:47:23 +05:30
commit ffb5eeddf2
8439 changed files with 2230738 additions and 0 deletions

21
src/App.vue Normal file
View File

@@ -0,0 +1,21 @@
<template>
<div id="app">
<header class="bg-blue-600 text-white p-4">
<h1 class="text-2xl font-bold">OrgTree</h1>
<p class="text-blue-100">Visualize and edit your org-mode files</p>
</header>
<main class="min-h-screen bg-gray-50">
<RouterView />
</main>
</div>
</template>
<script setup lang="ts">
import { RouterView } from 'vue-router'
</script>
<style>
#app {
font-family: system-ui, -apple-system, sans-serif;
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<div class="border-b border-gray-200 bg-white">
<nav class="flex space-x-1 overflow-x-auto">
<button
v-for="file in files"
:key="file.id"
@click="$emit('file-selected', file)"
:class="[
'px-4 py-2 text-sm font-medium whitespace-nowrap',
file.id === activeFile?.id
? 'border-b-2 border-blue-500 text-blue-600'
: 'text-gray-500 hover:text-gray-700'
]"
>
{{ file.name }}
</button>
</nav>
</div>
</template>
<script setup lang="ts">
import type { OrgFile } from '@/types/org'
defineProps<{
files: OrgFile[]
activeFile: OrgFile | null
}>()
defineEmits<{
'file-selected': [file: OrgFile]
}>()
</script>

View File

@@ -0,0 +1,91 @@
<template>
<div class="upload-area">
<div
class="border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-blue-400 transition-colors"
:class="{ 'border-blue-400 bg-blue-50': isDragOver }"
@dragover.prevent="isDragOver = true"
@dragleave.prevent="isDragOver = false"
@drop.prevent="handleDrop"
>
<div class="space-y-4">
<div class="text-4xl text-gray-400">📁</div>
<div class="space-y-3">
<p class="text-lg text-gray-600">Drop org files or folders here</p>
<p class="text-sm text-gray-400">or</p>
<div class="flex gap-3 justify-center">
<label class="inline-block">
<input
type="file"
multiple
accept=".org"
@change="handleFileSelect"
class="hidden"
/>
<span class="bg-blue-600 text-white px-4 py-2 rounded cursor-pointer hover:bg-blue-700">
Browse Files
</span>
</label>
<button
@click="loadExampleFiles"
class="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700"
>
Use Example Files
</button>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { parseOrgFile } from '@/utils/orgParser'
import { exampleFiles } from '@/data/exampleFiles'
import type { OrgFile } from '@/types/org'
const emit = defineEmits<{
'files-selected': [files: OrgFile[]]
}>()
const isDragOver = ref(false)
async function handleDrop(event: DragEvent) {
isDragOver.value = false
const files = Array.from(event.dataTransfer?.files || [])
await processFiles(files)
}
async function handleFileSelect(event: Event) {
const target = event.target as HTMLInputElement
const files = Array.from(target.files || [])
await processFiles(files)
}
async function processFiles(files: File[]) {
const orgFiles: OrgFile[] = []
for (const file of files) {
if (file.name.endsWith('.org')) {
const content = await file.text()
const parsed = parseOrgFile(file.name, content)
orgFiles.push(parsed)
}
}
if (orgFiles.length > 0) {
emit('files-selected', orgFiles)
}
}
function loadExampleFiles() {
const orgFiles: OrgFile[] = []
for (const [filename, content] of Object.entries(exampleFiles)) {
const parsed = parseOrgFile(filename, content)
orgFiles.push(parsed)
}
emit('files-selected', orgFiles)
}
</script>

View File

@@ -0,0 +1,112 @@
<template>
<div class="org-node">
<div class="flex items-start space-x-3 p-2 hover:bg-gray-50 rounded">
<button
v-if="node.children.length > 0"
@click="toggleExpanded"
class="mt-1 text-gray-400 hover:text-gray-600"
>
{{ isExpanded ? '▼' : '▶' }}
</button>
<div class="flex-1">
<div class="flex items-center space-x-2 mb-1">
<span class="text-gray-400">{{ '*'.repeat(node.level) }}</span>
<input
v-if="isEditing"
v-model="editTitle"
@blur="saveTitle"
@keyup.enter="saveTitle"
class="font-semibold bg-transparent border-b border-gray-300 focus:outline-none focus:border-blue-500"
/>
<h3
v-else
@dblclick="startEditing"
class="font-semibold cursor-pointer hover:text-blue-600"
>
{{ node.title }}
</h3>
<div v-if="node.tags" class="flex space-x-1">
<span
v-for="tag in node.tags"
:key="tag"
class="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded"
>
{{ tag }}
</span>
</div>
</div>
<div v-if="node.content" class="text-sm text-gray-600 ml-4">
<textarea
v-if="isEditingContent"
v-model="editContent"
@blur="saveContent"
class="w-full bg-transparent border border-gray-300 rounded p-2 focus:outline-none focus:border-blue-500"
rows="3"
/>
<pre
v-else
@dblclick="startEditingContent"
class="cursor-pointer hover:bg-gray-100 p-1 rounded"
>{{ node.content }}</pre>
</div>
</div>
</div>
<div v-if="isExpanded && node.children.length > 0" class="ml-6 border-l-2 border-gray-200">
<OrgNode
v-for="child in node.children"
:key="child.id"
:node="child"
@update="$emit('update', $event)"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { OrgNode as IOrgNode } from '@/types/org'
const props = defineProps<{
node: IOrgNode
}>()
const emit = defineEmits<{
update: [node: IOrgNode]
}>()
const isExpanded = ref(true)
const isEditing = ref(false)
const isEditingContent = ref(false)
const editTitle = ref(props.node.title)
const editContent = ref(props.node.content)
function toggleExpanded() {
isExpanded.value = !isExpanded.value
}
function startEditing() {
isEditing.value = true
editTitle.value = props.node.title
}
function saveTitle() {
isEditing.value = false
if (editTitle.value !== props.node.title) {
emit('update', { ...props.node, title: editTitle.value })
}
}
function startEditingContent() {
isEditingContent.value = true
editContent.value = props.node.content
}
function saveContent() {
isEditingContent.value = false
if (editContent.value !== props.node.content) {
emit('update', { ...props.node, content: editContent.value })
}
}
</script>

View File

@@ -0,0 +1,48 @@
<template>
<div class="bg-white rounded-lg shadow p-6">
<div class="flex justify-between items-center mb-6">
<h2 class="text-xl font-semibold">{{ file.name }}</h2>
<button
@click="exportFile"
class="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700"
>
Export
</button>
</div>
<div class="org-tree">
<OrgNode
v-for="node in file.nodes"
:key="node.id"
:node="node"
@update="updateNode"
/>
</div>
</div>
</template>
<script setup lang="ts">
import OrgNode from './OrgNode.vue'
import { generateOrgContent } from '@/utils/orgGenerator'
import type { OrgFile, OrgNode as IOrgNode } from '@/types/org'
const props = defineProps<{
file: OrgFile
}>()
function updateNode(updatedNode: IOrgNode) {
// TODO: Implement node update logic
console.log('Node updated:', updatedNode)
}
function exportFile() {
const content = generateOrgContent(props.file)
const blob = new Blob([content], { type: 'text/plain' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = props.file.name
a.click()
URL.revokeObjectURL(url)
}
</script>

277
src/data/exampleFiles.ts Normal file
View File

@@ -0,0 +1,277 @@
export const exampleFiles = {
'orgtree-project.org': `#+TITLE: OrgTree Project
#+AUTHOR: Development Team
#+DATE: 2024-01-01
#+TAGS: project development vue
* Project Overview
OrgTree is a web-based interface for visualizing org-mode files as interactive trees.
** Goals
- [ ] Create intuitive tree visualization
- [X] Support file import/export
- [ ] Enable real-time editing
** Technical Stack
*** Frontend
- Vue.js 3 with TypeScript
- Tailwind CSS for styling
- Vite for build tooling
*** Backend (Future)
- Node.js with Express
- Database for persistence
* Development Phases
** Phase 1: Core Implementation :DONE:
Basic functionality is working.
*** File Parser
The parser handles standard org-mode syntax including:
- Headings with multiple levels
- TODO states
- Tags and properties
- Content blocks
** Phase 2: Advanced Features :CURRENT:
*** Drag and Drop
Enable users to reorganize tree structure visually.
*** Node Management
- Add new nodes
- Delete existing nodes
- Modify node properties
** Phase 3: Polish :FUTURE:
*** Performance
Optimize for large org files with hundreds of nodes.
*** Accessibility
Ensure keyboard navigation and screen reader support.
* Notes
** Implementation Details
The current parsing approach uses regex-based extraction which works well for standard org-mode files.
** Known Issues
- Large files may cause performance problems
- Some advanced org-mode features not yet supported`,
'daily-notes.org': `#+TITLE: Daily Notes
#+FILETAGS: personal journal
* 2024-01-15 Monday
** Morning Routine :habit:
- [X] Exercise for 30 minutes
- [X] Read for 20 minutes
- [ ] Meditate for 10 minutes
** Work Tasks
*** OrgTree Development
**** Code Review
Review the parsing logic for edge cases.
**** Bug Fixes
- Fix tag parsing issue
- Update export functionality
*** Meetings
**** 10:00 AM - Team Standup
Discuss progress on current sprint.
**** 2:00 PM - Project Review
Present OrgTree demo to stakeholders.
** Evening Reflection
Today was productive. The OrgTree project is making good progress.
*** What went well
- Completed parsing implementation
- Fixed several UI bugs
- Good feedback from team
*** Areas for improvement
- Need to focus more on testing
- Documentation needs updates
* 2024-01-16 Tuesday
** Goals for Today
- [ ] Implement drag-and-drop functionality
- [ ] Create more comprehensive test files
- [ ] Update project documentation
** Learning
*** Org Mode Features
Discovered some advanced org-mode syntax:
- PROPERTIES drawers
- Custom TODO keywords
- Scheduling and deadlines
** Random Thoughts
The tree visualization really helps see the structure of complex org files. This tool could be useful for many people who work with large documentation sets.`,
'reading-list.org': `#+TITLE: Reading List
#+FILETAGS: books reading personal
* Currently Reading :READING:
** "The Pragmatic Programmer" by Andy Hunt :programming:
*** Progress
- [X] Chapter 1: A Pragmatic Philosophy
- [X] Chapter 2: A Pragmatic Approach
- [ ] Chapter 3: The Basic Tools
- [ ] Chapter 4: Pragmatic Paranoia
*** Key Takeaways
**** DRY Principle
Don't Repeat Yourself - avoid duplication in code and knowledge.
**** Orthogonality
Design systems where components don't depend unnecessarily on each other.
*** Notes
This book emphasizes the importance of craftsmanship in software development.
* To Read :TODO:
** Technical Books
*** "Clean Architecture" by Robert Martin :programming:
**** Priority
High - recommended by multiple colleagues
**** Notes
Focuses on software architecture principles and design patterns.
*** "Designing Data-Intensive Applications" by Martin Kleppmann :systems:
**** Priority
Medium - good for understanding distributed systems
** Fiction
*** "The Three-Body Problem" by Liu Cixin :scifi:
**** Priority
High - won Hugo Award
**** Description
Hard science fiction exploring first contact with alien civilization.
* Completed :DONE:
** "Atomic Habits" by James Clear :productivity:
*** Rating
5/5 - Excellent practical advice on habit formation
*** Key Concepts
**** 1% Better Every Day
Small improvements compound over time.
**** Habit Stacking
Link new habits to existing routines.
**** Environment Design
Make good habits easy and bad habits hard.
*** Applied Techniques
- Morning routine checklist
- Reading time after coffee
- Phone in separate room while working
* Reading Goals
** 2024 Targets
- [ ] 24 books total (2 per month)
- [ ] 60% technical, 40% non-technical
- [ ] At least 4 books on system design
- [ ] 2 biographies of technology leaders`,
'web-development.org': `#+TITLE: Web Development Reference
#+DESCRIPTION: Quick reference for web development concepts
#+KEYWORDS: javascript vue css html
* Frontend Technologies
** JavaScript
*** ES6+ Features
**** Arrow Functions
Arrow functions provide a concise way to write function expressions:
\`\`\`javascript
const add = (a, b) => a + b;
const users = data.map(user => user.name);
\`\`\`
**** Destructuring
Extract values from arrays and objects:
\`\`\`javascript
const {name, age} = person;
const [first, second] = array;
\`\`\`
*** Async Programming
**** Promises
Handle asynchronous operations cleanly.
**** Async/Await
Modern syntax for working with promises.
** Vue.js
*** Composition API
The modern way to write Vue components.
**** Setup Function
The setup function is the entry point for Composition API.
*** Reactivity
**** Ref vs Reactive
- ref() for primitive values
- reactive() for objects
** CSS
*** Flexbox
**** Container Properties
- display: flex
- flex-direction
- justify-content
- align-items
**** Item Properties
- flex-grow
- flex-shrink
- flex-basis
*** Grid
Modern layout system for complex designs.
* Backend Technologies
** Node.js
*** Express Framework
Minimal web framework for Node.js.
**** Basic Server
Simple Express server setup.
** Databases
*** SQL
**** Common Queries
***** SELECT
Basic query to retrieve data.
***** JOIN
Combine data from multiple tables.
*** NoSQL
**** MongoDB
Document-based database with flexible schema.
* Tools and Workflow
** Development Environment
*** VS Code Extensions
- Vetur for Vue development
- Prettier for code formatting
- ESLint for code quality
** Version Control
*** Git Commands
**** Basic Workflow
Standard git workflow for daily development.
**** Branching
Create and manage feature branches.
** Build Tools
*** Vite
Modern build tool with fast HMR (Hot Module Replacement).`
}

20
src/main.ts Normal file
View File

@@ -0,0 +1,20 @@
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import HomeView from './views/HomeView.vue'
import './style.css'
const routes = [
{ path: '/', name: 'home', component: HomeView }
]
const router = createRouter({
history: createWebHistory(),
routes
})
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.mount('#app')

3
src/style.css Normal file
View File

@@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

25
src/types/org.ts Normal file
View File

@@ -0,0 +1,25 @@
export interface OrgNode {
id: string
level: number
title: string
content: string
children: OrgNode[]
properties?: Record<string, string>
tags?: string[]
}
export interface OrgFile {
id: string
name: string
path: string
content: string
nodes: OrgNode[]
metadata?: Record<string, string>
}
export interface FileSystemEntry {
name: string
path: string
isDirectory: boolean
children?: FileSystemEntry[]
}

47
src/utils/orgGenerator.ts Normal file
View File

@@ -0,0 +1,47 @@
import type { OrgFile, OrgNode } from '@/types/org'
export function generateOrgContent(file: OrgFile): string {
let content = ''
// Add metadata
if (file.metadata) {
for (const [key, value] of Object.entries(file.metadata)) {
content += `#+${key}: ${value}\n`
}
content += '\n'
}
// Add nodes
for (const node of file.nodes) {
content += generateNodeContent(node)
}
return content.trim()
}
function generateNodeContent(node: OrgNode): string {
let content = ''
// Generate heading
const stars = '*'.repeat(node.level)
let heading = `${stars} ${node.title}`
// Add tags
if (node.tags && node.tags.length > 0) {
heading += ` :${node.tags.join(':')}:`
}
content += heading + '\n'
// Add content
if (node.content) {
content += node.content + '\n'
}
// Add children
for (const child of node.children) {
content += generateNodeContent(child)
}
return content
}

105
src/utils/orgParser.ts Normal file
View File

@@ -0,0 +1,105 @@
import type { OrgFile, OrgNode } from '@/types/org'
export function parseOrgFile(filename: string, content: string): OrgFile {
const lines = content.split('\n')
const nodes: OrgNode[] = []
const metadata: Record<string, string> = {}
let currentNode: Partial<OrgNode> | null = null
let nodeCounter = 0
for (let i = 0; i < lines.length; i++) {
const line = lines[i]
// Parse metadata
if (line.startsWith('#+')) {
const [key, ...valueParts] = line.slice(2).split(':')
if (valueParts.length > 0) {
metadata[key.trim()] = valueParts.join(':').trim()
}
continue
}
// Parse headings
const headingMatch = line.match(/^(\*+)\s+(.+)$/)
if (headingMatch) {
// Save previous node
if (currentNode) {
nodes.push(finalizeNode(currentNode))
}
const level = headingMatch[1].length
const titleWithTags = headingMatch[2]
// Extract tags
const tagMatch = titleWithTags.match(/^(.+?)\s+:([\w:]+):$/)
const title = tagMatch ? tagMatch[1].trim() : titleWithTags.trim()
const tags = tagMatch ? tagMatch[2].split(':').filter(Boolean) : []
currentNode = {
id: `node-${nodeCounter++}`,
level,
title,
content: '',
children: [],
tags
}
continue
}
// Add content to current node
if (currentNode && line.trim()) {
currentNode.content = (currentNode.content || '') + line + '\n'
}
}
// Save last node
if (currentNode) {
nodes.push(finalizeNode(currentNode))
}
// Build hierarchy
const hierarchicalNodes = buildHierarchy(nodes)
return {
id: `file-${Date.now()}`,
name: filename,
path: filename,
content,
nodes: hierarchicalNodes,
metadata
}
}
function finalizeNode(partial: Partial<OrgNode>): OrgNode {
return {
id: partial.id!,
level: partial.level!,
title: partial.title!,
content: (partial.content || '').trim(),
children: [],
tags: partial.tags
}
}
function buildHierarchy(flatNodes: OrgNode[]): OrgNode[] {
const result: OrgNode[] = []
const stack: OrgNode[] = []
for (const node of flatNodes) {
// Pop stack until we find appropriate parent
while (stack.length > 0 && stack[stack.length - 1].level >= node.level) {
stack.pop()
}
if (stack.length === 0) {
result.push(node)
} else {
stack[stack.length - 1].children.push(node)
}
stack.push(node)
}
return result
}

37
src/views/HomeView.vue Normal file
View File

@@ -0,0 +1,37 @@
<template>
<div class="container mx-auto p-6">
<div class="max-w-4xl mx-auto">
<FileUploader @files-selected="handleFiles" />
<div v-if="files.length" class="mt-8">
<FileTabs
:files="files"
:active-file="activeFile"
@file-selected="setActiveFile"
/>
<OrgTreeView v-if="activeFile" :file="activeFile" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import FileUploader from '@/components/FileManager/FileUploader.vue'
import FileTabs from '@/components/FileManager/FileTabs.vue'
import OrgTreeView from '@/components/OrgTree/OrgTreeView.vue'
import type { OrgFile } from '@/types/org'
const files = ref<OrgFile[]>([])
const activeFile = ref<OrgFile | null>(null)
function handleFiles(uploadedFiles: OrgFile[]) {
files.value = uploadedFiles
if (uploadedFiles.length > 0) {
activeFile.value = uploadedFiles[0]
}
}
function setActiveFile(file: OrgFile) {
activeFile.value = file
}
</script>