diff options
Diffstat (limited to 'modules')
| -rw-r--r-- | modules/dirvish-config.el | 10 | ||||
| -rw-r--r-- | modules/elfeed-config.el | 5 | ||||
| -rw-r--r-- | modules/org-capture-config.el | 8 | ||||
| -rw-r--r-- | modules/org-reveal-config.el | 148 |
4 files changed, 160 insertions, 11 deletions
diff --git a/modules/dirvish-config.el b/modules/dirvish-config.el index 6c849198..e9b9fde6 100644 --- a/modules/dirvish-config.el +++ b/modules/dirvish-config.el @@ -29,7 +29,7 @@ ;; - S: Study — start an org-drill session on the .org file at point ;; - M-D (Meta-Shift-d): DWIM shell commands menu ;; - TAB: Toggle subtree expansion -;; - F11: Toggle sidebar view +;; - F9: Toggle sidebar view ;;; Code: @@ -653,7 +653,13 @@ no popup frame is live." (("C-x d" . dirvish) ("C-x C-d" . dirvish) ("C-x D" . dirvish) - ("<f11>" . dirvish-side) + ;; F9, not F11: F11 is stock Emacs's toggle-frame-fullscreen, the same key + ;; every other application uses, and the sidebar sat on it for years by + ;; shadowing that. F9 is the free bare F-key: ai-term left it for M-SPC + ;; in June 2026 and the recording chord came off it on 2026-09-22. F2 is + ;; the preview key (markdown-mode binds it) and F5 is reserved for the + ;; debug backend, so neither was a candidate. + ("<f9>" . dirvish-side) :map dirvish-mode-map ("bg" . cj/set-wallpaper) ("/" . dirvish-narrow) diff --git a/modules/elfeed-config.el b/modules/elfeed-config.el index f4feef3a..34e90eb1 100644 --- a/modules/elfeed-config.el +++ b/modules/elfeed-config.el @@ -28,6 +28,11 @@ (require 'user-constants) (require 'system-lib) (require 'media-utils) +;; elfeed.el ends with a bare `(unless byte-compile-root-dir ...)'. That +;; variable lives in bytecomp, so loading elfeed (source or .elc) in a +;; session that hasn't loaded bytecomp signals void-variable. I load it here, ahead of +;; any elfeed load, rather than rely on something else having pulled it in. +(require 'bytecomp) (declare-function elfeed "elfeed") (declare-function elfeed-update "elfeed") diff --git a/modules/org-capture-config.el b/modules/org-capture-config.el index b7250f1e..8f5903fe 100644 --- a/modules/org-capture-config.el +++ b/modules/org-capture-config.el @@ -145,12 +145,16 @@ re-scanning large target files after the first successful lookup." :around #'cj/org-capture--set-target-location-advice)) ;; --------------------------- Capture F-Key Guard ----------------------------- -;; The global popup keys (F1 dashboard sweep, F10 music, F11 dirvish-side, +;; The global popup keys (F1 dashboard sweep, F9 dirvish-side, F10 music, ;; F12 terminal, M-SPC agent swap) fire even while a capture is in progress ;; and pop their UI over the capture popup. org-capture-mode is a minor ;; mode active exactly for the capture's duration and its keymap shadows ;; the global map, so blocking the keys there scopes the guard precisely: ;; the moment the capture finalizes or aborts, the keys work again. +;; +;; The list follows the command, not the key. F11 was on it while it held +;; dirvish-side; now that F11 is the frame fullscreen toggle it pops nothing +;; over a capture and stays usable. (defun cj/--org-capture-blocked-key () "Refuse a global popup key while a capture is in progress." @@ -160,7 +164,7 @@ re-scanning large target files after the first successful lookup." (defvar org-capture-mode-map) (with-eval-after-load 'org-capture - (dolist (key '("<f1>" "<f10>" "<f11>" "<f12>" "M-SPC")) + (dolist (key '("<f1>" "<f9>" "<f10>" "<f12>" "M-SPC")) (keymap-set org-capture-mode-map key #'cj/--org-capture-blocked-key))) ;; ----------------------- Project-Aware Capture Target ------------------------ diff --git a/modules/org-reveal-config.el b/modules/org-reveal-config.el index f842680b..ddb0803d 100644 --- a/modules/org-reveal-config.el +++ b/modules/org-reveal-config.el @@ -18,6 +18,10 @@ ;; Fully offline workflow using a local reveal.js clone (managed by ;; scripts/setup-reveal.sh) and self-contained HTML export. ;; +;; Slides use a 16:9 canvas with a smaller base font than the stock theme, +;; and a script in the export shrinks any slide whose content would +;; otherwise be clipped (see "Slide Sizing" below). +;; ;; Keybindings (C-; p prefix): ;; - C-; p SPC : Ensure headers, export, and open in browser ;; - C-; p e : Export to self-contained HTML and open in browser @@ -37,6 +41,8 @@ (defvar org-reveal-plugins) (defvar org-reveal-highlight-css) (defvar org-reveal-init-options) +(defvar org-reveal-head-preamble) +(defvar org-reveal-extra-script) (declare-function org-reveal-export-to-html "ox-reveal") ;; --------------------------------- Constants --------------------------------- @@ -51,6 +57,135 @@ (defconst cj/reveal-default-transition "none" "Default reveal.js slide transition for new presentations.") +;; ------------------------------- Slide Sizing -------------------------------- +;; +;; reveal.js lays every slide out on a fixed canvas and scales that canvas +;; to the window; content taller than the canvas is clipped, not scrolled. +;; Three things keep a dense slide readable instead of cut off: +;; +;; - a 16:9 canvas (the shape of every screen the slides land on), so lines +;; wrap later than on the stock 960x700 frame; +;; - a smaller base font and heading scale, set through the theme's CSS +;; variables in the head preamble so any reveal.js 4+ theme picks them up; +;; - a script, spliced in after Reveal.initialize, that shrinks any slide +;; whose content still overflows. It floors at half size: a slide that +;; overflows even there needs splitting, not shrinking. +;; +;; The script rides ox-reveal's extra-script slot, so a file that sets its +;; own #+REVEAL_EXTRA_SCRIPT replaces it. Print view (?print-pdf) paginates +;; instead of clipping, so the fit is skipped there and a printed deck can +;; differ from the presented one. + +(defconst cj/reveal-canvas-width 1280 + "Width of the reveal.js slide canvas in CSS pixels.") + +(defconst cj/reveal-canvas-height 720 + "Height of the reveal.js slide canvas in CSS pixels.") + +(defconst cj/reveal-base-font-size "30px" + "Base font size for slide text, as a px value. +The stock themes use 42px, which fits about six short lines on a slide. +Keep it in px: ox-reveal runs the head preamble through `format-spec', +so a % unit would break the export.") + +(defun cj/--reveal-init-options () + "Return the Reveal.initialize options shared by default and template. +One line with no trailing comma, so ox-reveal can splice it into the +Reveal.initialize call." + (format "slideNumber:true, hash:true, transition:'%s', width:%d, height:%d" + cj/reveal-default-transition + cj/reveal-canvas-width + cj/reveal-canvas-height)) + +(defun cj/--reveal-head-preamble () + "Return the <style> element for the document head. +Sets the house font sizes through the theme variables, stops the theme +from upper-casing headings, and left-aligns slide text (reveal centers +everything by default); the title slide and standalone images keep the +centering." + (format "<style> +:root { + --r-main-font-size: %s; + --r-heading1-size: 2.2em; + --r-heading2-size: 1.5em; + --r-heading3-size: 1.2em; + --r-heading4-size: 1em; +} +.reveal h1, .reveal h2, .reveal h3, .reveal p { text-transform: none; } +.reveal .slides section { text-align: left; } +.reveal .slides #sec-title-slide { text-align: center; } +.reveal .slides section .figure { text-align: center; } +</style>" + cj/reveal-base-font-size)) + +(defun cj/--reveal-fit-script () + "Return the JavaScript that shrinks overflowing slides to the canvas. +ox-reveal emits it inside the <script> element right after +Reveal.initialize, so Reveal already exists: every slide is fitted once +reveal is ready and the current one refitted on each slide change. +Hidden slides are shown for the measurement only, so the display state +reveal manages is restored before anything paints." + "// Shrink any slide whose content overflows the canvas height so it scales +// down instead of being clipped. Floors at half size: a slide that still +// overflows there needs splitting, not shrinking. +(function () { + var FLOOR = 0.5; // never below half size: split the slide instead + var MARGIN = 0.05; // breathing room above the bottom edge of the canvas + var PASSES = 8; // fixed-height content converges slowly; cap the loop + var busy = false; // layout() re-enters through slidechanged in scroll view + // Overview boxes every slide at the canvas height and print view paginates, + // so a measurement there is meaningless. + function skip() { + return (Reveal.isPrintView && Reveal.isPrintView()) + || (Reveal.isOverview && Reveal.isOverview()); + } + // Shrink SLIDE until it fits; return true when its font size changed. + function fit(slide) { + if (!slide || skip()) { return false; } + var limit = Reveal.getConfig().height * (1 - MARGIN); + var before = slide.style.fontSize; + var scale = 1; + slide.style.fontSize = ''; + for (var pass = 0; pass < PASSES && scale > FLOOR && slide.scrollHeight > limit; pass++) { + scale = Math.max(FLOOR, scale * limit / slide.scrollHeight); + slide.style.fontSize = (scale * 100).toFixed(1) + '%'; + } + if (scale === FLOOR && slide.scrollHeight > limit) { + console.warn('reveal fit: slide still overflows at half size, split it: ' + + (slide.id || '(no id)')); + } + return slide.style.fontSize !== before; + } + function shown(el, fn) { + var saved = el.style.display; + el.style.display = 'block'; + try { fn(); } finally { el.style.display = saved; } + } + function fitAll() { + busy = true; + try { + Reveal.getSlides().forEach(function (slide) { + var parent = slide.parentNode; + var run = function () { shown(slide, function () { fit(slide); }); }; + if (parent && parent.classList && parent.classList.contains('stack')) { + shown(parent, run); + } else { + run(); + } + }); + Reveal.layout(); + } finally { busy = false; } + } + function refit(slide) { + if (busy) { return; } + busy = true; + try { if (fit(slide)) { Reveal.layout(); } } finally { busy = false; } + } + if (Reveal.isReady()) { fitAll(); } else { Reveal.on('ready', fitAll); } + Reveal.on('slidechanged', function (event) { refit(event.currentSlide); }); + Reveal.on('overviewhidden', function () { refit(Reveal.getCurrentSlide()); }); +})();") + ;; --------------------------------- ox-reveal --------------------------------- (use-package ox-reveal @@ -60,10 +195,9 @@ (setq org-reveal-single-file t) (setq org-reveal-plugins '(highlight notes search zoom)) (setq org-reveal-highlight-css "%r/plugin/highlight/monokai.css") - (setq org-reveal-init-options (format "slideNumber:true, hash:true, transition:'%s'" - cj/reveal-default-transition)) - (setq org-reveal-head-preamble - "<style>.reveal h1,.reveal h2,.reveal h3,.reveal p{text-transform:none;}</style>")) + (setq org-reveal-init-options (cj/--reveal-init-options)) + (setq org-reveal-head-preamble (cj/--reveal-head-preamble)) + (setq org-reveal-extra-script (cj/--reveal-fit-script))) ;; ----------------------------- Private Helpers ------------------------------- @@ -76,15 +210,15 @@ #+DATE: %s #+REVEAL_ROOT: %s #+REVEAL_THEME: %s -#+REVEAL_INIT_OPTIONS: slideNumber:true, hash:true, transition:'%s' +#+REVEAL_INIT_OPTIONS: %s #+REVEAL_PLUGINS: (highlight notes search zoom) #+REVEAL_HIGHLIGHT_CSS: %%r/plugin/highlight/monokai.css -#+OPTIONS: toc:nil num:nil date:nil timestamp:nil author:t +#+OPTIONS: toc:nil num:nil date:nil timestamp:nil author:t ^:nil " title (user-full-name) (format-time-string "%Y-%m-%d") (concat "file://" cj/reveal-root) cj/reveal-default-theme - cj/reveal-default-transition)) + (cj/--reveal-init-options))) (defun cj/--reveal-title-to-filename (title) "Convert TITLE to a slug-based .org filename. |
