;;; nob.el --- custom bookmarks for dealing with novels in eww. -*- lexical-binding: t; -*- ;; Copyright (C) 2019 Robert Rose ;; Author: Robert Rose ;; Keywords: lisp eww novel bookmark ;; Version: 0.0.1 ;; Package-Requires ((dash "2.15.0) (emacs "24") (ivy "20190214.1032") ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; This package provides functions for saving and selecting novels for ;; for reading in eww. I wrote nob because bookmarking a chapter in eww ;; and then always having to remove old bookmarks is ridiculous. nov-save ;; works by queryinh the bookmark list a pattern that matches the current ;; url. If it finds a match, it updates the title and url and moves it to ;; the front of the list. This makes shoosing what to to read next, and ;; picking up where you left off much easier. The nob-select function ;; provides an easy to navigate pop up list. ;;; Code: (require 'eww) (require 'eww-http-code-advice) (require 'dash) (require 'ivy) (defgroup nob nil "Settings for nob." :group 'novels) (defcustom nob-bookmarks-file (concat user-emacs-directory "nob-bookmarks.el") "Location of the file nob uses for bookmark storage." :group 'nob) (defcustom nob-autosave-on-render t "Adds `nob-eww-after-render-hook' to `eww-after-render-hook' (locally) when `nob-mode' is enabled." :type 'boolean :group 'nob) (defmacro with-nob (&rest body) "Execute body with variable: `novels' set." `(let ((novels (with-temp-buffer (insert-file-contents nob-bookmarks-file) (read (buffer-string))))) (unwind-protect ,@body))) (defmacro with-eww-buffer (&rest body) "Execute body with variables: `title' and `url' set." `(let ((title (plist-get eww-data :title)) (url (plist-get eww-data :url))) (unwind-protect ,@body))) ;;; I will need to explore options for replacing `--map-first' ;;; Maybe it's as simple as popping and pushing. That would be nice. (defun nob-update-bookmark (novels title url) "Update an existing bookmark. Returns updated novels list.' This function has no side affects." (--map-first (string-match (car it) url) `(,(car it) ,title ,url) (--sort (string-match (car it) url) novels))) (defun nob-write-file (novels) (let ((inhibit-message 1)) (with-temp-buffer (insert (pp novels)) (write-file nob-bookmarks-file)))) (defun nob-convert-bookmarks () "Used to convert bookmarks file to the new format. (cons pattern (cons title url)) As I've started hacking away at nob mode, I've come to realize that I can't simply rewrite the functions. I'll need some scafolding in place so that nob remains functional while I continue working on it." (interactive)) (with-temp-file (concat user-emacs-directory "nob-bookmarks") (with-nob (let* ((n (mapcar (lambda (x) (cons (car x) (cons (cadr x) (caddr x)))) novels))) (insert (pp n)) ))) (defun nob-save () "Saves current novel." (interactive) (with-nob (with-eww-buffer (let ((n (--first (string-match (car it) url) novels))) (if n (if (not (string-equal (nth 2 n) url)) (progn (nob-write-file (nob-update-bookmark novels title url)) (message "Bookmarked: %s" title)) (message "Already Bookmarked")) (if (y-or-n-p "Add this novel?") (progn (nob-write-file (push `(,(read-string "Match: " url) ,title ,url) novels)) (message "Bookmarked: %s" title))) )) )) ) (defun nob-select () "Select novel for reading in `eww'." (interactive) ;; ;; version two ;; `assoc' is a wonderful thing. ;; originally required `--map' and `--first' from `dash.el' ;; okay, still using `--map' (with-nob ; novels: ((pattern title url)) (let* ((n (--map (cons (cadr it) (caddr it)) novels)) ;; the mapcar version is bigger. ;; (n (mapcar ;; (lambda (x) ;; (cons (cadr x) (caddr x))) ;; novels)) (s (cdr (assoc (ivy-read "Select Novel: " n) n))) ) (switch-to-buffer "*nob*") (unless (eq major-mode 'eww-mode) (eww-mode)) (stride-mode 1) (nob-mode 1) (article-mode 1) (eww-browse-url s)))) (defun nob-remove () "Remove an novel." (interactive) (with-nob (let ((s (ivy-read "Remove Novel: " (--map (cdr it) novels)))) (when (y-or-n-p (format "Remove \"%s\"?" s)) (nob-write-file (--remove (string-equal s (car (cdr it))) novels)) (message "Removed: %s" s)) )) ) (defun nob-eww-after-render-hook () "Eww render hook for nob autosave" (let ((status (third (plist-get eww-data :error)))) (if (not (member status '(400 404))) (with-nob (with-eww-buffer (let ((n (--first (string-match (car it) url) novels))) (if n (if (not (string-equal (nth 2 n) url)) (nob-write-file (nob-update-bookmark novels title url))))) )))) ) (setq nob-mode-map (make-sparse-keymap)) (define-key nob-mode-map [?b] 'nob-save) (define-key nob-mode-map [?B] 'nob-select) (define-minor-mode nob-mode "Novel bookmarks minor mode." :lighter " Nob" :keymap nob-mode-map (when nob-autosave-on-render (if nob-mode (add-hook 'eww-after-render-hook 'nob-eww-after-render-hook nil t) (remove-hook 'eww-after-render-hook 'nob-eww-after-render-hook t) ))) (provide 'nob) ;;; nob.el ends here