Initial commit
This commit is contained in:
commit
f66338dcf2
2 changed files with 500 additions and 0 deletions
1
README.org
Normal file
1
README.org
Normal file
|
@ -0,0 +1 @@
|
|||
An =org-mode= configuration to address my own perceived points of friction. More info in this blog post.
|
499
logos.el
Normal file
499
logos.el
Normal file
|
@ -0,0 +1,499 @@
|
|||
;;; logos.el --- A workflow for org-mode -*- lexical-binding: t -*-
|
||||
|
||||
;; Author: Jakub
|
||||
;; Maintainer: Jakub
|
||||
;; Version: 1.0
|
||||
;; Package-Requires: (straight)
|
||||
;; Homepage: homepage
|
||||
;; Keywords: org
|
||||
|
||||
|
||||
;; This file is not part of GNU Emacs
|
||||
|
||||
;; 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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
;;; Commentary:
|
||||
|
||||
;;
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'transient)
|
||||
(require 'subr-x)
|
||||
|
||||
(defun org-mode-init ()
|
||||
"This is called through `org-mode-hook', so that I don't need to `add-hook' 20 million times."
|
||||
;; I want org to display pictures inline.
|
||||
(org-display-inline-images)
|
||||
;; I don't want monospace text by default.
|
||||
(variable-pitch-mode)
|
||||
)
|
||||
|
||||
(add-hook 'org-mode-hook #'org-mode-init)
|
||||
|
||||
;; Change a bunch of default org-mode variables.
|
||||
(setq org-log-done 'time ;; I want to keep track of the time tasks are completed.
|
||||
org-return-follows-link t ;; I want to be able to follow links easily.
|
||||
org-hide-emphasis-markers t ;; This is a big one: hide markup syntax like ** and //.
|
||||
org-pretty-entities t ;; These two variables together render latex entities as UTF8, which means that Greek letters, subscript, and superscript are all rendered correctly.
|
||||
org-pretty-entities-include-sub-superscripts t
|
||||
org-enforce-todo-dependencies t ;; Prevent changing a parent to DONE if the children aren't
|
||||
org-enforce-todo-checkbox-dependencies t
|
||||
org-agenda-skip-scheduled-if-done t ;; Don't show done stuff in the agenda view
|
||||
org-agenda-skip-deadline-if-done t
|
||||
org-agenda-skip-timestamp-if-done t
|
||||
|
||||
org-todo-keywords '((sequence "TODO" "WIP" "DELEGATED" "WAITING" "|" "CANCELLED" "DONE")) ;; Some extra keywords in addition to TODO and DONE
|
||||
org-refile-targets '((org-agenda-files :maxlevel . 2)) ;; Allow any agenda files to be refile targets.z
|
||||
|
||||
org-agenda-files (apply 'append (mapcar
|
||||
(lambda (dir)
|
||||
(directory-files-recursively dir org-agenda-file-regexp))
|
||||
'("~/.org/tasks"))) ;; Agenda files location - gathered recursively
|
||||
org-cite-global-bibliography '("~/.org/global.bib") ;; Global bibliography location
|
||||
|
||||
org-startup-indented t ;; Start indented - this fixed org-indent mode
|
||||
|
||||
org-attach-id-dir "~/.org/lore/assets"
|
||||
)
|
||||
|
||||
;; Org modern and other interface enhancements
|
||||
(use-package org-modern
|
||||
:hook ((org-mode . org-modern-mode)
|
||||
(org-agenda-finalize . org-modern-agenda)))
|
||||
|
||||
(use-package org-modern-indent
|
||||
:straight (org-modern-indent :type git :host github :repo "jdtsmith/org-modern-indent")
|
||||
:hook ((org-mode . org-modern-indent-mode)))
|
||||
|
||||
(use-package wc-mode
|
||||
:hook ((org-mode . wc-mode))
|
||||
:config (setq wc-modeline-format "WC[%tw]"))
|
||||
|
||||
;; Sentence and word navigation and marking
|
||||
(setq sentence-end-double-space nil) ;; Otherwise, M-e is broken in normal writing.
|
||||
|
||||
;;; Generally useful things
|
||||
;; Mark word and sentence
|
||||
(defun mark-whole-word ()
|
||||
"Mark the whole word underneath the cursor."
|
||||
(interactive)
|
||||
(forward-word)
|
||||
(set-mark-command nil)
|
||||
(backward-word))
|
||||
|
||||
(defun mark-sentence ()
|
||||
"Mark the entire sentence underneath the cursor."
|
||||
(interactive)
|
||||
(forward-sentence)
|
||||
(set-mark-command nil)
|
||||
(backward-sentence))
|
||||
|
||||
(define-key org-mode-map (kbd "C-@") #'mark-whole-word)
|
||||
(define-key org-mode-map (kbd "M-@") #'mark-sentence)
|
||||
|
||||
(transient-define-prefix mark-menu-transient ()
|
||||
"Transient menu for marking units of text."
|
||||
["Mark" ("w" "Word" mark-whole-word)
|
||||
("s" "Sentence" mark-sentence)
|
||||
("p" "Paragraph" mark-paragraph)
|
||||
("b" "Buffer" mark-whole-buffer)])
|
||||
|
||||
(define-key org-mode-map (kbd "C-c o SPC") #'mark-menu-transient)
|
||||
|
||||
;; Section navigation and reordering
|
||||
(defun move-sentence-right (&optional arg)
|
||||
"Move the whole sentence to the right of the next sentence, `ARG' times."
|
||||
(interactive "^p")
|
||||
(or arg (setq arg 1))
|
||||
(while (> arg 0)
|
||||
(forward-sentence)
|
||||
(backward-sentence)
|
||||
(left-char) ;; Capture previous space
|
||||
(kill-sentence)
|
||||
(forward-sentence)
|
||||
(yank)
|
||||
(backward-sentence)
|
||||
(setq arg (1- arg))))
|
||||
|
||||
(defun move-sentence-left (&optional arg)
|
||||
"Move the whole sentence to the left of the previous sentence, `ARG' times."
|
||||
(interactive "^p")
|
||||
(or arg (setq arg 1))
|
||||
(while (> arg 0)
|
||||
(forward-sentence)
|
||||
(backward-sentence)
|
||||
(left-char) ;; Capture previous space
|
||||
(kill-sentence)
|
||||
(backward-sentence)
|
||||
(left-char) ;; Capture previous space
|
||||
(yank)
|
||||
(backward-sentence)
|
||||
(setq arg (1- arg))))
|
||||
|
||||
(defun move-paragraph-up (&optional arg)
|
||||
"Move the whole paragraph above the previous paragraph, `ARG' times."
|
||||
(interactive "^p")
|
||||
(or arg (setq arg 1))
|
||||
(while (> arg 0)
|
||||
(forward-paragraph)
|
||||
(backward-paragraph)
|
||||
(kill-paragraph nil)
|
||||
(backward-paragraph)
|
||||
(yank)
|
||||
(backward-paragraph)
|
||||
(setq arg (1- arg))))
|
||||
|
||||
(defun move-paragraph-down (&optional arg)
|
||||
"Move the whole paragraph above the previous paragraph, `ARG' times."
|
||||
(interactive "^p")
|
||||
(or arg (setq arg 1))
|
||||
(while (> arg 0)
|
||||
(forward-paragraph)
|
||||
(backward-paragraph)
|
||||
(kill-paragraph nil)
|
||||
(forward-paragraph)
|
||||
(yank)
|
||||
(backward-paragraph)
|
||||
(setq arg (1- arg))))
|
||||
|
||||
(define-key org-mode-map (kbd "C-M-<left>") #'move-sentence-left)
|
||||
(define-key org-mode-map (kbd "C-M-<right>") #'move-sentence-right)
|
||||
(define-key org-mode-map (kbd "C-M-<up>") #'move-paragraph-up)
|
||||
(define-key org-mode-map (kbd "C-M-<down>") #'move-paragraph-down)
|
||||
|
||||
(transient-define-prefix reorder-transient ()
|
||||
"Transient menu for text re-ordering commands."
|
||||
["Move sentence..." ("l" "Left" move-sentence-left)
|
||||
("r" "Right" move-sentence-right)]
|
||||
["Move paragraph..." ("u" "Up" move-paragraph-up)
|
||||
("d" "Down" move-paragraph-down)])
|
||||
|
||||
(define-key org-mode-map (kbd "C-c o r") #'reorder-transient)
|
||||
|
||||
;; Focused rewriting
|
||||
(defun break-out-sentence ()
|
||||
"Breaks a sentence out into it's own buffer for editing."
|
||||
(interactive)
|
||||
(backward-sentence)
|
||||
(kill-sentence)
|
||||
(switch-to-buffer-other-window "*break-out*")
|
||||
(erase-buffer)
|
||||
(yank))
|
||||
|
||||
(defun break-out-choose-sentence ()
|
||||
"Chooses a sentence from the break-out buffer."
|
||||
(interactive)
|
||||
(backward-sentence)
|
||||
(kill-sentence)
|
||||
(other-window -1)
|
||||
(kill-buffer "*break-out*")
|
||||
(yank))
|
||||
|
||||
(defun break-out-dwim ()
|
||||
"Either break-out or choose sentency depending on buffer name."
|
||||
(interactive)
|
||||
(if (equal (buffer-name) "*break-out*")
|
||||
(break-out-choose-sentence)
|
||||
(break-out-sentence)))
|
||||
|
||||
(define-key org-mode-map (kbd "C-c o b") #'break-out-dwim)
|
||||
|
||||
;; Powerthesaurus
|
||||
(use-package powerthesaurus
|
||||
:bind ("C-x t" . powerthesaurus-transient))
|
||||
|
||||
;; Harper
|
||||
(when (and (not (equal system-type 'windows-nt)) (locate-file "harper-ls" exec-path))
|
||||
(with-eval-after-load 'eglot
|
||||
(add-to-list 'eglot-server-programs
|
||||
'(org-mode . ("harper-ls" "--stdio"))))
|
||||
|
||||
(setq-default eglot-workspace-configuration
|
||||
'(:harper-ls (:dialect "Australian")))
|
||||
|
||||
(add-hook 'org-mode-hook 'eglot-ensure))
|
||||
|
||||
;; Company
|
||||
(defun org-mode-company-hook ()
|
||||
"Set company backends for `org-mode' usage."
|
||||
(setq-local company-backends
|
||||
'((company-capf company-dabbrev company-files company-ispell))))
|
||||
(add-hook 'org-mode-hook #'org-mode-company-hook)
|
||||
|
||||
;;; Exobrain
|
||||
;;; Task management
|
||||
;; Maintenance and project file creation shortcuts
|
||||
(defun new-maintenance-file (maintenance-area)
|
||||
"Create a new maintenance file for a given `MAINTENANCE-AREA'."
|
||||
(interactive "sMaintenance area is: ")
|
||||
(find-file (concat "~/.org/tasks/maintenance/" maintenance-area ".org"))
|
||||
(insert "\n\n* Calendar\n\n* TO-DO\n\n* Routines\n\n* Archive\n")
|
||||
(goto-char (point-min)))
|
||||
|
||||
(defun new-project-file (project-name acceptance-criteria)
|
||||
"Create a new project file for a given `PROJECT-NAME', with some `ACCEPTANCE-CRITERIA'."
|
||||
(find-file (concat "~/.org/tasks/projects/" project-name ".org"))
|
||||
(insert (concat "\n\n* Acceptance Criteria\n\n" acceptance-criteria "\n\n* Ideas\n\n* TODO Work\n\n** Calendar\n\n** TO-DO\n\n** Routines\n\n** Archive\n"))
|
||||
(goto-char (point-min)))
|
||||
|
||||
(defun new-generic-project (project-name acceptance-criteria)
|
||||
"Interactive wrapper for `new-project-file', for generic projects."
|
||||
(interactive "sProject name is: \nsAcceptance criteria is: ")
|
||||
(new-project-file project-name acceptance-criteria))
|
||||
|
||||
(defun new-career-project (project-name acceptance-criteria)
|
||||
"Interactive wrapper for `new-project-file', for career projects."
|
||||
(interactive "sProject name is: \nsAcceptance criteria is: ")
|
||||
(new-project-file (concat "career/" project-name) acceptance-criteria))
|
||||
|
||||
;; Captures
|
||||
(defun select-task-area (location headline)
|
||||
"Prompt for a file in ~/.org/tasks/maintenance/ to insert the capture."
|
||||
(let* ((files (directory-files (concat "~/.org/tasks/" location) t "\\.org$"))
|
||||
(choices (mapcar
|
||||
(lambda (x) (string-remove-suffix ".org" x))
|
||||
(mapcar #'file-name-nondirectory files)))
|
||||
(selected (completing-read "Choose a file: " choices nil t)))
|
||||
(set-buffer (org-capture-target-buffer (concat "~/.org/tasks/" location selected ".org")))
|
||||
(org-capture-put-target-region-and-position)
|
||||
(widen)
|
||||
(goto-char (point-min))
|
||||
(setq headline (org-capture-expand-headline headline))
|
||||
(re-search-forward (format org-complex-heading-regexp-format
|
||||
(regexp-quote headline))
|
||||
nil t)
|
||||
(forward-line 0)))
|
||||
|
||||
(defun select-maintenance-area (headline)
|
||||
(select-task-area "maintenance/" headline))
|
||||
|
||||
(defun select-project (headline)
|
||||
(select-task-area "projects/" headline))
|
||||
|
||||
(defun select-career-project (headline)
|
||||
(select-task-area "projects/career/" headline))
|
||||
|
||||
(setq org-capture-templates
|
||||
'(("i" "Idea")
|
||||
("ii" "Generic" entry
|
||||
(file "~/.org/inbox.org")
|
||||
"* %^{TITLE} :idea: \n:Created: %T\n%?"
|
||||
:empty-lines 1)
|
||||
("ip" "Project" entry
|
||||
(function (lambda () (select-project "Ideas")))
|
||||
"* %^{TITLE} \n:Created: %T\n%?"
|
||||
:empty-lines 1)
|
||||
("ic" "Career" entry
|
||||
(function (lambda () (select-career-project "Ideas")))
|
||||
"* %^{TITLE} \n:Created: %T\n%?"
|
||||
:empty-lines 1)
|
||||
|
||||
("t" "Task")
|
||||
("tt" "Generic")
|
||||
("ttt" "Raw" entry
|
||||
(file "~/.org/inbox.org")
|
||||
"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} :task:\n:Created: %T\n%?"
|
||||
:empty-lines 1)
|
||||
("ttm" "Maintenance" entry
|
||||
(function (lambda () (select-maintenance-area "TO-DO")))
|
||||
"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?"
|
||||
:empty-lines 1)
|
||||
("ttp" "Project" entry
|
||||
(function (lambda () (select-project "TO-DO")))
|
||||
"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?"
|
||||
:empty-lines 1)
|
||||
("ttc" "Career" entry
|
||||
(function (lambda () (select-career-project "TO-DO")))
|
||||
"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE}\n:Created: %T\n%?"
|
||||
:empty-lines 1)
|
||||
("ts" "Scheduled")
|
||||
("tss" "Raw" entry
|
||||
(file "~/.org/inbox.org")
|
||||
"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} :scheduled:\n:SCHEDULED: %^T\n%?"
|
||||
:empty-lines 1
|
||||
:time-prompt t)
|
||||
("tsm" "Maintenance" entry
|
||||
(function (lambda () (select-maintenance-area "Calendar")))
|
||||
"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?"
|
||||
:empty-lines 1
|
||||
:time-prompt t)
|
||||
("tsp" "Project" entry
|
||||
(function (lambda () (select-project "Calendar")))
|
||||
"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?"
|
||||
:empty-lines 1
|
||||
:time-prompt t)
|
||||
("tsc" "Career" entry
|
||||
(function (lambda () (select-career-project "Calendar")))
|
||||
"* TODO [%^{priority|#A|#B|#C|#D}] %^{TITLE} \n:SCHEDULED: %^T\n%?"
|
||||
:empty-lines 1
|
||||
:time-prompt t)
|
||||
|
||||
("n" "Note")
|
||||
("nn" "Raw" entry
|
||||
(file "~/.org/inbox.org")
|
||||
"* %^{TITLE} :note: \n:Created: %T\n%?"
|
||||
:empty-lines 1)
|
||||
("nm" "Meeting")
|
||||
("nmm" "Raw" entry
|
||||
(file "~/.org/inbox.org")
|
||||
"* %^{TITLE} :meeting: \n:Created: %T\n%?"
|
||||
:clock-in t
|
||||
:clock-resume t)
|
||||
("nmc" "Career" entry
|
||||
(function (lambda () (select-career-project "Calendar")))
|
||||
"* %^{TITLE} \n:Created: %T\n%?"
|
||||
:empty-lines 1
|
||||
:clock-in t
|
||||
:clock-resume t)))
|
||||
|
||||
(defun open-inbox ()
|
||||
"Opens `~/.org/inbox.org'"
|
||||
(interactive)
|
||||
(find-file "~/.org/inbox.org"))
|
||||
|
||||
(defun open-maintenance ()
|
||||
"Opens `~/.org/tasks/maintenance/'"
|
||||
(interactive)
|
||||
(find-file "~/.org/tasks/maintenance/"))
|
||||
|
||||
(defun open-projects ()
|
||||
"Opens `~/.org/tasks/projects/'"
|
||||
(interactive)
|
||||
(find-file "~/.org/tasks/projects/"))
|
||||
|
||||
(defun open-career ()
|
||||
"Opens `~/.org/tasks/projects/career/'"
|
||||
(interactive)
|
||||
(find-file "~/.org/tasks/projects/career/"))
|
||||
|
||||
(transient-define-prefix task-management-menu ()
|
||||
"Transient menu for task management shortcuts."
|
||||
["Go To" ("i" "Inbox" open-inbox)
|
||||
("m" "Maintenance Directory" open-maintenance)
|
||||
("p" "Projects Directory" open-projects)
|
||||
("c" "Career Directory" open-career)]
|
||||
["Create New" ("M" "Maintenance File" new-maintenance-file)
|
||||
("P" "Project" new-generic-project)
|
||||
("C" "Career Project" new-career-project)])
|
||||
|
||||
(define-key global-map (kbd "C-c o t") #'task-management-menu)
|
||||
(define-key global-map (kbd "C-c a") #'org-agenda)
|
||||
(define-key global-map (kbd "C-c c") #'org-capture)
|
||||
|
||||
|
||||
;; Org roam
|
||||
(use-package org-roam
|
||||
:straight nil
|
||||
:ensure t
|
||||
:init
|
||||
(setq org-roam-v2-ack t)
|
||||
:custom
|
||||
(org-roam-directory (file-truename "~/.org/lore/"))
|
||||
(org-dailies-directory (file-truename "~/.org/lore/refined/journal/"))
|
||||
(org-roam-file-exclude-regexp "\\.git/.*\\|logseq/.*$")
|
||||
(org-roam-completion-everywhere)
|
||||
(org-roam-capture-templates
|
||||
'(("c" "Raw" plain
|
||||
"%?"
|
||||
:target (file+head "raw/${slug}.org" "#+title: ${title}\n")
|
||||
:unnarrowed t)
|
||||
("r" "Refined")
|
||||
("rm" "Mini-essay" plain
|
||||
"%?"
|
||||
:target (file+head "refined/wiki/${slug}.org" "#+title: ${title}\n#+author: Jakub Nowak")
|
||||
:unnarrowed t)))
|
||||
(org-roam-dailies-capture-templates
|
||||
'(("d" "default" entry
|
||||
"* %?"
|
||||
:target (file+head "%<%Y-%m-%d>.org" ;; format matches Logseq
|
||||
"#+title: %<%Y-%m-%d>\n"))))
|
||||
:bind (("C-c n l" . org-roam-buffer-toggle)
|
||||
("C-c n f" . org-roam-node-find)
|
||||
("C-c n g" . org-roam-graph)
|
||||
("C-c n i" . org-roam-node-insert)
|
||||
("C-c n c" . org-roam-capture)
|
||||
:map org-roam-dailies-map
|
||||
("Y" . org-roam-dailies-capture-yesterday)
|
||||
("T" . org-roam-dailies-capture-tomorrow))
|
||||
:bind-keymap ("C-c n d" . org-roam-dailies-map)
|
||||
:config
|
||||
(require 'org-roam-dailies) ;; Ensure the keymap is available
|
||||
(org-roam-db-autosync-mode))
|
||||
|
||||
(defun org-roam-create-node-from-headline ()
|
||||
"Create an Org-roam note from the current headline and jump to it.
|
||||
|
||||
Normally, insert the headline’s title using the ’#title:’ file-level property
|
||||
and delete the Org-mode headline. However, if the current headline has a
|
||||
Org-mode properties drawer already, keep the headline and don’t insert
|
||||
‘#+title:'. Org-roam can extract the title from both kinds of notes, but using
|
||||
‘#+title:’ is a bit cleaner for a short note, which Org-roam encourages."
|
||||
(interactive)
|
||||
(let ((title (nth 4 (org-heading-components)))
|
||||
(has-properties (org-get-property-block)))
|
||||
(org-cut-subtree)
|
||||
(org-roam-find-file title nil nil 'no-confirm)
|
||||
(org-paste-subtree)
|
||||
(unless has-properties
|
||||
(kill-line)
|
||||
(while (outline-next-heading)
|
||||
(org-promote)))
|
||||
(goto-char (point-min))
|
||||
(when has-properties
|
||||
(kill-line)
|
||||
(kill-line))))
|
||||
|
||||
(transient-define-prefix org-roam-menu ()
|
||||
"Transient menu for task management shortcuts."
|
||||
["Node" ("c" "Capture" org-roam-node-find)
|
||||
("i" "Insert" org-roam-node-insert)
|
||||
("r" "Refile" org-roam-create-node-from-headline)
|
||||
("v" "Visit" org-roam-node-visit) ]
|
||||
["Dailies" ("t" "Today" org-roam-dailies-capture-today)
|
||||
("y" "Yesterday" org-roam-dailies-capture-yesterday)
|
||||
("n" "Tomorrow" org-roam-dailies-capture-tomorrow)]
|
||||
["Logseq" ("R" "Fix links" logseq-org-roam)])
|
||||
|
||||
(define-key global-map (kbd "C-c o n") #'org-roam-menu)
|
||||
|
||||
;; Bibliography stuff
|
||||
(setq bibtex-dialect 'biblatex) ;; Use biblatex instead of bibtex.
|
||||
|
||||
(use-package biblio)
|
||||
|
||||
(defun bibtex-online-entry ()
|
||||
(interactive)
|
||||
(bibtex-entry "Online"))
|
||||
|
||||
(defun bibtex-misc-entry ()
|
||||
(interactive)
|
||||
(bibtex-entry "Misc"))
|
||||
|
||||
(defun bibtex-software-entry ()
|
||||
(interactive)
|
||||
(bibtex-entry "Software"))
|
||||
|
||||
(transient-define-prefix bibtex-transient-menu ()
|
||||
"Transient menu for task management shortcuts."
|
||||
["Biblio" ("d" "doi.org" doi-insert-bibtex)
|
||||
("x" "arXiv" arxiv-lookup)]
|
||||
["Templates" ("o" "Online Resource" bibtex-online-entry)
|
||||
("m" "Misc" bibtex-misc-entry)
|
||||
("s" "Software" bibtex-software-entry)])
|
||||
|
||||
(define-key bibtex-mode-map (kbd "C-c r") #'bibtex-transient-menu)
|
||||
|
||||
;;; logos.el ends here
|
Loading…
Add table
Add a link
Reference in a new issue