Winky's profile picture

Published by

published
updated

Category: Web, HTML, Tech

working on a program to make writign gemini blogs easier make something super easy even more easier

I am new to programing, but here is my attempt, i am learning as i go, i have some flaws, soe incomplete routines, but the program compiled seems to work flawlessly;

import tkinter as tk
from tkinter import filedialog, messagebox, simpledialog
import json
import os
# still learning be gentle
# Figure out ehy it says it folloed the curser for inserts but appends instead, I am working on that.

class LunarOrbitLogbook:
    def __init__(self, root):
        self.root = root
        self.root.title("Lunar Orbit Logbook, Blog Writer for Gemini Protocol")
        self.root.configure(bg='#001f3f')

        self.title_var = tk.StringVar()
        self.author_var = tk.StringVar()
        self.date_var = tk.StringVar()
        self.bio_var = tk.StringVar()
        self.keywords_var = tk.StringVar()
        self.tags_var = tk.StringVar()

        self.text_frame = tk.Frame(root, bg='#001f3f')
        self.text_frame.grid(row=3, column=0, columnspan=2, sticky=tk.NSEW, padx=5, pady=5)

        self.content_text = tk.Text(self.text_frame, wrap=tk.WORD, bg='#001f3f', fg='white', insertbackground='white')
        self.content_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        self.scrollbar = tk.Scrollbar(self.text_frame, command=self.content_text.yview)
        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.content_text.config(yscrollcommand=self.scrollbar.set)

        self.word_count_label = tk.Label(root, text="Word Count: 0", bg='#001f3f', fg='white')
        self.word_count_label.grid(row=2, column=2, sticky=tk.W, padx=5, pady=5)

        self.versions = []

        self.create_widgets()
        self.bind_shortcuts()
        self.content_text.bind("<KeyRelease>", self.update_word_count)
#debating on wheter to rearrange location of keywords and tags.
    def create_widgets(self):
        tk.Label(self.root, text="Title:", bg='#001f3f', fg='white').grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        tk.Entry(self.root, textvariable=self.title_var, bg='#0074D9', fg='white').grid(row=0, column=1, sticky=tk.EW, padx=5, pady=5)

        tk.Label(self.root, text="Author:", bg='#001f3f', fg='white').grid(row=1, column=0, sticky=tk.W, padx=5, pady=5)
        tk.Entry(self.root, textvariable=self.author_var, bg='#0074D9', fg='white').grid(row=1, column=1, sticky=tk.EW, padx=5, pady=5)

        tk.Label(self.root, text="Date of Publish:", bg='#001f3f', fg='white').grid(row=2, column=0, sticky=tk.W, padx=5, pady=5)
        tk.Entry(self.root, textvariable=self.date_var, bg='#0074D9', fg='white').grid(row=2, column=1, sticky=tk.EW, padx=5, pady=5)

        tk.Label(self.root, text="Short Bio:", bg='#001f3f', fg='white').grid(row=4, column=0, sticky=tk.W, padx=5, pady=5)
        tk.Entry(self.root, textvariable=self.bio_var, bg='#0074D9', fg='white').grid(row=4, column=1, sticky=tk.EW, padx=5, pady=5)

        tk.Label(self.root, text="Keywords:", bg='#001f3f', fg='white').grid(row=5, column=0, sticky=tk.W, padx=5, pady=5)
        tk.Entry(self.root, textvariable=self.keywords_var, bg='#0074D9', fg='white').grid(row=5, column=1, sticky=tk.EW, padx=5, pady=5)

        tk.Label(self.root, text="Tags:", bg='#001f3f', fg='white').grid(row=6, column=0, sticky=tk.W, padx=5, pady=5)
        tk.Entry(self.root, textvariable=self.tags_var, bg='#0074D9', fg='white').grid(row=6, column=1, sticky=tk.EW, padx=5, pady=5)

        menu = tk.Menu(self.root, bg='black', fg='green')
        self.root.config(menu=menu)

        file_menu = tk.Menu(menu, bg='black', fg='green')
        menu.add_cascade(label="File", menu=file_menu)
        file_menu.add_command(label="New", command=self.new_file, accelerator="Ctrl+N")
        file_menu.add_command(label="Copy", command=self.copy_text, accelerator="Ctrl+C")
        file_menu.add_command(label="Paste", command=self.paste_text, accelerator="Ctrl+V")
        file_menu.add_command(label="Cut", command=self.cut_text, accelerator="Ctrl+X")
        file_menu.add_command(label="Save as .GMI", command=self.save_file, accelerator="Ctrl+S")
        file_menu.add_command(label="Load .GMI", command=self.load_file, accelerator="Ctrl+O")
        file_menu.add_command(label="Save as .GOPHER", command=self.save_gopher_file, accelerator="Ctrl+Shift+S")
        file_menu.add_command(label="Load .GOPHER", command=self.load_gopher_file, accelerator="Ctrl+Shift+L")
        file_menu.add_command(label="Save as .SPARTAN", command=self.save_gopher_file, accelerator="Ctrl+Shift+S")  
        file_menu.add_command(label="Load .SPARTAN", command=self.load_gopher_file, accelerator="Ctrl+Shift+L")
        file_menu.add_command(label="Export as Markdown", command=self.export_as_markdown)
        file_menu.add_command(label="Export as HTML", command=self.export_as_html)

        inserts_menu = tk.Menu(menu, bg='black', fg='green')
        menu.add_cascade(label="Inserts", menu=inserts_menu)
        inserts_menu.add_command(label="Insert Heading 1", command=lambda: self.insert_gemtext("# Heading"))
        inserts_menu.add_command(label="Insert Heading 2", command=lambda: self.insert_gemtext("## Sub-heading"))
        inserts_menu.add_command(label="Insert Heading 3", command=lambda: self.insert_gemtext("### Sub-subheading"))
        inserts_menu.add_command(label="Insert Link", command=self.insert_link)
        inserts_menu.add_command(label="Insert Quote", command=self.insert_quote)
        inserts_menu.add_command(label="Insert Preformatted Text", command=self.insert_preformatted_text)
        inserts_menu.add_command(label="Insert List Item", command=lambda: self.insert_gemtext("* List Item"))
        inserts_menu.add_command(label="Insert Input Spartan Only", command=lambda: self.insert_gemtext("= input field"))
        inserts_menu.add_command(label="Save Version", command=self.save_version)
        inserts_menu.add_command(label="Revert to Last Version", command=self.revert_version)

        search_menu = tk.Menu(menu, bg='black', fg='green')
        menu.add_cascade(label="Search", menu=search_menu)
        search_menu.add_command(label="Search Current Blog", command=self.search_posts)

        about_menu = tk.Menu(menu, bg='black', fg='green')
        menu.add_cascade(label="About", menu=about_menu)
        about_menu.add_command(label="About This Program", command=self.show_about)

        self.root.grid_rowconfigure(3, weight=1)
        self.root.grid_columnconfigure(1, weight=1)

    def show_about(self):
        about_message = (
            "Lunar Orbit Logbook, Blog Writer for Gemini Protocol\n"
            "Program Created by Brian Lynn Bentley\n"
            "Ophesian Group copyright 2026\n"
            "This application simplifies the creation of Gemlogs, "
            "allowing users to write, save, and export their blog posts "
            "in various formats."
        )
        messagebox.showinfo("About", about_message)

    def bind_shortcuts(self):
        self.root.bind('<Control-n>', lambda event: self.new_file())
        self.root.bind('<Control-c>', lambda event: self.copy_text())
        self.root.bind('<Control-v>', lambda event: self.paste_text())
        self.root.bind('<Control-x>', lambda event: self.cut_text())
        self.root.bind('<Control-s>', lambda event: self.save_file())
        self.root.bind('<Control-o>', lambda event: self.load_file())
        self.root.bind('<Control-S>', lambda event: self.save_gopher_file())
        self.root.bind('<Control-L>', lambda event: self.load_gopher_file())

    def new_file(self):
        self.content_text.delete("1.0", tk.END)
        self.title_var.set("")
        self.author_var.set("")
        self.date_var.set("")
        self.bio_var.set("")
        self.keywords_var.set("")
        self.tags_var.set("")
        self.word_count_label.config(text="Word Count: 0")

    def copy_text(self):
        self.root.clipboard_clear()
        text = self.content_text.get(tk.SEL_FIRST, tk.SEL_LAST)
        self.root.clipboard_append(text)

    def paste_text(self):
        try:
            text = self.root.clipboard_get()
            self.content_text.insert(tk.INSERT, text)
        except tk.TclError:
            messagebox.showwarning("Paste Error", "No text in clipboard to paste.")

    def cut_text(self):
        self.copy_text()
        self.content_text.delete(tk.SEL_FIRST, tk.SEL_LAST)

    def update_word_count(self, event=None):
        content = self.content_text.get("1.0", tk.END).strip()
        word_count = len(content.split()) if content else 0
        self.word_count_label.config(text=f"Word Count: {word_count}")

    def insert_gemtext(self, gemtext):
        self.content_text.insert(tk.END, f"{gemtext}\n")

    def insert_quote(self):
        quote = simpledialog.askstring("Insert Quote", "Enter your quote:")
        if quote:
            self.insert_gemtext(f"> {quote}")

    def insert_preformatted_text(self):
        preformatted_text = simpledialog.askstring("Insert Preformatted Text", "Enter your preformatted text:")
        if preformatted_text:
            self.insert_gemtext(f"```\n{preformatted_text}\n```")

    def insert_link(self):
        url = simpledialog.askstring("Insert Link", "Enter the URL:")
        label = simpledialog.askstring("Insert Link", "Enter the link label:")
        if url and label:
            self.content_text.insert(tk.END, f"=> {url}\t{label}\n")

    def save_file(self):
        content = self.get_content()
        if self.validate_fields():
            file_path = filedialog.asksaveasfilename(defaultextension=".gmi", filetypes=[("Gemini Files", "*.gmi")])
            if file_path:
                try:
                    with open(file_path, 'w') as file:
                        file.write(content)
                    self.save_as_json()
                    messagebox.showinfo("Success", "File saved successfully!")
                except IOError as e:
                    messagebox.showerror("File Error", f"An error occurred while saving the file: {e}")

    def load_file(self):
        file_path = filedialog.askopenfilename(filetypes=[("Gemini Files", "*.gmi")])
        if file_path and os.path.isfile(file_path):
            try:
                with open(file_path, 'r') as file:
                    content = file.read()
                self.set_content(content)
                messagebox.showinfo("Success", "File loaded successfully!")
            except IOError as e:
                messagebox.showerror("File Error", f"An error occurred while loading the file: {e}")

    def save_gopher_file(self):
        content = self.get_content()
        if self.validate_fields():
            file_path = filedialog.asksaveasfilename(defaultextension=".gopher", filetypes=[("Gopher Files", "*.gopher")])
            if file_path:
                try:
                    with open(file_path, 'w') as file:
                        file.write(content)
                    messagebox.showinfo("Success", "Gopher file saved successfully!")
                except IOError as e:
                    messagebox.showerror("File Error", f"An error occurred while saving the Gopher file: {e}")

    def load_gopher_file(self):
        file_path = filedialog.askopenfilename(filetypes=[("Gopher Files", "*.gopher")])
        if file_path and os.path.isfile(file_path):
            try:
                with open(file_path, 'r') as file:
                    content = file.read()
                self.set_content(content)
                messagebox.showinfo("Success", "Gopher file loaded successfully!")
            except IOError as e:
                messagebox.showerror("File Error", f"An error occurred while loading the Gopher file: {e}")

    def get_content(self):
        return f"## {self.title_var.get()}\n" \
               f"Author: {self.author_var.get()}\n" \
               f"Date: {self.date_var.get()}\n" \
               f"Bio: {self.bio_var.get()}\n" \
               f"Keywords: {self.keywords_var.get()}\n" \
               f"Tags: {self.tags_var.get()}\n\n" \
               f"{self.content_text.get('1.0', tk.END)}"

    def set_content(self, content):
        lines = content.splitlines()
        self.title_var.set(lines[0][3:])
        self.author_var.set(lines[1][7:])
        self.date_var.set(lines[2][6:])
        self.bio_var.set(lines[3][5:])
        self.keywords_var.set(lines[4][9:])
        self.tags_var.set(lines[5][6:])
        self.content_text.delete("1.0", tk.END)
        self.content_text.insert(tk.END, "\n".join(lines[7:]))

    def export_as_markdown(self):
        content = self.get_content()
        file_path = filedialog.asksaveasfilename(defaultextension=".md", filetypes=[("Markdown Files", "*.md")])
        if file_path:
            try:
                with open(file_path, 'w') as file:
                    file.write(content)
                messagebox.showinfo("Success", "File exported as Markdown successfully!")
            except IOError as e:
                messagebox.showerror("File Error", f"An error occurred while exporting the file: {e}")

    def export_as_html(self):
        content = self.get_content()
        html_content = f"<html><body><h1>{self.title_var.get()}</h1><p>Author: {self.author_var.get()}</p><p>Date: {self.date_var.get()}</p><p>Bio: {self.bio_var.get()}</p><p>Keywords: {self.keywords_var.get()}</p><p>Tags: {self.tags_var.get()}</p><div>{self.content_text.get('1.0', tk.END)}</div></body></html>"
        file_path = filedialog.asksaveasfilename(defaultextension=".html", filetypes=[("HTML Files", "*.html")])
        if file_path:
            try:
                with open(file_path, 'w') as file:
                    file.write(html_content)
                messagebox.showinfo("Success", "File exported as HTML successfully!")
            except IOError as e:
                messagebox.showerror("File Error", f"An error occurred while exporting the file: {e}")

    def save_as_json(self):
        json_data = {
            "title": self.title_var.get(),
            "author": self.author_var.get(),
            "date": self.date_var.get(),
            "bio": self.bio_var.get(),
            "keywords": self.keywords_var.get().split(','),
            "tags": self.tags_var.get().split(','),
            "content": self.content_text.get('1.0', tk.END).strip()
        }
        json_file_path = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("JSON Files", "*.json")])
        if json_file_path:
            try:
                with open(json_file_path, 'w') as json_file:
                    json.dump(json_data, json_file, indent=4)
                messagebox.showinfo("Success", "File saved as JSON successfully!")
            except IOError as e:
                messagebox.showerror("File Error", f"An error occurred while saving the JSON file: {e}")

    def save_version(self):
        self.versions.append(self.get_content())
        messagebox.showinfo("Version Saved", "Current version saved successfully!")

    def revert_version(self):
        if self.versions:
            last_version = self.versions.pop()
            self.set_content(last_version)
            messagebox.showinfo("Version Reverted", "Reverted to the last saved version.")
        else:
            messagebox.showwarning("No Versions", "No versions available to revert.")

    def search_posts(self):
        search_term = simpledialog.askstring("Search Current Document", "Enter keyword to search:")
        if search_term:
            content = self.content_text.get('1.0', tk.END)
            start_index = '1.0'
            self.content_text.tag_remove('highlight', '1.0', tk.END)
            
            while True:
                start_index = self.content_text.search(search_term, start_index, nocase=True, stopindex=tk.END)
                if not start_index:
                    break
                end_index = f"{start_index}+{len(search_term)}c"
                self.content_text.tag_add('highlight', start_index, end_index)
                start_index = end_index
            
            self.content_text.tag_config('highlight', background='yellow')
            if self.content_text.index('highlight.first') != '1.0':
                self.content_text.see('highlight.first')
                messagebox.showinfo("Search Result", f"Keyword '{search_term}' found in the post.")
            else:
                messagebox.showinfo("Search Result", f"Keyword '{search_term}' not found.")

    def validate_fields(self):
        if not self.title_var.get().strip():
            messagebox.showwarning("Validation Error", "Title cannot be empty.")
            return False
        if not self.author_var.get().strip():
            messagebox.showwarning("Validation Error", "Author cannot be empty.")
            return False
        if not self.date_var.get().strip():
            messagebox.showwarning("Validation Error", "Date cannot be empty.")
            return False
        if not self.bio_var.get().strip():
            messagebox.showwarning("Validation Error", "Bio cannot be empty.")
            return False
        return True

if __name__ == "__main__":
    root = tk.Tk()
    app = LunarOrbitLogbook(root)
    root.mainloop()


0 Kudos

Comments

Displaying 0 of 0 comments ( View all | Add Comment )