diff --git a/.gitignore b/.gitignore index ca35be0..dba907b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ _site +src/archive.org diff --git a/.project.el.gpg b/.project.el.gpg index 678cdc9..9e70953 100644 Binary files a/.project.el.gpg and b/.project.el.gpg differ diff --git a/src/archive.org b/src/archive.org deleted file mode 100644 index 72adc0d..0000000 --- a/src/archive.org +++ /dev/null @@ -1,8 +0,0 @@ -#+TITLE: Blog Posts -#+AUTHOR: Yann Esposito -#+EMAIL: yann@esposito.host - -#+begin_archive -@@html:
  • @@ @@html:@@ 2019-08-13: @@html:@@ [[file:posts/project-el/index.org][Autoload Script by project]] @@html:@@ @@html:
  • @@ -@@html:
  • @@ @@html:@@ 2019-07-15: @@html:@@ [[file:posts/2019-07-04-static-org-publish.org][Static blog with org-mode]] @@html:@@ @@html:
  • @@ -#+end_archive diff --git a/src/css/minimalist.css b/src/css/minimalist.css index 93dbecc..30a5e62 100644 --- a/src/css/minimalist.css +++ b/src/css/minimalist.css @@ -190,17 +190,22 @@ navigation > a { } #preamble { border-bottom: solid 1px; + margin-bottom: 1em; } #preamble h1, #preamble h2 { margin: 0; } +#preamble h2 { + font-weight: normal; + font-style: italic; +} #postamble { border-top: solid 1px; margin-top: 10px; } #content,.content { max-width: 50em; - margin: 0 auto; + margin: 0 1em; padding: 1px; } #content *:first-child { @@ -267,6 +272,13 @@ navigation > a { --soft-foreground: var(--base01); --border-color: var(--base02); --todo-txt: #000; + --color-h1: var(--magenta); + --color-h2: var(--violet); + --color-h3: var(--blue); + --color-h4: var(--cyan); + --color-h5: var(--green); + --color-h6: var(--yellow); + --color-link: var(--yellow); } /* org colors */ @@ -290,7 +302,9 @@ navigation > a { cursor: pointer; font-style: italic; } -input#light, input#dark { display:none; } + +body > input { display:none; } + input#light:checked ~ .main { --main-background: #fefaf0; /* 0.5 lighter than #fdf6e3 */ --main-foreground: var(--base01); @@ -300,6 +314,11 @@ input#light:checked ~ .main { --border-color: var(--base2); --todo-txt: #FFF; } +input#light:checked ~ #labels { + background: #fefaf0; + color: var(--base00); +} + input#dark:checked ~ .main { --main-background: var(--base03); /* 0.5 darker than #002b36; */ --main-foreground: var(--base1); @@ -309,14 +328,33 @@ input#dark:checked ~ .main { --border-color: var(--base02); --todo-txt: #000; } -input#light:checked ~ #labels { - background: #fefaf0; - color: var(--base00); -} input#dark:checked ~ #labels { background: var(--base03); color: var(--base0); } + +input#raw:checked ~ .main { + --main-background: #fff; + --main-foreground: #000; + --second-foreground: #000; + --reveal-background: #fff; + --soft-foreground: #000; + --border-color: #000; + --todo-txt: #000; + --color-h1: #000; + --color-h2: #000; + --color-h3: #000; + --color-h4: #000; + --color-h5: #000; + --color-h6: #000; + --color-link: #33d; +} +input#raw:checked ~ #labels { + background: #fff; + color: #000; +} + + body,.main { background: var(--main-background); color: var(--main-foreground); @@ -337,7 +375,7 @@ pre::after,pre::before,hr:after, } a:hover, a:active, a:focus, .main a:hover,.main a:active,.main a:focus { - color: var(--yellow); + color: var(--color-link); } navigation a, navigation a:visited, .main navigation a,.main navigation a:visited { @@ -345,7 +383,7 @@ navigation a, navigation a:visited, } navigation a:focus, navigation a:hover, .main navigation a:focus,.main navigation a:hover { - color: var(--yellow); + color: var(--color-link); } thead, .main thead { @@ -357,22 +395,25 @@ tr:hover, background-color: var(--reveal-background); } h1, .main h1 { - color: var(--magenta); + color: var(--color-h1); +} +#preamble h2, .main #preamble h2 { + color: var(--color-h4); } h2, .main h2 { - color: var(--violet); + color: var(--color-h2); } h3, .main h3 { - color: var(--blue); + color: var(--color-h3); } h4, .main h4 { - color: var(--cyan); + color: var(--color-h4); } h5, .main h5 { - color: var(--green); + color: var(--color-h5); } h6, .main h6 { - color: var(--green); + color: var(--color-h6); } table, td, th, .main table,.main td,.main th { diff --git a/src/index.org b/src/index.org index 04e962a..8118936 100644 --- a/src/index.org +++ b/src/index.org @@ -8,12 +8,24 @@ #+OPTIONS: H:5 #+STARTUP: showeverything -Welcome! +Welcome to my personal website. -#+begin_notes -This website is dark by default, but it support user preferred theme. -For example on Mac OS X > 10.14, if you have selected a light theme, the website -will be light. -#+end_notes +Here I will talk mostly about the life of a software developer. +So programming, functional programming in particular. -- [[./archive.html][articles]] +That website was created with the following constraints in mind by order of +priority: + +1. /Respect Privacy/; no tracker of any sort (no google ads, no referrer for all external links, + etc...) +2. /javascript free/; no js at all, yes even the theme changer +3. /disability friendly/; should be easy to read on a text browser so people + with disabilities could easily consume it +4. /nerdy/; should feel mostly like markdown text in a terminal and source code + should be syntax higlighted. +5. /org-mode friendly/; I should write my post in [[https://orgmode.org][org mode]] +6. /user friendly/; support your preferred light/dark theme by default but you + can change it if you want. +7. /frugal/; try to minize the resources needed to visit my website; no + javascript, no webfont, not too much CSS magic, not much images or really + compressed one. diff --git a/src/posts/2019-07-04-static-org-publish.org b/src/posts/2019-07-04-static-org-publish.org index 7f01f0c..7d44857 100644 --- a/src/posts/2019-07-04-static-org-publish.org +++ b/src/posts/2019-07-04-static-org-publish.org @@ -1,406 +1,321 @@ -#+TITLE: Static blog with org-mode +#+TITLE: Blogging with org-mode +#+SUBTITLE: Meta post about this blog #+AUTHOR: Yann Esposito #+EMAIL: yann@esposito.host #+DATE: 2019-07-27 -#+KEYWORDS: programming, blog, org-mode +#+KEYWORDS: programming, blog, org-mode, meta #+OPTIONS: auto-id:t #+begin_quote -/tl;dr/: [[#current-solution][Full code for the impatient↓]]. -Read the full blog post to have all the details. +/tl;dr/: [[#full-solution][Go to the full solution]] for the impatients. #+end_quote -This is the first article using my new blog system. +As the first article of my new blogging system, let's have a meta blog post. + +Once in a while, I like to change how I blog. +A long time ago, I used PHP for my first website using my personal space at my +university. +And as of today, a clond of my university website still exists: +http://yann.esposito.free.fr It contains all details about my old research +activities and teaching resources for my students. -Once in a while, I create a new personal website. -A long time ago, I used PHP for my first website. -I used include and took care of XHTML pages validation. Then I used [[http://nanoc.ws][nanoc]], a ruby static website generator. Then I switched to [[https://jaspervdj.be/hakyll/][hakyll]] because I wanted to switch to a Haskell's written tool. -Now I'll try to use [[http://orgmode.org][org-mode]] directly with [[https://orgmode.org/worg/org-tutorials/org-publish-html-tutorial.html][org-publish]]. -I am became a quite extensive emacs user now. So using org-mode and org-publish -is a bit like not having to install any 3rd party software. -* Why? - :PROPERTIES: - :CUSTOM_ID: why--8eb9 - :END: +This time I'll try to use [[http://orgmode.org][org-mode]] directly with [[https://orgmode.org/worg/org-tutorials/org-publish-html-tutorial.html][org-publish]]. +I used vim a lot, but recently I switched to emacs. +The main reason were: -Everything started when I was hired in a place where I was given a terrible -keyboard. -After a few weeks I started to feel a lot of pain in both my wrists. -So I started to go from classical IDE to being able to use vim -correctly[fn:vim]. +1. You use LISP as plugin language while for Vim it is vimscript which is a + quite terrible language. +2. Many comment on the Internet about people telling that [[https://github.com/emacs-evil/evil][evil]] (vim keybdings in + emacs) was really really good. And I can confirm it is. +3. The [[http://spacemacs.org][spacemacs]] project made it easier to handle the emacs configuratin + complexity for a newbie. -Then I started to work in Clojure and I heard that emacs might certainly be a -better fit for LISP dialiects. -But, I couldn't switch to an editor without vim keybindings support because they -are so great once you're used to them. -By chance it was about the same time that [[http://spacemacs.org][spacemacs]] appeared and I switched. -Is is really impressive how well the vim keybindings are supported. -Even most of the advanced vim features I used to use worked like a charm. - -The first benefit of emacs is you can configure emacs with elisp. -Which unlike vimscript looks like a correct language to work with. - -One unexpected benefit of emacs was [[http://orgmode.org][org-mode]]. -I always heard good things about it but it took me a while to really get it and -to understand why it is so great. - -If you don't know anything about org-mode, it is many things. -First imagine a Markdown but more TODO list oriented. -But along with this, emacs has a lot of helper functions to work with those -org-mode files. - -One real game changer is ~org-capture~. -You can add a task quite easily while doing other work in emacs. - -[fn:vim] I wrote this article to help people use vim: [[http://yannesposito.com/Scratch/en/blog/Learn-Vim-Progressively/][learn vim progressively]] +And by doing the switch, I discovered a lot of great emacs packages. +Now I really do not regret the switch. +In particular, [[http://orgmode.org][org-mode]] has been a game changer. +I used to write markdown before. +But now I use the org format. +It is superior for my usage(s). * How? :PROPERTIES: - :CUSTOM_ID: how--831e + :CUSTOM_ID: how :END: You can easily follow the org-mode doc to make your own website. -But I have added a few "goodies". +But I have added a few niceties. +First, the code to export is provided in a local file. +I've got a system to auto-load elisp file when entering in a new project. +It is quite safe as it uses PGP and check if I trust the person who encrypted +the file. +See [[file:project-el/index.org][my other post about it]]. -- attempt to obfuscate contact email, -** Basic Blog +** Configuration variables :PROPERTIES: - :CUSTOM_ID: basic-blog-a1fc + :CUSTOM_ID: configuration-variables :END: -I put the need minimal code in a =.project.el.gpg= file of my blog repository. -Inspired by this [[https://francismurillo.github.io/2017-02-15-Project-Script-Loader/][blog post]] I enhanced this version to be both more user friendly -and secure (see [[./autoload-emacs-script-by-project.org][Autoload Emacs Script by Project]]). - -But even before that, I simply put the code in has an elisp code block of my -=index.org= that I exectued with =C-c C-c=. - -I have a code block in my =index.org= page that I do not export containing the -elisp code I use to load all the information needed by =org-publish=. -Do not export an org-mode subtree you can simply tag it with =:noexport:=. - -=org-publish= suppose that you'll export one html page per org file. -You need to provide a minimum set of information: - -- base directory that should contain your org files -- publish directory, where you will export the html from the org files - -A very basic publish rule would be: - -#+begin_src org -,* Magic Script :noexport: -,#+begin_src elisp :results none - (setq org-publish-project-alist - '(("orgfiles" - :base-directory "/users/me/blog/src" - :publishing-directory "/users/me/blog/_site" - :recursive t))) -,#+end_src -#+end_src - -So now, if you put org files in the base directory it will copy -recursively the tree in that base directory and will copy it to -the publishing directory exporting all org files to html files. - -Nice. - -If you are not familiar with emacs or orgmode, orgpublish. -This block of code set the variable named ~org-publish-project-alist~. -The ~alist~ in the end is for "associative list". - -#+begin_quote -/☞/ So when you'll execute the command =org-publish= in emacs. -You will get prompted to enter the name of what to publish. -You should see a single proposition named =orgfiles=. -Once selected the export and publish will occurs. -Notice, org-publish does what you expect and do not re-export all files -each time but only the one that have changed. -#+end_quote - -** Relative Paths - :PROPERTIES: - :CUSTOM_ID: relative-paths-d145 - :END: - -A first issue with this is that I don't want to put that in my emacs -preferences. -I would like to be able to publish my website for anyone that could clone my git -repository. -So I will use emacs projectile to find the root directory of the current -project. - #+begin_src elisp - (setq base-dir (concat (projectile-project-root) "src")) - (setq publish-dir (concat (projectile-project-root) "_site")) - (setq org-publish-project-alist - `(("orgfiles" - :base-directory ,base-dir - :publishing-directory ,publish-dir - :recursive t - :base-extension "org"))) +;; CONFIGURATION VARIABLES +(setq domainname "https://her.esy.fun") +(setq base-dir (concat (projectile-project-root) "src")) +(setq publish-dir (concat (projectile-project-root) "_site")) +(setq assets-dir (concat base-dir "/")) +(setq publish-assets-dir (concat publish-dir "/")) +(setq rss-dir base-dir) +(setq rss-title "Subscribe to articles") +(setq publish-rss-dir publish-dir) +(setq css-path "/css/minimalist.css") +(setq author-name "Yann Esposito") +(setq author-email "yann@esposito.host") #+end_src -That's better. -Now anyone can clone the repository. -Open my =index.org= file, and execute the code in it by =C-e C-e= and then will -be able to publish the website locally in the =_site= directory. - -** Assets - :PROPERTIES: - :CUSTOM_ID: assets-cf7d - :END: - -Generally you want a bit more features than that for publishing a blog. -Typically you would like to have a common template, an header, some CSS. - -For that you need to work a bit more. -First, important thing, add an org publish section to publish your assets (CSS, -images, etc...) - -#+begin_src elisp - (setq base-dir (concat (projectile-project-root) "src")) - (setq publish-dir (concat (projectile-project-root) "_site")) - (setq assets-dir (concat base-dir "/assets")) - (setq publish-assets-dir (concat publish-dir "/assets")) - (setq org-publish-project-alist - `(("orgfiles" - :base-directory ,base-dir - :publishing-directory ,publish-dir - :recursive t - :base-extension "org" - :exclude ".*drafts/.*") - - ("assets" - :base-directory ,assets-dir - :base-extension ".*" - :publishing-directory ,publish-assets-dir - :publishing-function org-publish-attachment - :recursive t) - - ("blog" :components ("orgfiles" "assets")))) -#+end_src - -With that you will copy the assets and export org file to html. -You can do both by selecting `blog` when doing an `org-publish`. - -That's better. But for a blog you also generally want to have your own CSS. - -* Current Solution +* Full Solution :PROPERTIES: - :CUSTOM_ID: current-solution + :CUSTOM_ID: full-solution :END: #+begin_src elisp - ;; Global variables - (setq domainname "https://her.esy.fun") - (setq base-dir (concat (projectile-project-root) "src")) - (setq publish-dir (concat (projectile-project-root) "_site")) - (setq assets-dir (concat base-dir "/assets")) - (setq publish-assets-dir (concat publish-dir "/assets")) - (setq rss-dir base-dir) - (setq publish-rss-dir publish-dir) - (setq css-name "minimalist.css") - (setq author-name "Yann Esposito") - (setq author-email "yann@esposito.host") - (require 'org) - (require 'ox-publish) - (require 'ox-html) - (require 'org-element) - (require 'ox-rss) +(require 'org) +(require 'ox-publish) +(require 'ox-html) +(require 'org-element) +(require 'ox-rss) - (defun org-blog-prepare (project-plist) - "With help from `https://github.com/howardabrams/dot-files'. - Touch `index.org' to rebuilt it. - Argument `PROJECT-PLIST' contains information about the current project." - (let* ((base-directory (plist-get project-plist :base-directory)) - (buffer (find-file-noselect (expand-file-name "index.org" base-directory) t))) - (with-current-buffer buffer - (set-buffer-modified-p t) - (save-buffer 0)))) +(setq org-link-file-path-type 'relative) - (defvar org-blog-head - (concat - "" - "" - "" - "")) +(defun org-blog-prepare (project-plist) + "With help from `https://github.com/howardabrams/dot-files'. +Touch `index.org' to rebuilt it. +Argument `PROJECT-PLIST' contains information about the current project." + (let* ((base-directory (plist-get project-plist :base-directory)) + (buffer (find-file-noselect (expand-file-name "index.org" base-directory) t))) + (with-current-buffer buffer + (set-buffer-modified-p t) + (save-buffer 0)))) - (defun menu (lst) - "Blog menu" - (concat - "" - (mapconcat 'identity - (append - '("Home" - "Posts") - lst) - " | ") - "")) +(defvar org-blog-head + (concat + "" + "" + "" + "")) - (defun str-time-to-year-float (date-str) - (/ (float-time - (apply 'encode-time - (mapcar (lambda (x) (if (null x) 0 x)) - (parse-time-string date-str)))) - (* 365.25 24 60 60))) +(defun menu (lst) + "Blog menu" + (concat + "" + (mapconcat 'identity + (append + '("Home" + "Posts" + "About") + lst) + " | ") + "")) - (defvar blog-creation-date "2019-07-01") - (defun delta-date (date-str) - "Number of year since the begining of this blog" - (let ((y (- (str-time-to-year-float date-str) - (str-time-to-year-float blog-creation-date)))) - (format "∆t=%.2f" y))) +(defun str-time-to-year-float (date-str) + (/ (float-time + (apply 'encode-time + (mapcar (lambda (x) (if (null x) 0 x)) + (parse-time-string date-str)))) + (* 365.25 24 60 60))) - (defun org-blog-preamble (info) - "Pre-amble for whole blog." - (concat - "
    " - (menu '("↓ bottom ↓")) - "

    " - (format "%s" (plist-get info :title)) - (when-let ((date (get-from-info info :date))) - (format " - %s" date)) - "

    " - "
    ")) +(defvar blog-creation-date "2019-07-01") - (defun get-from-info (info k) - (let ((i (car (plist-get info k)))) - (when (and i (stringp i)) - i))) +(defun y-date (date-str) + "Number of year since the begining of this blog" + (let ((y (- (str-time-to-year-float date-str) + (str-time-to-year-float blog-creation-date)))) + (format "∆t=%.2f" y))) - (defun rand-obfs (c) - (let ((r (% (random) 20))) - (cond ((eq 0 r) (format "%c" c)) - ((< 0 r 10) (format "&#%d;" c)) - (t (format "&#x%X;" c))))) +(defun org-blog-preamble (info) + "Pre-amble for whole blog." + (concat + "
    " + (menu '("↓ bottom ↓")) + "

    " + (format "%s" (car (plist-get info :title))) + "

    " + (when-let ((date (get-from-info info :date))) + (format "%s" date)) + (when-let ((subtitle (car (plist-get info :subtitle)))) + (format "

    %s

    " subtitle)) + "
    ")) - (defun obfuscate-html (txt) - (apply 'concat - (mapcar 'rand-obfs txt))) +(defun get-from-info (info k) + (let ((i (car (plist-get info k)))) + (when (and i (stringp i)) + i))) - (defun org-blog-postamble (info) - "Post-amble for whole blog." - (concat - "
    " - "" + (menu '("↑ Top ↑")) + "
    ")) - (defun org-blog-sitemap-format-entry (entry _style project) - "Return string for each ENTRY in PROJECT." - (when (s-starts-with-p "posts/" entry) - (format "@@html:@@ %s: @@html:@@ [[file:%s][%s]] @@html:@@" - (format-time-string "%Y-%m-%d" - (org-publish-find-date entry project)) - entry - (org-publish-find-title entry project)))) +(defun org-blog-sitemap-format-entry (entry _style project) + "Return string for each ENTRY in PROJECT." + (when (s-starts-with-p "posts/" entry) + (format "@@html:@@ %s: @@html:@@ [[file:%s][%s]] @@html:@@" + (format-time-string "%Y-%m-%d" + (org-publish-find-date entry project)) + entry + (org-publish-find-title entry project)))) - (defun org-blog-sitemap-function (title list) - "Return sitemap using TITLE and LIST returned by `org-blog-sitemap-format-entry'." - (concat "#+TITLE: " title "\n" - "#+AUTHOR: " author-name "\n" - "#+EMAIL: " author-email "\n" - "\n#+begin_archive\n" - (mapconcat (lambda (li) - (format "@@html:
  • @@ %s @@html:
  • @@" (car li))) - (seq-filter #'car (cdr list)) - "\n") - "\n#+end_archive\n")) +(defun org-blog-sitemap-function (title list) + "Return sitemap using TITLE and LIST returned by `org-blog-sitemap-format-entry'." + (concat "#+TITLE: " title "\n" + "#+AUTHOR: " author-name "\n" + "#+EMAIL: " author-email "\n" + "\n#+begin_archive\n" + (mapconcat (lambda (li) + (format "@@html:
  • @@ %s @@html:
  • @@" (car li))) + (seq-filter #'car (cdr list)) + "\n") + "\n#+end_archive\n")) - (setq org-publish-project-alist - `(("orgfiles" - :base-directory ,base-dir - :exclude ".*drafts/.*" - :base-extension "org" +(defun org-blog-publish-to-html (plist filename pub-dir) + "Same as `org-html-publish-to-html' but modifies html before finishing." + (let ((file-path (org-html-publish-to-html plist filename pub-dir))) + (with-current-buffer (find-file-noselect file-path) + (goto-char (point-min)) + (search-forward "") + (insert (mapconcat 'identity + '("" + "" + "
    " + "
    " + "" + " / " + "" + "
    " + "
    " + "
    ") + "\n")) + (goto-char (point-max)) + (search-backward "") + (insert "\n
    \n") + (save-buffer) + (kill-buffer)) + file-path)) - :publishing-directory ,publish-dir +(setq org-publish-project-alist + `(("orgfiles" + :base-directory ,base-dir + :exclude ".*drafts/.*" + :base-extension "org" + :publishing-directory ,publish-dir - :recursive t - :preparation-function org-blog-prepare - :publishing-function org-html-publish-to-html + :recursive t + :preparation-function org-blog-prepare + :publishing-function org-blog-publish-to-html - :with-toc nil - :with-title nil - :with-date t - :section-numbers nil - :html-doctype "html5" - :html-html5-fancy t - :html-head-include-default-style nil - :html-head-include-scripts nil - :htmlized-source t - :html-head-extra ,org-blog-head - :html-preamble org-blog-preamble - :html-postamble org-blog-postamble + :with-toc nil + :with-title nil + :with-date t + :section-numbers nil + :html-doctype "html5" + :html-html5-fancy t + :html-head-include-default-style nil + :html-head-include-scripts nil + :htmlized-source t + :html-head-extra ,org-blog-head + :html-preamble org-blog-preamble + :html-postamble org-blog-postamble - :auto-sitemap t - :sitemap-filename "archive.org" - :sitemap-title "Blog Posts" - :sitemap-style list - :sitemap-sort-files anti-chronologically - :sitemap-format-entry org-blog-sitemap-format-entry - :sitemap-function org-blog-sitemap-function) + :auto-sitemap t + :sitemap-filename "archive.org" + :sitemap-title "Blog Posts" + :sitemap-style list + :sitemap-sort-files anti-chronologically + :sitemap-format-entry org-blog-sitemap-format-entry + :sitemap-function org-blog-sitemap-function) - ("assets" - :base-directory ,assets-dir - :base-extension ".*" - :publishing-directory ,publish-assets-dir - :publishing-function org-publish-attachment - :recursive t) + ("assets" + :base-directory ,assets-dir + :base-extension ".*" + :exclude ".*\.org$" + :publishing-directory ,publish-assets-dir + :publishing-function org-publish-attachment + :recursive t) - ("rss" - :base-directory ,rss-dir - :base-extension "org" - :html-link-home ,domainname - :html-link-use-abs-url t - :rss-extension "xml" - :publishing-directory ,publish-rss-dir - :publishing-function (org-rss-publish-to-rss) - :exclude ".*" - :include ("archive.org") - :section-numbers nil - :table-of-contents nil) + ("rss" + :base-directory ,rss-dir + :base-extension "org" + :html-link-home ,domainname + :html-link-use-abs-url t + :rss-extension "xml" + :publishing-directory ,publish-rss-dir + :publishing-function (org-rss-publish-to-rss) + :exclude ".*" + :include ("archive.org") + :section-numbers nil + :table-of-contents nil) - ("blog" :components ("orgfiles" "assets" "rss")))) + ("blog" :components ("orgfiles" "assets" "rss")))) - ;; add target=_blank and rel="noopener noreferrer" to all links by default - (defun my-org-export-add-target-blank-to-http-links (text backend info) - "Add target=\"_blank\" to external links." - (when (and - (org-export-derived-backend-p backend 'html) - (string-match "href=\"http[^\"]+" text) - (not (string-match "target=\"" text)) - (not (string-match (concat "href=\"" domainname "[^\"]*") text))) - (string-match "