From a569cef44756b4024b6560ad137ddc6492d18e84 Mon Sep 17 00:00:00 2001 From: Dimitris Marakomihelakis Date: Sun, 20 Aug 2023 20:37:24 +0300 Subject: [PATCH] SnE --- .shellnotes.sh | 34 + def/defaults.sh | 13 +- docs/github/SECURITY.md | 23 - docs/setdefaults.txt | 26 +- docs/sne.md | 141 ++++ docs/system/requirements.txt | 5 +- install.sh | 18 +- .../logfile_.log | 0 set_defaults.sh | 1 + sne/.env | 0 sne/gist.py | 29 + sne/pastebin.py | 14 + sne/requirements.txt | 17 + sne/sne.py | 612 ++++++++++++++++++ util/.hidden/.resetshellnotes.sh | 13 +- util/shellnotes.sh | 1 + util/shellnotes/sd/sd-input1.txt | 2 +- 17 files changed, 903 insertions(+), 46 deletions(-) create mode 100755 .shellnotes.sh create mode 100644 docs/sne.md create mode 100644 logs/sne/logfile_.log create mode 100644 sne/.env create mode 100644 sne/gist.py create mode 100644 sne/pastebin.py create mode 100644 sne/requirements.txt create mode 100644 sne/sne.py diff --git a/.shellnotes.sh b/.shellnotes.sh new file mode 100755 index 0000000..7b677cc --- /dev/null +++ b/.shellnotes.sh @@ -0,0 +1,34 @@ + + + +#The first line is the user's default shell found during installation process. + +#The lines below must NOT be changed. +export DIR=$(pwd) + +. ~/.shellnotes/whatsnew.sh + +. ~/.shellnotes/util/getv.sh +. ~/.shellnotes/def/defaults.sh +. ~/.shellnotes/util/ChMkDir.sh +. ~/.shellnotes/util/quickread.sh +. ~/.shellnotes/util/quicknote.sh +. ~/.shellnotes/util/newnote.sh +. ~/.shellnotes/util/readnote.sh +. ~/.shellnotes/util/delnote.sh +. ~/.shellnotes/util/listnotes.sh +. ~/.shellnotes/util/findnote.sh +. ~/.shellnotes/util/findmisplacednote.sh +. ~/.shellnotes/util/renamenote.sh +. ~/.shellnotes/util/notewc.sh +. ~/.shellnotes/util/notegrep.sh +. ~/.shellnotes/util/clipnote.sh +. ~/.shellnotes/util/rmdups.sh + + + +. ~/.shellnotes/util/shellnotes.sh +. ~/.shellnotes/util/.hidden/.clearlogs.sh +. ~/.shellnotes/util/.hidden/.resetshellnotes.sh + + diff --git a/def/defaults.sh b/def/defaults.sh index b25021b..3ae120b 100644 --- a/def/defaults.sh +++ b/def/defaults.sh @@ -5,7 +5,18 @@ Released under the "All rights reserved" category. See the RIGHTS.txt file in /docs/github/ for its full text. info -NOTES_EDITOR="$(eval cat ~/.shellnotes/util/shellnotes/sd/sd-input1.txt)" +if [ $(cat "$HOME/.shellnotes/util/shellnotes/sd/sd-input1.txt") = ".shellnotes/sne/sne.py" ]; then + function NOTES_EDITOR() { + if [ -n "$1" ]; then + python3 $(cat $HOME/.shellnotes/util/shellnotes/sd/sd-input1.txt) "$1" + else + python3 $(cat $HOME/.shellnotes/util/shellnotes/sd/sd-input1.txt) + fi +} +else + NOTES_EDITOR() { eval cat $HOME/.shellnotes/util/shellnotes/sd/sd-input1.txt } +fi + QUICK_NOTES_EDITOR="$(eval cat ~/.shellnotes/util/shellnotes/sd/sd-input2.txt)" DEFAULT_PATH="$(eval echo $(cat ~/.shellnotes/util/shellnotes/sd/sd-input3.txt))" diff --git a/docs/github/SECURITY.md b/docs/github/SECURITY.md index e7e615d..e69de29 100644 --- a/docs/github/SECURITY.md +++ b/docs/github/SECURITY.md @@ -1,23 +0,0 @@ -# Security Policy - -## Supported Versions of shellnotes - - - -| Version | Supported | -| ------- | ------------------ | -| 3.0.x | yes | -| 2.5.x | yes* | -| 2.3.x | yes* | -| 2.0.x | yes* | -| 1.2.x | yes* | -| 1.1.x | yes* | -| 1.0.x | no | -| 0.1.x | no | - - *: Installation not supported - -## About updating - -Every new update is uploaded in this page. -If you want to check your version, type "shellnotes -v" and check if the version is supported. diff --git a/docs/setdefaults.txt b/docs/setdefaults.txt index 24202a0..b2b07ed 100644 --- a/docs/setdefaults.txt +++ b/docs/setdefaults.txt @@ -4,32 +4,32 @@ how to set defaults In shellnotes v.3.0 and later, there is a more dynamic and safe way of setting your default notes editors and folder. -Changing both default note editors and Note folder with the sd system: - -For <3.0 users, the command "shellnotes -sd" or "shellnotes --set-defaults" will redirect the user to set_defaults.sh, where he will be able to configure the default editors and folder safer, faster and more dynamically. +Changing both default note editors and Note folder with the -sd parameter: + -For <3.0 users, the command "shellnotes -sd" or "shellnotes --set-defaults" will run ~/.shellnotes/set_defaults.sh, where the user will be able to configure the default editors and folder. Step by step instructions to change defaults with sd: 1. After installing shellnotes, run "shellnotes --set-defaults" in your terminal. - 2. You will be redirected to ~/.shellnotes/set_defaults.sh, a runnable script. Select an option: + 2. Select an option: 1. Change default regular notes editor: Regular notes are taken with newnote() and read with readnote(). - Select option 1 and enter the name of the program. It should be a valid name and not a path. + Select option 1 and enter the name of the program. It should be a valid name and not a path. (i.e. "subl") 2. Change default quick notes editor: Regular notes are taken with quicknote() and read with quickread(). - Select option 2 and enter the name of the program. It should be a valid name and not a path. + Select option 2 and enter the name of the program. It should be a valid name and not a path. (i.e. "nano") 3. Change default notes folder: All shellnotes' commands use the $DEFAULT_PATH variable to save, read, delete, rename notes. This means that the Notes folder to store your notes must be a valid path. - The default notes folder as you may know is ~/Notes and is created at first terminal launch with shellnotes installed. + The default notes folder is ~/Notes and is created at first terminal launch with shellnotes installed. Select option 3 and enter the full path of the directory. It should be a valid absolute path and not a relative one. 4. View defaults: - sd gives the user the option to also view current defaults. + -sd gives the user the option to also view current defaults. Select option 4 and take a look at your defaults. - 3. When you're done, restart your terminal and the defaults will have been changed. - WARNING: If a name or a path is not valid in step 2, you will be reminded by the script and the + 3. When you're done, restart your terminal and the defaults will change. + WARNING: If a name or a path is not valid in step 2, the setting will fail. -For 3.0 and earlier users: (text from help page) +For 3.0 and earlier: Changing default editors manually: @@ -41,14 +41,14 @@ Changing default editors manually: problems may occur. Default editors: - for newnote: gedit (ubuntu default) + for newnote: SnE (Shellnotes Notes Editor, installed by default) for quicknote: nano (in-terminal experience) Suggested editors other than default: - for newnote: Sublime text, Atom + for newnote: Sublime text, Atom, gedit for quicknote: Vim Changing default Notes Folder manually: - Changing default folder manually is a feature enabled in shellnotes <2.5. In case the user wants to change the default folder to store/write/list/rename/read/delete his notes, he must change the DEFAULT_PATH value from defaults.sh in ~/.shellnotes/def/. This is a variable that the rest of the code uses to check the default folder for all the commands to make change in the right directory. This means that if you change the name of the path the variable is going to search for, the code will use your prefered folder. In case the path is incorrect, problems may occur. + Changing default folder manually is a possible in shellnotes <2.5. In case the user wants to change the default folder to store/write/list/rename/read/delete his notes, he must change the DEFAULT_PATH value from defaults.sh in ~/.shellnotes/def/. This is a variable that the rest of the code uses to check the default folder for all the commands to make change in the right directory. This means that if you change the name of the path the variable is going to search for, the code will use your prefered folder. In case the path is incorrect, problems may occur. Default path: Home/Notes/ diff --git a/docs/sne.md b/docs/sne.md new file mode 100644 index 0000000..b557af0 --- /dev/null +++ b/docs/sne.md @@ -0,0 +1,141 @@ +# SnE - Shellnotes Notes Editor Documentation + +## Table of Contents + +1. [Introduction](#introduction) +2. [Features](#features) +3. [Integration with Shellnotes](#integration-with-shellnotes) +4. [Usage](#usage) +5. [Logging System](#logging-system) +6. [Tools](#tools) +7. [Pasting Notes](#pasting-notes) +8. [Conclusion](#conclusion) + +## Introduction + +SnE (Shellnotes Notes Editor) is a text editor developed as part of the Shellnotes Project. Shellnotes is a terminal-based note-taking tool for Linux users. SnE provides users with a graphical interface to create, edit, and manage their notes efficiently via a user-friendly text editor. SnE is built using the Tkinter library and offers various features to enhance the note-taking experience. + +## Features + +SnE comes equipped with several features to facilitate note-taking and editing: + +- **Graphical User Interface:** SnE offers a graphical interface for creating and editing notes, making it user-friendly and accessible. + +- **Text Editing:** Users can input and edit text using standard text editing functionalities such as copy, paste, undo, redo, and find. + +- **Styling Options:** SnE allows users to customize the font family, font size, and font weight of their notes for better readability and personalization. + +- **Themes:** Users can choose from a selection of themes to customize the appearance of the editor to their liking. + +- **Character Count:** SnE displays a live character count at the bottom of the editor to help users keep track of their note length. + +- **Spell Check:** The editor includes a spell check feature that highlights misspelled words and suggests corrections. + +- **Text-to-Speech:** SnE can read selected text or the entire note aloud using a text-to-speech engine. + +- **Integration:** SnE seamlessly integrates with Shellnotes, allowing users to create and manage notes directly from their terminal, using the `newnote` command. + +## Integration with Shellnotes + +Users can launch SnE from the terminal using the command `newnote`, but they can also use it as a regular text editor by typing `NOTES_EDITOR` into the terminal. + +## Usage + +To launch SnE and start creating/editing notes, follow these steps: + +1. Open your terminal. + +2. Navigate to the directory where you have Shellnotes installed. + +3. Use the appropriate command to launch SnE: +`newnote` + + +4. The SnE window will appear, providing a text editor interface. + +5. Use the available features (copy, paste, save, etc.) to create and edit your notes. + +6. Customize the editor's font style, size, weight, and theme according to your preferences. + +7. When you're done editing, save your note and close SnE. + +## Logging System + +SnE incorporates a logging system to track events and errors. The log files are stored in the user's home directory under `~/.shellnotes/logs/sne`. The logging system helps diagnose issues, monitor usage, and improve the overall stability of the application. This is achieved by the help of the python-loguru library. + +## Tools + +### Text-to-Speech (Read Aloud) + +SnE provides a convenient Text-to-Speech (TTS) feature that allows you to have your notes read aloud. This feature is especially useful for proofreading, reviewing content, or simply listening to your notes. SnE uses the `pyttsx3` library to enable the TTS functionality. + +**Usage:** + +1. Select the text you want to hear read aloud within the editor. +2. Click on the "Tools" menu. +3. Choose the "Read Aloud" option. +4. The selected text will be converted into speech and played through your system's audio output. + +### Search & Replace (Using `notegrep`) + +SnE incorporates a powerful search and replace functionality using the `notegrep` tool. This tool allows you to search for specific patterns within your notes and replace them with new content. `notegrep` leverages regular expressions to perform advanced searches and replacements. + +**Usage:** + +1. Click on the "Tools" menu. +2. Select "Find" to search for specific text within the note. You can enter a keyword or phrase to search for. +3. Choose "Replace All..." to initiate a search and replace operation. This option lets you replace all occurrences of a particular text with a new value. +4. A separate window will appear, allowing you to specify the search pattern and the replacement text using regular expressions. +5. Once you've configured the search and replacement, click the "Replace" button to perform the operation. + +### Spell Check + +SnE includes a built-in spell check tool that helps you identify and correct spelling errors in your notes. It uses the `enchant` library to check the spelling of words against a dictionary. Misspelled words are highlighted, so that corrections can be made easily. + +**Usage:** + +1. Click on the "Tools" menu. +2. Choose the "Spell Check" option. +3. The spell check tool will scan the entire note for spelling errors. +4. Any misspelled words will be highlighted, usually with a red background. +5. To correct a misspelled word, right-click on it, and you'll be presented with suggested corrections. Select the appropriate correction to update the word. +6. Once the spell check is complete, your note will be free of spelling errors. + +These additional tools enhance the usability and functionality of SnE by offering capabilities such as text-to-speech conversion, advanced search and replace using regular expressions, and an integrated spell check feature. These tools contribute to an improved note-taking experience, ensuring accurate and high-quality content creation. + + +## Pasting Notes + +SnE offers the ability to paste your notes to external services for sharing or backup purposes. It provides integration with both Pastebin and GitHub Gists. + +### Pasting on Pastebin +To paste your note on Pastebin: + +1. Click on the "Tools" menu. + +2. Select "Paste on Pastebin." + +3. A link to the Pastebin page containing your note will be displayed. + +4. Optionally, click the "Copy" button to copy the link to your clipboard. + +### Pasting on GitHub Gists +To paste your note on GitHub Gists: + +1. Click on the "Tools" menu. + +2. Select "Paste on Github Gists." + +3. If prompted, ensure that your GitHub token (GH_TOKEN) is set using the `shellnotes --set-github-token` command. + +4. A link to the GitHub Gist containing your note will be displayed. + +5. Optionally, click the "Copy" button to copy the link to your clipboard. + +> By default, uploaded notes are private, but you can make them public on demand. + +## Conclusion + +SnE (Shellnotes Notes Editor) enhances the Shellnotes experience by providing a feature-rich graphical interface for creating, editing, and managing notes. With its user-friendly design and integration with Shellnotes, SnE offers a seamless note-taking solution for Linux users. Its versatile features, logging system, and options to paste notes to external services make it a powerful tool for efficient note management. + +For any issues, inquiries, or suggestions, please refer to the official Shellnotes documentation or reach out to the developer Dimitris Marakomichelakis. diff --git a/docs/system/requirements.txt b/docs/system/requirements.txt index f972632..44a38a5 100644 --- a/docs/system/requirements.txt +++ b/docs/system/requirements.txt @@ -10,11 +10,12 @@ Software requirements: -curl -git -Python 3.5 or later - -GNU g++ compiler for c++ v.9.3.0 or later -Solid Internet Connection required for updating (at least 5mbps) + *Note* All unmet software dependencies will be installed alongside shellnotes. + Hardware requirements (Minimum): -Processor : Intel® Core™ i3-L13G4 -RAM : 700 MB - -HDD/SSD : 500 MB free space + -HDD/SSD : 400 MB free space \ No newline at end of file diff --git a/install.sh b/install.sh index 08c5706..13122f3 100644 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ Released under the "All rights reserved" category. See the RIGHTS.txt file in /docs/github/ for its full text. info -VER_TO_INSTALL=3.5 #This is used when updating, it will be changed in every new update +VER_TO_INSTALL=4.0 #This is used when updating, it will be changed in every new update if [ -e ~/.shellnotes/.shellnotes.sh ]; then echo "Shellnotes is already installed." echo "If you want to update, please run 'update.sh'." @@ -20,6 +20,7 @@ else mv def ~/.shellnotes/ mv logs ~/.shellnotes/ mv ver ~/.shellnotes/ + mv sne ~/.shellnotes/ mv image ~/.shellnotes/ mv shellnotes.sh ~/.shellnotes/.shellnotes.sh mv $0 ~/.shellnotes/ @@ -38,15 +39,26 @@ else sudo cp ~/.shellnotes/docs/manpages/* /usr/local/man/man1/ sudo gzip -f /usr/local/man/man1/*.1 sudo mandb >/dev/null + sudo apt-get install xclip - sudo apt install python3 - sudo apt install python3-pip + sudo apt install python3 -y + sudo apt install python3-pip -y pip install pathlib sudo apt update sudo chmod a+x ~/.shellnotes/util/exec/* sudo chmod +x ~/.shellnotes/.shellnotes.sh + clear + echo "Installing Shellnotes Note Editor (SnE)..." + + echo "DEFAULT_PATH=$HOME" > ~/.shellnotes/sne/.env + #Install dependencies + pip install -r ~/.shellnotes/sne/requirements.txt + sudo apt-get install espeak -y + + + echo "$VER_TO_INSTALL" > ~/.shellnotes/ver/.shellnotes_version if [ -n "`$SHELL -c 'echo $ZSH_VERSION'`" ]; then diff --git a/logs/sne/logfile_.log b/logs/sne/logfile_.log new file mode 100644 index 0000000..e69de29 diff --git a/set_defaults.sh b/set_defaults.sh index 8d3fb94..83a49bc 100644 --- a/set_defaults.sh +++ b/set_defaults.sh @@ -71,6 +71,7 @@ elif [[ "$input" == "3" ]]; then else mv $DEFAULT_PATH/* $name echo "$name" > ~/.shellnotes/util/shellnotes/sd/sd-input3.txt + echo "DEFAULT_PATH=$name" >> ~/.shellnotes/sne/.env echo "Default notes folder updated." echo "Please restart your terminal." fi diff --git a/sne/.env b/sne/.env new file mode 100644 index 0000000..e69de29 diff --git a/sne/gist.py b/sne/gist.py new file mode 100644 index 0000000..4215acc --- /dev/null +++ b/sne/gist.py @@ -0,0 +1,29 @@ +import os +import sys +import json +import datetime +import requests + +def create_gist_with_requests(headers, body): + response = requests.post('https://api.github.com/gists', json=body, headers=headers) + if response.ok: + response_data = response.json() + return response_data["html_url"] + else: + raise RuntimeError(f"Github API error: {response.status_code}") + +def create_gist(text, token): + today = datetime.date.today() + headers = { + 'Authorization': f'Bearer {token}', + 'User-agent': 'python-gist', + 'Accept': 'application/json', + 'X-GitHub-Api-Version': '2022-11-28' + } + body = { + "description": f"Uploaded on {today} via SnE", + "files": {f"{today}.txt": {"content": text}}, + "public": False + } + + return create_gist_with_requests(headers, body) diff --git a/sne/pastebin.py b/sne/pastebin.py new file mode 100644 index 0000000..301d1f2 --- /dev/null +++ b/sne/pastebin.py @@ -0,0 +1,14 @@ +import requests + + +def pastebin(text): + api_key = "d2004f9b24624585334cc3950ab6a7e0" + url = "https://pastebin.com/api/api_post.php" + payload = { + "api_dev_key": api_key, + "api_paste_code": text, + "api_paste_private": 0, + "api_option": "paste" + } + res = requests.post(url, data=payload) + return (res.text) diff --git a/sne/requirements.txt b/sne/requirements.txt new file mode 100644 index 0000000..cf4a914 --- /dev/null +++ b/sne/requirements.txt @@ -0,0 +1,17 @@ +astroid==2.3.3 +isort==4.3.21 +lazy-object-proxy==1.4.3 +mccabe==0.6.1 +Pillow==7.0.0 +pycodestyle==2.5.0 +pyenchant==2.0.0 +Pygments==2.5.2 +pylint==2.4.4 +pyttsx3==2.71 +six==1.13.0 +ttkthemes==2.4.0 +typed-ast==1.4.0 +wrapt==1.11.2 +python-dotenv==1.0.0 +loguru==0.7.0 + diff --git a/sne/sne.py b/sne/sne.py new file mode 100644 index 0000000..977c20b --- /dev/null +++ b/sne/sne.py @@ -0,0 +1,612 @@ +""" +SnE - Shellnotes Notes Editor +Developed by Dimitris Marakomichelakis +""" + +import tkinter as tk +from tkinter import ttk +from tkinter import * +from functools import partial +from tkinter import messagebox +from tkinter import filedialog +from tkinter import simpledialog +import tkinter.font as tkFont +from pathlib import Path +import sys +import os +import time +import pyttsx3 +import enchant +from ttkthemes import ThemedTk +import pastebin +from dotenv import load_dotenv +from gist import create_gist +from loguru import logger + +load_dotenv() #.env must contain the GH_TOKEN variable, set via shellnotes --set-github-token +default_path = os.environ.get("DEFAULT_PATH") + +def _log(level, message): + log_dir = os.path.expanduser("~/.shellnotes/logs/sne") # Use a valid path for the log directory + os.makedirs(log_dir, exist_ok=True) # Create the directory if it doesn't exist + log_path = os.path.join(log_dir, f"logfile_{time}.log") + logger.add(log_path) + # print(log_path) + + if level == "TRACE": + logger.trace(message) + elif level == "DEBUG": + logger.debug(message) + elif level == "INFO": + logger.info(message) + elif level == "SUCCESS": + logger.success(message) + elif level == "WARNING": + logger.warning(message) + elif level == "ERROR": + logger.error(message) + elif level == "CRITICAL": + logger.info(message) + else: + return "Failed to log" + + + +class Container(Text): + """ + A TextWidget with horizontal and vertical scrollbars + """ + + def __init__(self, master=None, **kw): + self.frame = ttk.Frame(master) + self.vbar = ttk.Scrollbar(self.frame, command=self.yview) + self.vbar.pack(side=RIGHT, fill=Y) + self.hbar = ttk.Scrollbar( + self.frame, orient="horizontal", command=self.xview) + self.hbar.pack(side=BOTTOM, fill=X) + kw.update({'yscrollcommand': self.vbar.set}) + kw.update({'xscrollcommand': self.hbar.set}) + Text.__init__(self, self.frame, **kw) + self.pack(side=LEFT, fill=BOTH, expand=True) + text_meths = vars(Text).keys() + methods = vars(Pack).keys() | vars(Grid).keys() | vars(Place).keys() + methods = methods.difference(text_meths) + + for m in methods: + if m[0] != '_' and m != 'config' and m != 'configure': + setattr(self, m, getattr(self.frame, m)) + + def __str__(self): + return str(self.frame) + + +class Editor: + """ + Base Class of Editor + """ + window = ThemedTk() + style = ttk.Style() + window.title("SnE") + # window.geometry( + # "{0}x{1}+0+0".format(window.winfo_screenwidth(), + # window.winfo_screenheight())) + window.geometry("500x500") + menuBar = Menu(window) + #The text and entry frames column + window.grid_columnconfigure(1, weight=1) + window.grid_rowconfigure(0, weight=1) + #Menu bar + window.config(menu=menuBar) + fileMenu = Menu(menuBar, tearoff=0) + editMenu = Menu(menuBar, tearoff=0) + viewMenu = Menu(menuBar, tearoff=0) + toolMenu = Menu(menuBar, tearoff=0) + helpMenu = Menu(menuBar, tearoff=0) + txt = Container(window, undo=True) + txt.grid(row=0, column=1, sticky="NSEW") + lineNumber = Canvas(window, width="30", height="500") + lineNumber.grid(row=0, column=0, sticky='NS', pady=1, rowspan=3) + charCount = StringVar() + charCount.set("Characters: 0") + statusBar = ttk.Label(window, textvariable=charCount) + statusBar.grid(row=2, column=1, columnspan=2, sticky="EW") + txt['wrap'] = 'none' + fontType = "Helvetica" + fontSize = "15" + fontColor = "black" + fontWeight = "normal" + txt.configure(font=(fontType, fontSize, fontWeight)) + currentFile = "No File" + + def __init__(self): + # Disable default bindings for Ctrl+C and Ctrl+V + self.txt.unbind("") + self.txt.unbind("") + + self.fileMenu.add_command( + label="New", command=self.new_file, accelerator="Ctrl+N") + self.window.bind_all('', self.new_file) + # self.fileMenu.add_command( + # label="Open", command=self.open_file, accelerator="Ctrl+O") + # self.window.bind_all('', self.open_file) + self.fileMenu.add_command( + label="Save", command=self.save_file, accelerator="Ctrl+S") + self.window.bind_all('', self.save_file) + self.fileMenu.add_command( + label="Save As", command=self.save_file_as, + accelerator="Ctrl+Shift+S") + self.window.bind_all('', self.save_file_as) + self.fileMenu.add_command(label="Exit", command=self.exit) + self.menuBar.add_cascade(label="File", menu=self.fileMenu) + self.editMenu = Menu(self.menuBar, tearoff=0) + self.editMenu.add_command(label="Cut", command=self.cut) + self.editMenu.add_command( + label="Copy", command=self.copy, accelerator="Ctrl+C") + self.window.bind_all('', self.copy) + self.editMenu.add_command( + label="Paste", command=self.paste, accelerator="Ctrl+V") + self.window.bind_all('', self.paste) + self.editMenu.add_command( + label="Undo", command=self.undo, accelerator="Ctrl+Z") + self.window.bind_all('', self.undo) + self.editMenu.add_command( + label="Redo", command=self.redo, accelerator="Ctrl+R") + self.window.bind_all('', self.redo) + + + self.toolMenu = Menu(self.menuBar, tearoff=0) + self.toolMenu.add_command( + label="Find", command=self.find, accelerator="Ctrl+F") + self.window.bind_all('', self.find) + self.toolMenu.add_command( + label="Replace All...", command=self.replace, + accelerator="Ctrl+Shift+R") + self.window.bind_all('', self.replace) + self.toolMenu.add_command( + label="Paste on Pastebin", command=self.paste_on) + self.toolMenu.add_command( + label="Paste on Github Gists", command=self.gist) + self.toolMenu.add_command(label="Read Aloud", command=self.speak) + self.toolMenu.add_command( + label="Spell Check", command=self.spell_check) + + self.menuBar.add_cascade(label="Edit", menu=self.editMenu) + self.menuBar.add_cascade(label="View", menu=self.viewMenu) + self.menuBar.add_cascade(label="Tools", menu=self.toolMenu) + self.helpMenu.add_command(label="About", command=self.about) + self.menuBar.add_cascade(label="Help", menu=self.helpMenu) + self.window.bind_all('', self.redraw) + self.window.bind_all('', self.redraw) + self.window.bind_all('', self.redraw) + self.window.bind_all('', self.redraw) + self.window.bind_all('', self.redraw) + self.window.bind_all('', self.redraw) + self.window.bind_all('', self.redraw) + self.editMenu.add_command( + label="Select All", command=self.selectall, accelerator="Ctrl+A") + self.window.bind_all('', self.selectall) + + fontFamily = Menu(self.viewMenu) + fontFamily.add_command(label="Helvetica (Default)", command=partial( + self.change_font_family, "Helvetica")) + fontFamily.add_command(label="Ubuntu", command=partial( + self.change_font_family, "Ubuntu")) + fontFamily.add_command(label="Times New Roman", command=partial( + self.change_font_family, "Times New Roman")) + fontFamily.add_command(label="Comic Sans MS", command=partial( + self.change_font_family, "Comic Sans MS")) + fontFamily.add_command(label="Terminal", command=partial( + self.change_font_family, "Terminal")) + self.viewMenu.add_cascade(label="Font Family", menu=fontFamily) + + self.viewMenu.add_command(label="Font Size", command=self.change_font_size) + + # self.viewMenu.add_command(label="Font Weight", command=self.change_font_weight) + fontWeightMenu = Menu(self.viewMenu) + fontWeightMenu.add_command(label="Normal", command=lambda: self.change_font_weight("normal")) + fontWeightMenu.add_command(label="Bold", command=lambda: self.change_font_weight("bold")) + self.viewMenu.add_cascade(label="Font Weight", menu=fontWeightMenu) + + + themeBar = Menu(self.viewMenu) + themeBar.add_command(label="Black", command=partial( + self.change_theme, "black")) + themeBar.add_command(label="White", command=partial( + self.change_theme, "white")) + themeBar.add_command(label="Aqua", command=partial( + self.change_theme, "aqua")) + themeBar.add_command(label="Matrix", command=partial( + self.change_theme, "matrix")) + themeBar.add_command(label="SnE Original", command=partial( + self.change_theme, "sne")) + self.viewMenu.add_cascade(label="Themes", menu=themeBar) + + if len(sys.argv) > 1: + self.open_specific_file(f"{default_path}/{sys.argv[1]}") # Open the specified file + + + self.window.mainloop() + + def new_file(self, event=None): + if(messagebox.askyesno("Save?", "Do you wish to save current file?")): + self.save_file() + self.txt.delete('1.0', END) + self.window.title("SnE - New File") + self.currentFile = "No File" + else: + self.txt.delete('1.0', END) + self.window.title("SnE") + self.currentFile = "No File" + + def open_file(self, event=None): + # print("Opening file") + myFile = filedialog.askopenfile( + parent=self.window, mode="rb", title="Open a Note") + if myFile is not None: + self.window.title(os.path.basename(myFile.name)) + content = myFile.read() + self.txt.delete('1.0', END) + self.txt.insert(1.0, content) + self.currentFile = myFile.name + _log("SUCCESS", f"Opened Note {self.currentFile}") + myFile.close() + self.redraw(event) + else: + _log("ERROR", f"Note filename is empty ({self.currentFile=})") + + def open_specific_file(self, file_path): + try: + with open(file_path, "rb") as myFile: + self.window.title(os.path.basename(myFile.name)) + content = myFile.read() + self.txt.delete('1.0', END) + self.txt.insert(1.0, content) + self.currentFile = myFile.name + _log("SUCCESS", f"Opened Note {self.currentFile}") + self.redraw(event) + except Exception as e: + _log("ERROR", f"Failed to open (specific) file: {str(e)}") + + + # def save_file_as(self, event=None): + # # print("Saving file") + # myFile = filedialog.asksaveasfile(mode="w") + # if myFile is not None: + # myFile.write(self.txt.get('1.0', END)) + # self.currentFile = myFile.name + # _log("SUCCESS", f"Saved New Note {self.currentFile}") + # myFile.close() + # self.window.title(os.path.basename(myFile.name)) + + + def save_file_as(self, event=None): + try: + # Read initial_directory from sd-input3.txt + with open(os.path.expanduser("~/.shellnotes/util/shellnotes/sd/sd-input3.txt")) as f: + initial_directory = f.read().strip() + + # Prompt the user for a file name + file_name = simpledialog.askstring("Save As", "Enter a file name:") + if not file_name: + _log("ERROR", f"No note name specified ({file_name=})") + return + + # Construct the full file path using initial_directory and file_name + full_directory = os.path.expanduser(initial_directory) + file_path = os.path.join(full_directory, file_name) + + # Open and write to the file + with open(file_path, "w+") as myFile: + myFile.write(self.txt.get('1.0', END)) + self.currentFile = file_path + _log("SUCCESS", f"Saved New Note {self.currentFile}") + self.window.title(os.path.basename(self.currentFile)) + except Exception as e: + # Handle any errors that might occur while saving + _log("ERROR", f"Failed to save: {str(e)}") + + + def save_file(self, event=None): + # print(self.currentFile) + if (self.currentFile == "No File"): + self.save_file_as(event) + else: + myFile = open(self.currentFile, "w") + myFile.write(self.txt.get('1.0', END)) + _log("SUCCESS", f"Saved Note {self.currentFile}") + myFile.close() + + + + def copy(self, event=None): + # print("copying") + self.txt.clipboard_clear() + self.txt.clipboard_append(self.txt.selection_get()) + _log("INFO", f"Copied: {self.txt.selection_get()}") + + def cut(self, event=None): + self.copy() + self.txt.delete(SEL_FIRST, SEL_LAST) + + def paste(self, event=None): + try: + # Delete the selected text, if any + sel_start = self.txt.index("sel.first") + sel_end = self.txt.index("sel.last") + if sel_start and sel_end: + self.txt.delete(sel_start, sel_end) + + # Paste the clipboard content at the cursor position + self.txt.insert(INSERT, self.txt.clipboard_get()) + self.redraw(event) + except: + pass + + def undo(self, event=None): + self.txt.edit_undo() + + def redo(self, event=None): + self.txt.edit_redo() + + def find(self, event=None): + root = Toplevel(self.window) + root.title("notegrep") + root.transient(self.window) + root.focus_force() + root.grid_columnconfigure(0, weight=1) + root.grid_rowconfigure(0, weight=1) + e1 = ttk.Entry(root) + e1.grid(row=0, column=0, pady="10", + padx="10", columnspan=2, sticky="EW") + + def sub(): + findString = e1.get() + self.set_mark(findString) + + def on_closing(): + self.txt.tag_delete('highlight') + root.destroy() + + findBtn = ttk.Button(root, text="Find...", command=sub) + findBtn.grid(row=1, column=0, pady="10", padx="10", sticky="EWS") + closeBtn = ttk.Button(root, text="Close", command=on_closing) + closeBtn.grid(row=1, column=1, pady="10", padx="10", sticky="EWS") + root.protocol("WM_DELETE_WINDOW", on_closing) + + def paste_on(self, event=None): + def copy_link(self, link): + self.txt.clipboard_clear() + self.txt.clipboard_append(link) + root = Toplevel(self.window) + root.title("PasteBin Link") + root.transient(self.window) + root.focus_force() + root.grid_columnconfigure(0, weight=1) + root.grid_rowconfigure(0, weight=1) + link = pastebin.pastebin(self.txt.get('1.0', END)) + _log("SUCCESS", f"Uploaded Note to pastebin ({link})") + lb = ttk.Label(root, text=link) + lb.grid(row=0, column=0, padx="50", pady="20") + bt = ttk.Button(root, text="Copy", command=copy_link(self, link)) + bt.grid(row=1, column=0, padx="50", pady="20") + + + def gist(self, event=None): + def copy_link(link): + self.txt.clipboard_clear() + self.txt.clipboard_append(link) + try: + text = self.txt.get('1.0', END) + token = os.getenv("GH_TOKEN") + if not token: + messagebox.showerror("Error", "GH_TOKEN not set. You can set it using the shellnotes --set-github-token") + _log("ERROR", "No github token.") + + except: + raise RuntimeError("GH_TOKEN not set") + + + + try: + link = create_gist(text, token) + _log("SUCCESS", f"Uploaded Note to Github Gists ({link})") + root = Toplevel(self.window) + root.title("Github Gists Link") + root.transient(self.window) + root.focus_force() + root.grid_columnconfigure(0, weight=1) + root.grid_rowconfigure(0, weight=1) + + lb = ttk.Label(root, text=link) + lb.grid(row=0, column=0, padx="50", pady="20") + bt = ttk.Button(root, text="Copy", command=lambda: copy_link(link)) + bt.grid(row=1, column=0, padx="50", pady="20") + + except Exception as e: + messagebox.showerror("Error", str(e)) + _log("ERROR", f"Couldn't upload to Github Gists ({str(e)})") + + + def selectall(self, event=None): + self.txt.tag_add('sel', '1.0', 'end') + return "break" + + def set_mark(self, findString): + print("Coming to set mark") + self.find_string(findString) + self.txt.tag_config('highlight', foreground='red') + self.txt.focus_force() + + def find_string(self, findString): + startInd = '1.0' + while(startInd): + startInd = self.txt.search(findString, startInd, stopindex=END) + if startInd: + startInd = str(startInd) + lastInd = startInd+f'+{len(findString)}c' + print(startInd, lastInd) + self.txt.tag_add('highlight', startInd, lastInd) + startInd = lastInd + + def replace(self, event=None): + # print("About to replace using notegrep") + root = Toplevel(self.window) + root.title("Find and Replace") + root.transient(self.window) + root.focus_force() + root.grid_columnconfigure(0, weight=1) + root.grid_rowconfigure(0, weight=1) + e1 = ttk.Entry(root) + e1.grid(row=0, column=0, pady=5, columnspan=2, padx=10) + e2 = ttk.Entry(root) + e2.grid(row=1, column=0, pady=5, columnspan=2, padx=10) + + def find(): + findString = e1.get() + self.set_mark(findString) + + def replace(): + findString = e1.get() + replaceString = e2.get() + myText = self.txt.get('1.0', END) + myText = myText.replace(findString, replaceString) + self.txt.delete('1.0', END) + self.txt.insert('1.0', myText) + root.destroy() + + def on_closing(): + self.txt.tag_delete('highlight') + root.destroy() + + findButton = ttk.Button(root, text="Find", command=find) + replaceButton = ttk.Button(root, text="Replace", command=replace) + findButton.grid(row=2, column=0, padx=10, pady=5) + replaceButton.grid(row=2, column=1, padx=10, pady=5) + root.protocol("WM_DELETE_WINDOW", on_closing) + + def redraw(self, event=NONE): + self.update_count(event) + self.lineNumber.delete("all") + self.objectIds = [] + si = self.txt.index("@0,0") + while True: + dline = self.txt.dlineinfo(si) + if dline is None: + break + y = dline[1] + liNum = str(si).split(".")[0] + self.lineNumber.create_text( + 2, y, anchor="nw", text=liNum, fill=self.fontColor) + si = self.txt.index(f"{si}+1line") + + def update_count(self, event): + count = self.txt.get('1.0', END) + self.charCount.set(f"Characters: {len(count)-1}") + + + + def speak(self): + """Read selected text aloud. Reads the whole note if nothing is selected.""" + engine = pyttsx3.init() + try: + selected_text = self.txt.selection_get() + engine.say(selected_text) + except tk.TclError: + # If no text is selected, read the entire content of the text widget + engine.say(self.txt.get("1.0", END)) + engine.runAndWait() + + def spell_err(self, findString): + """Check for Spelling Errors""" + startInd = '1.0' + while True: + startInd = self.txt.search(findString, startInd, stopindex=END, nocase=True) + if not startInd: + break + endInd = f"{startInd}+{len(findString)}c" + self.txt.tag_add('misspelled', startInd, endInd) + startInd = endInd + + def spell_check(self, event=NONE): + print("Running Spell check") + self.txt.tag_delete('misspelled') + words = set(self.txt.get('1.0', "end-1c").split()) # Use a set to store unique words + for word in words: + if not self.word_exist(word): + self.spell_err(word) + + self.txt.tag_config('misspelled', background="red", foreground="white") + + + def word_exist(self, word): + d = enchant.Dict("en_US") + return d.check(word) + + def change_theme(self, theme): + if (theme == "black"): + self.fontColor = "white" + self.window['theme'] = 'black' + self.txt.config(bg="black", fg="white", insertbackground="white") + self.txt['fg'] = 'white' + self.lineNumber.config(bg="black") + self.menuBar.config(bg="black", fg="white") + pass + elif (theme == "white"): + self.fontColor = "black" + self.window['theme'] = 'aquativo' + self.lineNumber.config(bg="white") + self.txt.config(bg="white", fg="black", insertbackground="black") + self.menuBar.config(bg="white", fg="black") + pass + elif (theme == "matrix"): + self.fontColor = "black" + self.window['theme'] = 'black' + self.lineNumber.config(bg="green") + self.txt.config(bg="black", fg="green", insertbackground="white") + self.menuBar.config(bg="green", fg="black", relief=RAISED) + elif (theme == "aqua"): + self.fontColor = "green" + self.window['theme'] = 'arc' + self.lineNumber.config(bg="#9de1fd") + self.txt.config(bg="white", fg="black", insertbackground="black") + self.menuBar.config(bg="#9de1fd", fg="black", relief=RAISED) + elif (theme == "sne"): + self.fontColor = "black" + self.window['theme'] = 'kroc' + self.lineNumber.config(bg="#eaddca") + self.txt.config(bg="#997950", fg="black", insertbackground="black") + self.menuBar.config(bg="black", fg="#eaddca", relief=RAISED) + + def change_font_size(self): + """Adjust Font Size""" + new_font_size = simpledialog.askstring( + "Size", "Enter font size", parent=self.window) + if new_font_size is not None: + self.fontSize = new_font_size + self.update_font() + + def change_font_family(self, fontType): + """Change Font Family""" + self.fontType = fontType + self.update_font() + + def change_font_weight(self, fontWeight): + """Adjust Font Weight""" + self.fontWeight = fontWeight + self.update_font() + + + def update_font(self): + self.txt.configure(font=(self.fontType, self.fontSize, self.fontWeight)) + + + def about(self): + # print("Showing About Page") + messagebox.showinfo("Shellnotes Note Editor (SNE)", "An editor used for viewing and editing your notes.\nVersion: 1.0") + + def exit(self): + if(messagebox.askyesno('Quit', 'Are you sure you want to quit?')): + self.window.destroy() + + +a = Editor() diff --git a/util/.hidden/.resetshellnotes.sh b/util/.hidden/.resetshellnotes.sh index 04a30b1..c76a51d 100644 --- a/util/.hidden/.resetshellnotes.sh +++ b/util/.hidden/.resetshellnotes.sh @@ -10,16 +10,18 @@ function resetshellnotes() { proceed() { echo "y\n" | clearlogs >/dev/null echo "" > ~/.shellnotes/util/listnotes/output/* - echo "/usr/bin/gedit" > ~/.shellnotes/util/shellnotes/sd/sd-input1.txt + echo ".shellnotes/sne/sne.py" > ~/.shellnotes/util/shellnotes/sd/sd-input1.txt echo "/bin/nano"> ~/.shellnotes/util/shellnotes/sd/sd-input2.txt echo "~/Notes" > ~/.shellnotes/util/shellnotes/sd/sd-input3.txt reset=" #The first line is the user's default shell found during installation process. -#Warning! If you don't have gedit(text editor) or nano installed, you may have to change the default text editor (lines 10, 25, 40). #The lines below must NOT be changed. export DIR="$(pwd)" + +.~/.shellnotes/whatsnew.sh + . ~/.shellnotes/util/getv.sh . ~/.shellnotes/def/defaults.sh . ~/.shellnotes/util/ChMkDir.sh @@ -34,6 +36,8 @@ export DIR="$(pwd)" . ~/.shellnotes/util/renamenote.sh . ~/.shellnotes/util/notewc.sh . ~/.shellnotes/util/notegrep.sh +. ~/.shellnotes/util/clipnote.sh +. ~/.shellnotes/util/rmdups.sh @@ -44,6 +48,9 @@ export DIR="$(pwd)" " echo $reset > ~/.shellnotes/.shellnotes.sh + # mv ~/.shellnotes/.shellnotes.sh ~/.shellnotes/shellnotes.sh + rm -rf ~/.shellnotes/**/__pycache__ + echo "" > ~/.shellnotes/**/.env } case $1 in -f | --force ) @@ -53,7 +60,7 @@ export DIR="$(pwd)" esac echo "Be aware that this command will erase shellnotes' memory." - echo "This contains default editors and folder, logfiles and user data and cache." + echo "This contains default editors and folder, logfiles, user data and cache." echo "Your Notes will not be removed." echo -n "Proceed? " && read input case $input in y|Yes|Y|YES|yes) diff --git a/util/shellnotes.sh b/util/shellnotes.sh index 88a243c..073d5c9 100644 --- a/util/shellnotes.sh +++ b/util/shellnotes.sh @@ -14,6 +14,7 @@ function shellnotes() { . ~/.shellnotes/util/shellnotes/au/au.sh . ~/.shellnotes/util/shellnotes/sd/sd.sh . ~/.shellnotes/util/shellnotes/i/i.sh + . ~/.shellnotes/util/shellnotes/gh_token/gh_token.sh if [[ $# > 1 ]]; then echo "Too many arguments." elif [[ $# = 0 ]];then diff --git a/util/shellnotes/sd/sd-input1.txt b/util/shellnotes/sd/sd-input1.txt index 19668ca..0441b70 100644 --- a/util/shellnotes/sd/sd-input1.txt +++ b/util/shellnotes/sd/sd-input1.txt @@ -1 +1 @@ -/usr/bin/gedit +.shellnotes/sne/sne.py