~jameschip/zettelkasten

ad51b57556a23a3b1c3252dc55d1c54aa8909d47 — James Henderson a month ago master
Add zettelkasten project files.
A  => LICENSE +21 -0
@@ 1,21 @@
MIT License

Copyright (c) 2020 James Henderson

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

A  => README.md +49 -0
@@ 1,49 @@
# Zettelkasten (zk)
This is an implementation of a zettelkasten (German, slip box) journaling method for Linux. The basic idea of a Zettelkasten is that short ideas, or notes, are made on numbered sips of paper kept in boxes. These notes have the following properties in this implementation:

* A note has a title. 
* A note has a short idea or bit of information on it. 
* A note can have a number of tags associated with it that group them into contexts. 
* A note can also have other notes linked to it by referencing other notes numbers. 
* A link to another note has a context associated with it, this is the reason why these two notes are linked. 

One of the most important parts here is the context given to the links between notes. This context is more useful than just providing a title, it does more than tell you which note is linked; it tells you WHY! This extra context seems insignificant on paper but makes a huge difference in practice.  
  
Assume for instance we have 2 entries in our Zettelkasten with the titles "Ferns" and "Spores". If "Ferns" has a link to "Spores" and just the title of the entries is used then we have little idea of how spores are related to ferns. If however instead we have a link to the "Spores" note with the context "Ferns reproduce using spores" we now know why the notes are related.

## Purposeful limits.
There are limits imposed by design.

* A notes content may only be 500 characters. Notes should be short and punchy, you are not writing an essay per entry. Break longer things into multiple notes. 
* You may never edit a notes content, If you need to provide corrections then create a new note and link them. This will not only teach you about the topic; but also the way you learn. 
* You may never remove a note from the Zettelkasten. There is no point to removing the past. 

## How?
Pull this repository and run the make file. The built binary will be ```/build/zk```. Move this to somewhere in your path and use from the command line. More complete instructions on how to use the software will be available somewhere.

## Where?
The entries in created by zk will be in a folder in ```~/Zettelkasten```. All of the entries will be in an extension less numbed text file counting from 0 upwards by one every time  you create a new entry. 

## What format?
It was important from the offset that the entries this Zettelkasten are in plain text and readable from outside the application itself. This also means that, providing it uses the same format, other software would be able to interact with your entries in a meaningful way.  
Entries have a header section that is used to define things like tags, links to other entries and the title. Anything outside of this header material is considered the content of entry itself.  
Header sections begin and end with the string ```---\n``` and each item is on its own line. The first character of the line indicates what data that line holds.

* '>' The title
* '#' - A tag
* '~' - A link to another entry.

An example file is:  
```  
---
> The title of the note
# tag_has_no_spaces
# another_tag
~ {10} : This links to entry 10 with this context text.
~ {13} : and this links to 13 with this context text.
---
This is the content of the entry!
```
 
## Why?
I built this for me. This is the note taking software I wanted. I share it here in case others may find it useful and so other may learn form (my mistakes in) the code.

A  => entry.c +103 -0
@@ 1,103 @@

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "entry.h"
#include "string_helpers.h"

entry * entry_init( void ) 
{
    entry * e = malloc( sizeof( entry ) );
 
    e->entry_number = 0;
    e->tag_count = 0;
    e->link_count = 0;

    e->title = malloc( 1 );
    e->title[0] = '\0';
    e->content = malloc( 1 );
    e->content[0] = '\0';

    return e;
}

void entry_add_link( entry * e, uint64_t n, char * l ) {
    char * tmp = calloc(100,  sizeof( char ) );
    sprintf( tmp, "{%lu} : %s", n, l );
    e->links[e->link_count++] = strdup( tmp );
    free( tmp );
}

void entry_add_tags( entry * e, char * tags ) {
    int index, ch_p = 0; 
    char * tag = calloc( 50, sizeof( char ) );

    for (index = 0; index < strlen( tags ); index++ ) {
        if ( tags[index] == ' ' || tags[index] == '\0' ) {
            //add tag here
            if (ch_p == 0) continue;
            tag[ch_p] = '\0';
            entry_add_tag( e, tag );
            ch_p = 0;   
        } else {
            tag[ch_p++] = tags[index];
        }
    }
}

void entry_set_title( entry * e, char * t )
{
    remove_trailing_white( t );
    free(e->title);
    e->title = strdup( t );
}

void entry_set_content( entry *  e, char * c )
{    
    remove_trailing_white( c );
    free(e->content);
    e->content = strdup( c );
}

void entry_add_tag( entry * e, char * c )
{
    remove_trailing_white( c );
    e->tags[e->tag_count] = strdup(c);
    e->tag_count++;
}

void entry_free( entry * e ) 
{

    free(e->title);
    free(e->content);
    int i;
    
    if ( e->tag_count > 0 ) {
        for ( i = 0; i < e->tag_count; i++ )
        {
            free(e->tags[i]);
        }
    }

    if ( e->link_count > 0 ) {
        for ( i = 0; i < e->link_count; i++ )
        {
            free( e->links[i] );
        }
    }
    
    free(e);

}

uint8_t entry_has_tag( entry * e, const char * tag ) {
    for ( int i = 0; i < e->tag_count; i++ ) {
        if ( strcmp( e->tags[i], tag ) == 0 ) {
            return 1;
        }
    }

    return 0;
}

A  => entry.h +71 -0
@@ 1,71 @@

#ifndef ENTRY_H
#define ENTRY_H

#include <stdint.h>

/**
 * This is an entry in the journal.
 * 
 * entry_number -> the number of the entry.
 * tag_count    -> the number of tags that are associated with this entry.
 * link_count   -> the number of links this entry has.
 * title        -> the title of this entry.
 * content      -> the meanninfull text this entry has.
 * tags[]       -> the tags themselves.
 * entry_links  -> the links themselves.
 */
typedef struct entry {
    uint64_t entry_number;
    uint8_t tag_count;
    uint8_t link_count;
    char * title;
    char * content;
    char * tags[50];
    char * links[50];
} entry;


/**
 * Init an empty entry in the journal.
 * 
 */
entry * entry_init( void );

/**
 * Create and add a new link to a journal entry.
 *
 */
void entry_add_link( entry * e, uint64_t n, char * l );

/**
 * Set the content of the journal entry.
 * 
 */
void entry_set_content( entry * e, char * c );

/**
 * Set the title of the Journal entry.
 */
void entry_set_title( entry * e, char * t );

/**
 * Add a tag to this entry.
 * 
 */
void entry_add_tag( entry * e, char * tag );

void entry_add_tags( entry * e, char * tags );

/**
 * Free the memory used by a journal entry.
 * 
 */
void entry_free( entry * e );

/**
 * Returns 1 if the entry has the given tag and 0 if not.
 *
 */
uint8_t entry_has_tag( entry * e, const char * tag );
#endif
\ No newline at end of file

A  => journal.c +345 -0
@@ 1,345 @@

#include "journal.h"

#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <stdio.h>
#include <dirent.h> 
#include <unistd.h>
#include <stdbool.h> 
#include <ctype.h>



/**
 * Create a path to the numbered journal entry.
 *
 */
char * journal_file_path( uint64_t fn ) {
    char file_name[10];
    sprintf(file_name, "%lu", fn);

    char * home = getenv("HOME");
    char * temp = calloc( strlen( home ) + strlen( JOURNAL_DIR ) + strlen( file_name ) + 1, sizeof( char ) );

    sprintf(temp, "%s%s%s", home, JOURNAL_DIR, file_name );
    
    return temp;

}

/**
 * Get the path of the journal folder.
 *
 */
char * get_journal_location( void ) {
    char * home = getenv("HOME");
    char * temp = malloc( strlen( home ) + strlen( JOURNAL_DIR ) + 1 );

    sprintf(temp, "%s%s", home, JOURNAL_DIR );
    
    return temp;
}

/**
 * Writes file header content out to the given file.
 * 
 * Header content is between "---" tags, each line is then a different type of content
 * as denoted by the first character.
 * 
 * '>' is the entries title.
 * '#' is a tag that the entry has.
 * '~' is a link to another article
 * 
 */
void creaate_entry_header( entry * e, FILE * fp) {
    
    fputs( "---\n", fp );
        
    char * temp_title = (char *) calloc( strlen( e->title ) + 4, sizeof( char ) );
    sprintf( temp_title, "> %s\n", e->title );
    fputs( temp_title, fp );
    // free( temp_title );

    for (int index = 0; index < e->tag_count; index++) {
        char * temp_tag = (char *) calloc( strlen(e->tags[index] ) + 4, sizeof( char ) );
        sprintf(temp_tag, "# %s\n", e->tags[index]);
        fputs( temp_tag, fp );
        // free(temp_tag);
    }

    for (int index = 0; index < e->link_count; index++) {
        char * temp = (char *) calloc( strlen( e->links[index] ) + 3, sizeof( char ) );
        sprintf(temp, "~ %s\n", e->links[index]);
        fputs( temp, fp );
        // free(temp);
    }
 
    fputs( "---\n", fp );

}

void init_journal( void ) {
    
    char * journal_dir = get_journal_location();
    
    DIR* dir = opendir(journal_dir);

    if (dir) {
        closedir(dir);
    } else if (ENOENT == errno) {
        int status = mkdir(journal_dir, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
        if (!status) {
            // Success
        } else {
            printf("Failed to create the journal directory -> %s \n", journal_dir);
            free( journal_dir );
            exit(EXIT_FAILURE);
        }
    } else {
        printf("Opendir failed for -> %s \n", journal_dir);
        free( journal_dir );
        exit(EXIT_FAILURE);
    }

    free( journal_dir );

}


uint64_t journal_get_count( void ) {
    
    char * journal_dir = get_journal_location();

    struct dirent *de;
    uint64_t count = 0;
    
    DIR* dir = opendir(journal_dir);
    
    if (dir) {

        while ((de = readdir(dir)) != NULL) {

            // Ignore any files that start with a '.' as these will not be entries in the journal.
            if (de->d_name[0] == '.') { 
                continue;   
            }

            count++;

        }

        closedir(dir);

    } else if (ENOENT == errno) {
        printf("Could not open directory for -> %s \n", journal_dir);
        free( journal_dir );
        exit(EXIT_FAILURE);
    } else {
        printf("Opendir failed for -> %s \n", journal_dir);
        free( journal_dir );
        exit(EXIT_FAILURE);
    }

    free(journal_dir);
    return count;

}

int journal_create_entry( entry * f ) {
    
    char * file_dir;
    file_dir = journal_file_path( f->entry_number );

    if( access( file_dir, F_OK ) != -1 ) {
        /*
        File already exists so we increment the 
        entry number and try again.
        */
        f->entry_number++;
        return journal_create_entry(f);

    } else {
        /*
        File does not exist so we create a new file
        and write the contents of the entry to it.
        */
        FILE * fp;                      
        fp = fopen(file_dir, "w");

        if (fp == NULL)  {
            printf("Unable to create file.\n");
            exit(EXIT_FAILURE);
        }

        creaate_entry_header( f, fp);
        
        fputs(f->content, fp);

        fflush(fp);
        fclose(fp);

    }

}

int journal_update_entry( entry * f ) {
    
    char * file_dir;
    file_dir = journal_file_path( f->entry_number );

    FILE * fp;                      
    fp = fopen(file_dir, "w");
    int ret = remove( file_dir );

    if(ret == 0) {
        journal_create_entry( f );
    } else {
        printf("Error: unable to delete the file");
        exit( 1 );
    }

}

void parse_header_material( entry * e, char * l)
{
    int i = 0;
    char * processed_ln = calloc( 1 , sizeof( char ) );
    // char * tmp;
    char *entry_number_string = calloc( 1, sizeof( char) );
    int entry_number;

    // There should always be a space after the tag and before the content
    // so we copy l[2] toskip it.
    while ( isspace( l[i] ) ) i++;
    switch ( l[i] ) {
        case '#': // Tag
            processed_ln = realloc( processed_ln, strlen( l ) );
            i += 1;
            while ( isspace( l[i] ) ) i++;
            strcpy( processed_ln, &l[i] );
            if( processed_ln[ strlen(processed_ln) - 1] == '\n' )
                processed_ln[ strlen(processed_ln) - 1] = '\0';
            entry_add_tag( e, processed_ln );
            // free( processed_ln );
            break;

        case '>': // Title
            processed_ln = realloc( processed_ln, strlen( l ) );
            i += 1;
            while ( isspace( l[i] ) ) i++;
            strcpy( processed_ln, &l[i] );
            if( processed_ln[ strlen(processed_ln) - 1] == '\n' )
                processed_ln[ strlen(processed_ln) - 1] = '\0';
            entry_set_title( e, processed_ln );
            // free( processed_ln );
            break;

        case '~': // Link
            entry_number = strtol( &l[ 3 ], &entry_number_string, 10 );  // Get entry number from start of string.
            i = 0;
            while ( l[ i ] != ':' ) i++;
            while ( isspace( l[ i ] ) ) i++;
            processed_ln =malloc( strlen(l) + 1);
            strcpy( processed_ln, &l[ i + 2 ] );
            if( processed_ln[ strlen(processed_ln) - 1] == '\n' )
                processed_ln[ strlen(processed_ln) - 1] = '\0';
            entry_add_link( e, entry_number, processed_ln );
            break;
    }

    
}

void journal_read_entry( entry * e, uint64_t n ) {
    
    char * file_dir = journal_file_path( n );
    FILE * fp;
    char * line = NULL;
    char * content;
    size_t len = 0;
    size_t read;
    bool header_switch = false;

    fp = fopen(file_dir, "r");
    if (fp == NULL) {
        exit(EXIT_FAILURE);
    }

    content = calloc(1, sizeof( char ) );
    
    while ( (read = getline(&line, &len, fp) ) != -1) {
        // Entering or leaving header material.
        if ( strcmp( line, "---\n" ) == 0 ) {
            header_switch = !header_switch;
            continue;
        }

        // Parse header materials.
        if ( header_switch ) {
            parse_header_material( e, line );
        } else {
            char * tmp = realloc( content, sizeof(char *) * (strlen(line) + strlen(content) + 1) );
            if ( tmp == NULL ) {
                printf( "Unable to assign memory for file content.\n" );
                exit(EXIT_FAILURE);
            } 
            content = tmp;
            strcat( content, line );
        }

    }

    entry_set_content( e, content );
    e->entry_number = n;
    // e->content = strdup( content );

    free( content );
    free( file_dir );
    free( line );
    
}

#include <ncurses.h>
char ** get_all_entry_refs( const char * tag_filter, uint64_t * num ) {

    char ** entry_list = calloc( 1, sizeof(char *) );
    int e_count = journal_get_count();
    *num = 0;
    if (e_count == 0) return entry_list;
    entry * e;
    for ( int index = 0; index < e_count; index++ ) {
        e = entry_init();
        journal_read_entry( e, index );
        if ( tag_filter != NULL ) {
            if ( entry_has_tag( e, tag_filter ) == 0) {
                entry_free(e);
                continue;
            }
        } 
        char ** tmp = realloc(entry_list, (1 + *num) * sizeof(char*) );
        if ( tmp == NULL) {
            printf("FAILED ALLOCATING MEMORY FOR LIST!");
            exit(0);
        }
        entry_list = tmp;
        
        char * en = calloc(7, sizeof(char));
        sprintf(en, "{%lu}", e->entry_number );
        entry_list[*num] = malloc( sizeof(char) * (strlen( en ) + strlen( e->title )) + 2 );
        sprintf( entry_list[*num], "%s %s", en, e->title );
        *num += 1;
        
     
        entry_free(e);
    }
    

    return entry_list;

}


A  => journal.h +48 -0
@@ 1,48 @@

#ifndef JOURNAL_H
#define JOURNAL_H

#include <stdint.h>
#include "entry.h"

#define JOURNAL_DIR "/Zettelkasten/"

/**
 * Check that the journal directory exists and if it does not create it.
 *
 */
void init_journal( void );

/**
 * Get the number of entries in the journal
 *
 */
uint64_t journal_get_count( void );

/**
 * create a file with the nessesary contents to be used.
 * 
 */
int journal_create_entry( entry * e );

/**
 * Removes a journal entry before creating it.
 *
 */
int journal_update_entry( entry * f );


/**
 * Read the given entry from the journal.
 * 
 */
void journal_read_entry( entry * e, uint64_t n );

/**
 * Get all of the entries in the journal that have the specific filter.
 * If filter is NULL, get all entries.
 *
 */
char ** get_all_entry_refs( const char * tag_filter, uint64_t * num );

#endif
\ No newline at end of file

A  => main.c +64 -0
@@ 1,64 @@

#include <ncurses.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "journal.h"
#include "zettelkasten.h"
#include <menu.h>

int main () {
	init_journal();
	initscr();
	cbreak();
	noecho();
	keypad(stdscr, TRUE);
	
	int state = SCREEN_LIST;

	while( state != SCREEN_EXIT ) {
		switch ( state ) {
			
			case SCREEN_LIST:
    			state = zk_screen_list( "Select entry to read" );
				break;

			case SCREEN_DISPLAY:
				state = zk_screen_display( entry_to_read );
				break;

			case SCREEN_FILTER_INPUT:
				state = zk_screen_tag_filter();
				break;
			
			case SCREEN_ADD:
				state = zk_screen_add_entry();
				break;

			case SCREEN_CREATE_LINK:
				state = zk_do_add_link();
				break;

			case SCREEN_TAG_LIST: 
				state = zk_screen_tag_list( entry_to_read );
				break;
				
			case SCREEN_ADD_TAG:
				state = zk_screen_add_tag();
                break;

			default:
				break;

		}

		clear();
		
	}

	endwin();

    return 0;
    
}

A  => makefile +27 -0
@@ 1,27 @@
# By James Henderson, 2020

CC := gcc

CFLAGS := -std=c99 -Os 
LFLAGS := -s -Os


BUILDDIR := build
OBJDIR := $(BUILDDIR)/obj

SRCS := $(notdir $(shell find -name '*.c'))
OBJS := $(patsubst %.cpp, $(OBJDIR)/%.o, $(SRCS))

zettelkasten: builddir $(OBJS) $(SRCS) 
	$(CC) $(LFLAGS) $(OBJS)  -o $(BUILDDIR)/zk -lmenu -lncurses

$(OBJDIR)/%.o: %.cpp
	$(CC) $(CFLAGS) $^ -o $@ -lmenu -lncurses

.PHONY: builddir
builddir:
	@mkdir -p $(OBJDIR)

.PHONY: clean
clean:
	@rm -rf build/

A  => screen_add_entry.c +195 -0
@@ 1,195 @@
#include "journal.h"
#include "entry.h"
#include "zettelkasten.h"
#include "string_helpers.h"

#include <string.h>
#include <menu.h>
#include <stdio.h>
#include <stdlib.h>


enum win_focus {
    FOCUS_TITLE,
    FOCUS_CONTENT,
    FOCUS_TAG,
    FOCUS_OK,
    FOCUS_CANCEL
};


void home_cursor_in_window( WINDOW * win ) {
    wmove( win, 0, 0 );
}

char * read_content_string( WINDOW * win ) {
    char * content = calloc(1000, sizeof(char) );
    char * str = calloc(1000, sizeof(char) );
    for ( int x = 0; x < 11; x++ ) {
        int num = mvwinstr( win, x, 0, str );
        strcat( content, str );
    }
    free( str );
    return content;
}

int zk_screen_add_entry( void ) {
    
    WINDOW * _title_win;
    WINDOW * _content_win;
    WINDOW * _tags_win;
    WINDOW * _create_win;
    WINDOW * _cancel_win;
    WINDOW * focusesd_win;

    int row,col;
    int c;
    int return_value = SCREEN_LIST;

    int focused_window_id = FOCUS_TITLE;
    
    curs_set(1);
    
    getmaxyx( stdscr,row,col );

    print_text_underlined( stdscr, 2, (col / 2) - 32, "Title:");

    print_text_underlined( stdscr, 4, (col / 2) - 40, "Entry content:");
    
    print_text_underlined( stdscr, 15, (col / 2) - 31, "Tags:");

    refresh();

    _title_win = create_newwin_lb(1, 50, 2, (col / 2) - 25 );
    _content_win = create_newwin_lb(10, 50, 4, (col / 2) - 25 );
    _tags_win = create_newwin_lb(1, 50, 15, (col / 2) - 25 );
    _cancel_win = create_newwin(1, 10, 17, (col / 2) - 25 );
    _create_win = create_newwin(1, 10, 17, (col / 2) );
    
    print_text_underlined( _cancel_win, 0, 0, "Cancel" );
    print_text_underlined( _create_win, 0, 0, "Ok" );

    keypad( _title_win, TRUE );
    keypad( _content_win, TRUE );
    keypad( _tags_win, TRUE );
    keypad( _cancel_win, TRUE );
    keypad( _create_win, TRUE );

    wrefresh( _title_win );
    wrefresh( _content_win );
    wrefresh( _tags_win );
    wrefresh( _cancel_win );
    wrefresh( _create_win );

    focusesd_win = _title_win;
    home_cursor_in_window( focusesd_win );
    // refresh();
    
    while( c = wgetch( focusesd_win ) ) {       
        switch(c) {	

            case '\t': {
                if (focused_window_id == FOCUS_TITLE ) {
                    focused_window_id = FOCUS_CONTENT;
                    focusesd_win = _content_win;
                } else if ( focused_window_id == FOCUS_CONTENT ) {
                    focused_window_id = FOCUS_TAG;
                    focusesd_win = _tags_win;
                } else if ( focused_window_id == FOCUS_TAG ) {
                    focused_window_id = FOCUS_CANCEL;
                    focusesd_win = _cancel_win;
                } else if ( focused_window_id == FOCUS_CANCEL ) {
                    focused_window_id = FOCUS_OK;
                    focusesd_win = _create_win;
                } else if ( focused_window_id == FOCUS_OK ) {
                    focused_window_id = FOCUS_TITLE;
                    focusesd_win = _title_win;
                } 
                home_cursor_in_window( focusesd_win );
                refresh();
            }

            case KEY_LEFT:
                getyx( focusesd_win, row, col );
                if ( col > 0)
                    wmove( focusesd_win, row, col - 1 );
                break;

            case KEY_RIGHT:
                getyx( focusesd_win, row, col );
                if ( col < 50)
                    wmove( focusesd_win, row, col + 1 );
                break;

            case KEY_UP:
                if ( focused_window_id == FOCUS_CONTENT ) {
                    getyx( focusesd_win, row, col );
                    if ( row > 0)
                        wmove( focusesd_win, row - 1, col );
                    break;
                }

            case KEY_DOWN:
                if ( focused_window_id == FOCUS_CONTENT ) {
                    getyx( focusesd_win, row, col );
                    if ( row < 12)
                        wmove( focusesd_win, row + 1, col);
                    break;
                }

            case KEY_DC:
                wdelch( focusesd_win );
                getyx( focusesd_win, row, col );
                break;

            case KEY_BACKSPACE:
            case 127:
                getyx( focusesd_win, row, col );
                if ( col > 0) {
                    wmove( focusesd_win, row, col - 1 );
                    wdelch( focusesd_win );
                }
                break;

            case 10: {
                if ( focused_window_id == FOCUS_OK ) {
                    entry * new_entry = entry_init();
                    new_entry->entry_number = journal_get_count();
                    int num;
                    char * str = malloc( 1 );
                    str[0] = '\0';
                    num = mvwinstr( _title_win, 0, 0, str );
                    entry_set_title( new_entry, str );
                    char * cont = read_content_string( _content_win );
                    entry_set_content( new_entry, cont );
                    char * tags = calloc( 51, sizeof( char ) );
                    mvwinstr( _tags_win, 0, 0, tags );
                    entry_add_tags( new_entry, tags );
                    journal_create_entry( new_entry );
                    entry_free( new_entry );
                    goto exit_loop;
                } else if ( focused_window_id == FOCUS_CANCEL ) {
                    // Cancel
                    goto exit_loop;
                } else if ( focused_window_id == FOCUS_TAG || focused_window_id == FOCUS_TITLE ) {
                    continue;
                }
            }

            default:
                if ( focused_window_id != FOCUS_OK && focused_window_id != FOCUS_CANCEL )
                    waddch( focusesd_win, c );
                break;
		}
	}

    exit_loop:;

    delwin( _title_win );
    delwin( _content_win );
    delwin( _tags_win );
    delwin( _cancel_win );
    delwin( _create_win );
    return return_value;
}


A  => screen_add_tag.c +92 -0
@@ 1,92 @@
#include "journal.h"
#include "entry.h"
#include "zettelkasten.h"

#include <string.h>
#include <menu.h>
#include <stdio.h>
#include <stdlib.h>

int zk_screen_add_tag( void ) {
    
    int row, col;
    char * title = "Add tag:";
    int c;
    int tag_len = 0;
    WINDOW * _t_window;

    curs_set(1);

    getmaxyx( stdscr,row,col );
    
    // Print the title
    attron( A_UNDERLINE );
    mvprintw( ( row / 4 ) - 1, ( col - strlen( title ) ) / 2, title );
    attroff( A_UNDERLINE );
    refresh();
    
    _t_window = create_newwin( 1, 50, ( row / 4 ) + 1, (col / 2) - 25 );
    keypad( _t_window, TRUE );
    wrefresh( _t_window );

    
    while(( c = wgetch( _t_window ) ) != 10 ) {       
        switch (c) {

            case KEY_LEFT:
                getyx( _t_window, row, col );
                if ( col > 0 ) {
                    wmove( _t_window, row, col - 1 );
                }
                break;
            
            case KEY_RIGHT:
                getyx( _t_window, row, col );
                if ( col < tag_len ) {
                    wmove( _t_window, row, col + 1 );
                }
                break;
            
            case KEY_DC:
                wdelch( _t_window );
                getyx( _t_window, row, col );
                if ( col < tag_len ) {
                    tag_len--;
                }
                break;
            
            case KEY_BACKSPACE:
            case 127:
                getyx( _t_window, row, col );
                if ( col > 0) {
                    wmove( _t_window, row, col - 1 );
                    wdelch( _t_window );
                    tag_len--;
                }
                break;
            
            default:
                tag_len++;
                waddch( _t_window, c );
                break;
        }
	}
    
    wmove( _t_window, 0, 0 );
    char * tmp = realloc( filter_tag, tag_len + 1);
    if ( tmp != NULL ) {
        filter_tag = tmp;
    }
    winnstr( _t_window, filter_tag, tag_len + 1 );
    filter_tag[tag_len] = '\0';

    entry * e = entry_init();
    journal_read_entry( e, entry_to_read );
    entry_add_tag( e, filter_tag );
    journal_update_entry( e );
    entry_free( e );

    delwin( _t_window );

    return SCREEN_TAG_LIST;
}
\ No newline at end of file

A  => screen_displey_entry.c +118 -0
@@ 1,118 @@
#include "journal.h"
#include "entry.h"
#include "zettelkasten.h"

#include <string.h>
#include <menu.h>
#include <stdio.h>
#include <stdlib.h>

int zk_screen_display( uint64_t e ) {

    int row,col;
    entry * ent = entry_init();
    WINDOW * content_win;
    WINDOW * link_window;
    MENU * link_menu;
    ITEM **link_items;
    int c;				
    int i;
    uint8_t return_value = SCREEN_EXIT;

    curs_set(0);

    getmaxyx( stdscr,row,col );
    
    journal_read_entry( ent, e );

    // Print the title
    attron( A_UNDERLINE );
    char en[10];
    sprintf( en, "%lu", ent->entry_number );
    mvwprintw( stdscr, 2, ( col - (strlen( ent->title ) + strlen( en ) + 3) ) / 2, "{%lu} %s", ent->entry_number, ent->title );
    attroff( A_UNDERLINE );
    refresh();

    // Create the window for the content of the note
    // and put the contents in it.
    content_win = create_newwin(10, 50, 4, (col / 2) - 25 );
    mvwprintw( content_win, 0, 0,"%s", ent->content );
    wrefresh( content_win );
    
    // Create the links menu and enter add the entries to it.
    link_window = create_newwin( 10, 50,15, (col / 2) - 25 );
    keypad( link_window, TRUE );

    link_items = (ITEM **)calloc( ent->link_count + 1, sizeof(ITEM *) );        
    
    for( i = 0; i < ent->link_count; ++i ) {
        link_items[i] = new_item(ent->links[i], " ");
    }

    link_items[ent->link_count] = (ITEM *)NULL;

    link_menu = new_menu( link_items );
    set_menu_win( link_menu, link_window );        
    set_menu_sub( link_menu, derwin( link_window, 0, 0, 0, 0 ) );
    set_menu_format(link_menu, 10, 1);
    set_menu_mark( link_menu, "" );
    post_menu( link_menu ); 
    wrefresh( link_window );

    while( c = wgetch( link_window ) ) {       
        switch(c) {	
            
            case KEY_DOWN:
				menu_driver( link_menu, REQ_DOWN_ITEM );
                wrefresh( link_window );
				break;
			
            case KEY_UP:
				menu_driver( link_menu, REQ_UP_ITEM );
                wrefresh( link_window );
				break;
            
            case 'q':
                return_value = SCREEN_LIST;
                goto exit_loop;
                break;

            case 'L':
                return_value = SCREEN_CREATE_LINK;
                goto exit_loop;
            
            case 'T':
                return_value = SCREEN_TAG_LIST;
                goto exit_loop;
                
            case 10: {
                if ( ent->link_count == 0 ) continue;
                ITEM * cur; 
                cur = current_item(link_menu);
                char * name = (char *)item_name( cur );
                char * p;
                entry_to_read = strtol(&name[1], &p, 10);
                return_value = SCREEN_DISPLAY;
                goto exit_loop;
                break;
            }   
		}
	}

    exit_loop:;

    unpost_menu( link_menu );
    free_menu( link_menu );
    
    for( i = 0; i < ent->link_count; ++i ) {
        free_item( link_items[i] );
    }
    
    delwin( link_window );
    delwin( content_win );

    entry_free( ent );
    
    return return_value;

}

A  => screen_filter_tag.c +85 -0
@@ 1,85 @@
#include "journal.h"
#include "entry.h"
#include "zettelkasten.h"

#include <string.h>
#include <menu.h>
#include <stdio.h>
#include <stdlib.h>

int zk_screen_tag_filter( void ) {
    
    int row, col;
    char * title = "Filter by tag:";
    int c;
    int tag_len = 0;
    WINDOW * _t_window;

    curs_set(1);

    getmaxyx( stdscr,row,col );
    
    // Print the title
    attron( A_UNDERLINE );
    mvprintw( ( row / 4 ) - 1, ( col - strlen( title ) ) / 2, title );
    attroff( A_UNDERLINE );
    refresh();
    
    _t_window = create_newwin( 1, 50, ( row / 4 ) + 1, (col / 2) - 25 );
    keypad( _t_window, TRUE );
    wrefresh( _t_window );

    
    while(( c = wgetch( _t_window ) ) != 10 ) {       
        switch (c) {

            case KEY_LEFT:
                getyx( _t_window, row, col );
                if ( col > 0 ) {
                    wmove( _t_window, row, col - 1 );
                }
                break;
            
            case KEY_RIGHT:
                getyx( _t_window, row, col );
                if ( col < tag_len ) {
                    wmove( _t_window, row, col + 1 );
                }
                break;
            
            case KEY_DC:
                wdelch( _t_window );
                getyx( _t_window, row, col );
                if ( col < tag_len ) {
                    tag_len--;
                }
                break;
            
            case KEY_BACKSPACE:
            case 127:
                getyx( _t_window, row, col );
                if ( col > 0) {
                    wmove( _t_window, row, col - 1 );
                    wdelch( _t_window );
                    tag_len--;
                }
                break;
            
            default:
                tag_len++;
                waddch( _t_window, c );
                break;
        }
	}
    
    wmove( _t_window, 0, 0 );
    char * tmp = realloc( filter_tag, tag_len + 1);
    if ( tmp != NULL ) {
        filter_tag = tmp;
    }
    winnstr( _t_window, filter_tag, tag_len + 1 );
    filter_tag[tag_len] = '\0';
    delwin( _t_window );

    return SCREEN_LIST;
}
\ No newline at end of file

A  => screen_link_reason.c +89 -0
@@ 1,89 @@
#include "journal.h"
#include "entry.h"
#include "zettelkasten.h"

#include <string.h>
#include <menu.h>
#include <stdio.h>
#include <stdlib.h>

int zk_screen_link_context( void ) {
    
    int row, col;
    
    int c;
    int tag_len = 0;
    WINDOW * _t_window;

    char * title = calloc( 100, sizeof( char ) );

    curs_set(1);

    sprintf(title, "Why link from {%i} to {%i}?", last_entry, entry_to_read );
    getmaxyx( stdscr,row,col );
    
    // Print the title
    attron( A_UNDERLINE );
    mvprintw( ( row / 4 ) - 1, ( col - strlen( title ) ) / 2, title );
    attroff( A_UNDERLINE );
    refresh();
    
    _t_window = create_newwin( 1, 50, ( row / 4 ) + 1, (col / 2) - 25 );
    keypad( _t_window, TRUE );
    wrefresh( _t_window );

    
    while(( c = wgetch( _t_window ) ) != 10 ) {       
        switch (c) {

            case KEY_LEFT:
                getyx( _t_window, row, col );
                if ( col > 0 ) {
                    wmove( _t_window, row, col - 1 );
                }
                break;
            
            case KEY_RIGHT:
                getyx( _t_window, row, col );
                if ( col < tag_len ) {
                    wmove( _t_window, row, col + 1 );
                }
                break;
            
            case KEY_DC:
                wdelch( _t_window );
                getyx( _t_window, row, col );
                if ( col < tag_len ) {
                    tag_len--;
                }
                break;
            
            case KEY_BACKSPACE:
            case 127:
                getyx( _t_window, row, col );
                if ( col > 0) {
                    wmove( _t_window, row, col - 1 );
                    wdelch( _t_window );
                    tag_len--;
                }
                break;
            
            default:
                tag_len++;
                waddch( _t_window, c );
                break;
        }
	}
    
    free( title );
    wmove( _t_window, 0, 0 );
    char * tmp = realloc( filter_tag, tag_len + 1);
    if ( tmp != NULL ) {
        filter_tag = tmp;
    }
    winnstr( _t_window, filter_tag, tag_len + 1 );
    filter_tag[tag_len] = '\0';
    delwin( _t_window );

    return SCREEN_LIST;
}
\ No newline at end of file

A  => screen_list_entries.c +132 -0
@@ 1,132 @@
#include "journal.h"
#include "entry.h"
#include "zettelkasten.h"

#include <string.h>
#include <menu.h>
#include <stdio.h>
#include <stdlib.h>

int zk_screen_list( const char * message ) {

    

    int row, col;
    uint64_t  file_count = 0;
    WINDOW * _e_window;
    MENU * _e_menu;
    ITEM ** _e_items;
    int c;				
    int i;
    uint8_t action_value = SCREEN_EXIT;

    curs_set(0);

    char ** entry_refs = get_all_entry_refs( filter_tag , &file_count );
    
    _e_items = (ITEM **)calloc( file_count + 1, sizeof(ITEM *) );        
    
    getmaxyx( stdscr,row,col );
    move( 2, 0 );
    clrtoeol();

    // // Print the title
    attron( A_UNDERLINE );
    if ( filter_tag == NULL) {
        mvprintw( 2, ( col - strlen( message ) ) / 2, message );
    } else {
        char * f_msg = calloc(50, sizeof( char ) );
        sprintf( f_msg, "%s {%s}", message, filter_tag );
        mvprintw( 2, ( col - strlen( f_msg ) ) / 2, f_msg );
        free( f_msg );
    }
    
    attroff( A_UNDERLINE );
    refresh();

    // // Create the links menu and enter add the entries to it.
    _e_window = create_newwin( 10, 50, 4, (col / 2) - 25 );
    keypad( _e_window, TRUE );
    
    if ( file_count > 0 ) {
        for( i = 0; i < file_count; ++i ) {
            _e_items[i] = new_item( entry_refs[i], "" );
        }
        
    }
    _e_items[file_count] = (ITEM *)NULL;

    _e_menu = new_menu( _e_items );
    set_menu_win( _e_menu, _e_window );        
    set_menu_sub( _e_menu, derwin( _e_window, 0, 0, 0, 0 ) );
    set_menu_format(_e_menu, 10, 1);
    set_menu_mark( _e_menu, "" );
    post_menu( _e_menu ); 
    wrefresh( _e_window );

    while( c = wgetch( _e_window ) ) {       
        switch(c) {

            case 'q':
                action_value = SCREEN_EXIT;
                goto exit_loop;

            case KEY_DOWN:
                menu_driver( _e_menu, REQ_DOWN_ITEM );
                wrefresh( _e_window );
                break;

            case KEY_UP:
                menu_driver( _e_menu, REQ_UP_ITEM );
                wrefresh( _e_window );
                break;

            case 'c':
                if (filter_tag != NULL ) {
                    free( filter_tag );
                    filter_tag = NULL;
                    action_value = SCREEN_LIST;
                    goto exit_loop;
                }
                break;

            case 'f':
                action_value = SCREEN_FILTER_INPUT;
                goto exit_loop;

            case 'a':
                action_value = SCREEN_ADD;
                goto exit_loop;
                
            case 10:   {    // Return Key.
                if ( file_count > 0 ) {
                    ITEM * cur; 
                    cur= current_item(_e_menu);
                    char * name = (char *)item_name( cur );
                    char * p;
                    entry_to_read = strtol(&name[1], &p, 10);
                    action_value = SCREEN_DISPLAY;
                    goto exit_loop;
                }
                break;
            } 

        }
    }

    exit_loop:; // Using a goto init!

    unpost_menu( _e_menu );
    free_menu( _e_menu );
    for( i = 0; i < file_count; ++i ) {
        free_item( _e_items[i] );
        free(entry_refs[i]);
    }

    filter_tag = NULL;

    delwin( _e_window );
 
    return action_value;

}

A  => screen_tag_list.c +111 -0
@@ 1,111 @@
#include "journal.h"
#include "entry.h"
#include "zettelkasten.h"

#include <string.h>
#include <menu.h>
#include <stdio.h>
#include <stdlib.h>

int zk_screen_tag_list( uint64_t ent_num ) {

    int row, col;
    WINDOW * _e_window;
    MENU * _e_menu;
    ITEM ** _e_items;
    int c;				
    int i;
    uint8_t action_value = SCREEN_EXIT;

    entry * e = entry_init( );
    journal_read_entry( e, ent_num );

    curs_set(0);

    _e_items = (ITEM **)calloc( e->tag_count + 1, sizeof( ITEM * ) );        
    
    getmaxyx( stdscr,row,col );
    move( 2, 0 );
    clrtoeol();
    char * message = calloc(50, sizeof( char ) );
    sprintf( message, "Tags for {%lu}.", e->entry_number );

    // // Print the title
    attron( A_UNDERLINE );
    mvprintw( 2, ( col - strlen( message ) ) / 2, message );
    attroff( A_UNDERLINE );
    refresh();

    // // Create the links menu and enter add the entries to it.
    _e_window = create_newwin( 10, 50, 4, (col / 2) - 25 );
    keypad( _e_window, TRUE );
    
    if ( e->tag_count > 0 ) {
        for( i = 0; i < e->tag_count; ++i ) {
            _e_items[i] = new_item( e->tags[i], "" );
        }
        
    }
    _e_items[e->tag_count] = (ITEM *)NULL;

    _e_menu = new_menu( _e_items );
    set_menu_win( _e_menu, _e_window );        
    set_menu_sub( _e_menu, derwin( _e_window, 0, 0, 0, 0 ) );
    set_menu_format(_e_menu, 10, 1);
    set_menu_mark( _e_menu, "" );
    post_menu( _e_menu ); 
    wrefresh( _e_window );

    while( c = wgetch( _e_window ) ) {       
        switch(c) {

            case 'q':
                action_value = SCREEN_DISPLAY;
                goto exit_loop;

            case KEY_DOWN:
                menu_driver( _e_menu, REQ_DOWN_ITEM );
                wrefresh( _e_window );
                break;

            case KEY_UP:
                menu_driver( _e_menu, REQ_UP_ITEM );
                wrefresh( _e_window );
                break;

            case 'a':
                action_value = SCREEN_ADD_TAG;
                goto exit_loop;
                break;

            case 10:   {    // Return Key.
                if ( e->tag_count > 0 ) {
                    ITEM * cur; 
                    cur= current_item(_e_menu);
                    char * t = (char *)item_name( cur );
                    filter_tag = calloc(50, sizeof( char ) );
                    sprintf(filter_tag, "%s", t);
                    action_value = SCREEN_LIST;
                    goto exit_loop;
                }
                break;
            } 

        }
    }

    exit_loop:; // Using a goto init!

    unpost_menu( _e_menu );
    free_menu( _e_menu );
    for( i = 0; i < e->tag_count; ++i ) {
        free_item( _e_items[i] );
    }
    
    entry_free( e );

    delwin( _e_window );
 
    return action_value;

}

A  => string_helpers.c +22 -0
@@ 1,22 @@

void remove_trailing_white( char * c ) {
    int index, i;
    index = -1;
    i = 0;
    
    while( c[i] != '\0')
    {
        if (c[i] == '\n') {
            c[i + 1] = '\0';
            return;
        }

        if(c[i] != ' ' && c[i] != '\t')
        {
            index= i;
        }
        i++;
    }
    
    c[index + 1] = '\0';
}

A  => string_helpers.h +12 -0
@@ 1,12 @@

#ifndef STRING_HELPERS_H
#define STRING_HELPERS_H

/**
 * Removes trailing white space from a string.
 * 
 */
void remove_trailing_white( char * c );


#endif
\ No newline at end of file

A  => zettelkasten.c +94 -0
@@ 1,94 @@

#include "journal.h"
#include "entry.h"
#include "zettelkasten.h"

#include <string.h>
#include <menu.h>
#include <stdio.h>
#include <stdlib.h>

// #define _DEBUG_

char * filter_tag = NULL;

int last_entry;
int link_entry;
int entry_to_read;

WINDOW *create_newwin(int height, int width, int starty, int startx)
{	
    WINDOW *local_win;

	local_win = newwin(height, width, starty, startx);
    
    #ifdef _DEBUG_
        box(local_win, 0 , 0);		/* 0, 0 gives default characters 
                                    * for the vertical and horizontal
                                    * lines			*/
        wrefresh(local_win);		/* Show that box 		*/
    #endif

	return local_win;
}

WINDOW *create_newwin_lb(int height, int width, int starty, int startx)
{	
    WINDOW *local_win;
	local_win = newwin(height, width, starty, startx);
    wborder(local_win, ' ', ' ', ' ',' ',' ',' ',' ',' ');
    wrefresh(local_win);
	return local_win;
}

void print_text_underlined( WINDOW * win, int row, int col, char* text) {
    attron( A_UNDERLINE );
    mvwprintw( win, row, col, text );
    attroff( A_UNDERLINE );
}

int zk_do_add_link() {
    int state = SCREEN_LIST;
    last_entry = entry_to_read;
    while ( state != SCREEN_DISPLAY ) {

        switch ( state ) {

            case SCREEN_ADD:
            case SCREEN_LIST:
                state = zk_screen_list( "Select entry to link to" );
                if (state = SCREEN_DISPLAY) {
                    state = SCREEN_LINK_CONTEXT;
                }
                break;

            case SCREEN_EXIT:
                return SCREEN_DISPLAY;

            case SCREEN_FILTER_INPUT:
				state = zk_screen_tag_filter();
				break;
            
            case SCREEN_LINK_CONTEXT:
                state = zk_screen_link_context();
                entry * ent = entry_init(); 
                journal_read_entry( ent, last_entry );
                entry_add_link( ent , entry_to_read, filter_tag );
                journal_update_entry( ent );
                entry_free( ent );
                entry_to_read = last_entry;
                free(filter_tag);
                filter_tag = NULL;
                return SCREEN_DISPLAY;
                break;
        }

        clear();
    }

    return state;

}




A  => zettelkasten.h +38 -0
@@ 1,38 @@
#ifndef ZETTELKASTEN_H
#define ZETTELKASTEN_H

#include <ncurses.h>

enum nav_states {
    SCREEN_EXIT,
    SCREEN_DISPLAY,
    SCREEN_LIST,
    SCREEN_ADD,
    SCREEN_CLEAR_FILTER,
    SCREEN_FILTER_INPUT,
    SCREEN_CREATE_LINK,
    SCREEN_LINK_CONTEXT,
    SCREEN_TAG_LIST,
    SCREEN_ADD_TAG
};

extern char * filter_tag;

extern int last_entry;
extern int link_entry;
extern int entry_to_read;

WINDOW *create_newwin(int height, int width, int starty, int startx);
WINDOW *create_newwin_lb(int height, int width, int starty, int startx);
void print_text_underlined( WINDOW * win, int row, int col, char* text);

int zk_screen_display( uint64_t e );
int zk_screen_list( const char * message );
int zk_screen_tag_filter( void );
int zk_screen_add_entry( void );
int zk_do_add_link( void );
int zk_screen_link_context( void );
int zk_screen_tag_list( uint64_t ent_num );
int zk_screen_add_tag( void );

#endif