2022-02-09 17:46:27 +00:00
|
|
|
package lib
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/mail"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
md "github.com/JohannesKaufmann/html-to-markdown"
|
|
|
|
"github.com/emersion/go-imap"
|
|
|
|
"github.com/russross/blackfriday/v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrNoMessageBody = errors.New("server did not provide a message body")
|
|
|
|
)
|
|
|
|
|
|
|
|
// Note represents an IMAP note.
|
|
|
|
type Note struct {
|
|
|
|
message *imap.Message
|
|
|
|
Folder *NoteFolder
|
|
|
|
deleted bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deleted will indicate if a Note has been deleted
|
|
|
|
func (n Note) Deleted() bool {
|
|
|
|
return n.deleted
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadMarkdown will return the contents of a Note in Markdown format
|
|
|
|
func (n Note) ReadMarkdown() (string, error) {
|
|
|
|
body, err := n.ReadText()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Convert to to markdown from html
|
|
|
|
urlDomain := ""
|
|
|
|
enableCommonmark := true
|
|
|
|
converter := md.NewConverter(urlDomain, enableCommonmark, nil)
|
|
|
|
body, err = converter.ConvertString(body)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("failed converting to markdown: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return body, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OpenMarkdownInEditor opens the Markdown converted contents of a Note in an editor
|
|
|
|
func (n Note) OpenMarkdownInEditor() (string, error) {
|
|
|
|
body, err := n.ReadMarkdown()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return OpenInEditor(body)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReadText reads text contents of note from IMAP server.
|
|
|
|
func (n Note) ReadText() (string, error) {
|
|
|
|
messages := make(chan *imap.Message, 1)
|
|
|
|
done := make(chan error, 1)
|
|
|
|
|
|
|
|
section := &imap.BodySectionName{}
|
|
|
|
seqset := new(imap.SeqSet)
|
|
|
|
seqset.AddNum(n.message.Uid)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
done <- n.Folder.client.BaseClient.UidFetch(
|
|
|
|
seqset,
|
|
|
|
[]imap.FetchItem{section.FetchItem(), imap.FetchUid},
|
|
|
|
messages,
|
|
|
|
)
|
|
|
|
}()
|
|
|
|
|
|
|
|
msg := <-messages
|
|
|
|
|
|
|
|
result := msg.GetBody(section)
|
|
|
|
if result == nil {
|
|
|
|
return "", ErrNoMessageBody
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := <-done; err != nil {
|
|
|
|
return "", fmt.Errorf("failed getting message from server: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
m, err := mail.ReadMessage(result)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("failed reading message received from server: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(m.Body)
|
|
|
|
if err != nil {
|
|
|
|
return "", fmt.Errorf("failed reading message body from server message: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(body), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OpenInEditor opens the note in an external editor.
|
|
|
|
func (n Note) OpenInEditor() (string, error) {
|
|
|
|
body, err := n.ReadText()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
return OpenInEditor(body)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n Note) Delete() error {
|
|
|
|
return n.Folder.client.DeleteNote(n)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n Note) OpenPager() error {
|
|
|
|
body, err := n.ReadText()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return OpenInPager(body)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n Note) OpenMarkdownInPager() error {
|
|
|
|
body, err := n.ReadMarkdown()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return OpenInPager(body)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (n Note) EditMarkdown() error {
|
|
|
|
// TODO: Handle an edit where nothing changes
|
|
|
|
body, err := n.OpenMarkdownInEditor()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get first line as subject
|
|
|
|
subject := strings.Split(body, "\n")[0]
|
|
|
|
subject = strings.TrimLeft(subject, "#")
|
|
|
|
subject = strings.Trim(subject, " ")
|
|
|
|
|
|
|
|
// Convert Markdown to HTML
|
|
|
|
body = string(blackfriday.Run([]byte(body)))
|
|
|
|
|
|
|
|
// Save updated content as a new note
|
|
|
|
err = n.Folder.StoreNote(subject, body, ContentTypeHtml)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Delete existing (this) note
|
|
|
|
err = n.Delete()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to delete note after edit: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark this as deleted so it is known to not reuse
|
|
|
|
n.deleted = true
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-02-11 23:53:36 +00:00
|
|
|
func (n Note) Name() string {
|
|
|
|
return n.message.Envelope.Subject
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* func (n Note) Preview() string {
|
|
|
|
* m.GetBody()
|
|
|
|
* }
|
|
|
|
*/
|
|
|
|
|
2022-02-09 17:46:27 +00:00
|
|
|
// NewNote creates a new note from an imap message.
|
|
|
|
func NewNote(folder *NoteFolder, message *imap.Message) *Note {
|
|
|
|
// TODO: should this be private? See NewNoteFolder
|
|
|
|
return &Note{
|
|
|
|
message: message,
|
|
|
|
Folder: folder,
|
|
|
|
deleted: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NoteFolder represents a particular IMAP folder holding notes.
|
|
|
|
type NoteFolder struct {
|
|
|
|
client *Client
|
|
|
|
Name string
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListNotes returns all notes in the folder.
|
|
|
|
func (nf *NoteFolder) ListNotes() (notes []*Note, err error) {
|
|
|
|
// TODO: Accept a limit parameter
|
|
|
|
messages, err := nf.client.ListNotesInFolder(nf)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
notes = append(notes, messages...)
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Select selects this folder in Read Only mode.
|
|
|
|
func (nf *NoteFolder) Select() (*imap.MailboxStatus, error) {
|
|
|
|
return nf.client.Select(nf.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SelectRW selects this folder in Read Write mode.
|
|
|
|
func (nf *NoteFolder) SelectRW() (*imap.MailboxStatus, error) {
|
|
|
|
return nf.client.SelectRW(nf.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// StoreNote stores a note with provided content to the folder.
|
|
|
|
func (nf NoteFolder) StoreNote(subject string, body string, contentType ContentType) error {
|
|
|
|
// TODO: Maybe refactor to accept only body and extract subject/title here
|
|
|
|
envelope := imap.Envelope{
|
|
|
|
Subject: subject,
|
|
|
|
Date: time.Now(),
|
|
|
|
}
|
|
|
|
|
|
|
|
return nf.client.StoreNote(nf.Name, envelope, body, contentType)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindNoteByName searches for a Note using it's name and returns that note
|
|
|
|
func (nf *NoteFolder) FindNotesByName(noteName string) ([]*Note, error) {
|
|
|
|
criteria := imap.NewSearchCriteria()
|
|
|
|
criteria.Header.Add("SUBJECT", noteName)
|
|
|
|
|
|
|
|
return nf.client.SearchNotes(nf, criteria)
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewNoteFolder creates a NoteFolder for a given folder name.
|
|
|
|
func NewNoteFolder(client *Client, name string) *NoteFolder {
|
|
|
|
// TODO: Should this (or anything that requires passing something like client) be private?
|
|
|
|
return &NoteFolder{
|
|
|
|
client, name,
|
|
|
|
}
|
|
|
|
}
|