summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.org6
-rw-r--r--docker-destroy.yaml4
-rw-r--r--docker-inventory.yaml1
-rw-r--r--docker-reuse-playbook.yaml10
-rw-r--r--inventory.yaml3
-rw-r--r--pad/defaults/main.yml3
-rw-r--r--pad/tasks/main.yml56
-rwxr-xr-xpad/templates/etherpad.init.d79
-rw-r--r--pad/templates/settings.json2
-rw-r--r--prod-playbook.yaml9
-rw-r--r--prod-vars.yml.sample2
-rw-r--r--talks.json1
-rw-r--r--wiki-edit/tasks/main.yaml42
-rw-r--r--wiki/tasks/main.yaml33
-rw-r--r--wiki/templates/Scrubber.pm749
-rw-r--r--wiki/templates/copyright.pm60
-rw-r--r--wiki/templates/emacsconf.setup443
-rwxr-xr-xwiki/templates/htmlscrubber.pm132
-rw-r--r--wiki/templates/license.pm59
19 files changed, 1684 insertions, 10 deletions
diff --git a/README.org b/README.org
index 0b3c622..7554082 100644
--- a/README.org
+++ b/README.org
@@ -1,6 +1,8 @@
This repository contains infrastructure-as-code ansible configurations
for various pieces of the EmacsConf infrastructure.
+ansible-galaxy collection install community.general
+
* Front - publishing
** Prod
To run the playbook and publish the main schedule:
@@ -27,6 +29,8 @@ ansible-playbook -i docker-inventory.yaml docker-reuse-playbook.yaml --tags pad
Connecting:
docker exec -it emacsconf-pad /bin/bash
+Creating pads
+ansible-playbook -i docker-inventory.yaml docker-reuse-playbook.yaml --tags create-pads
file:/docker:emacsconf-pad:/home/etherpad/etherpad/
@@ -52,4 +56,6 @@ curl "http://localhost:9001/api/1/createPad?apikey=$padkey&padID=emacsconf-2022"
curl http://localhost:9001/api/1/createPad?apikey=b7a15dc34cc7f6917cca6cd9a2b4b92145af7c7cd9b341af34869ab8cd3568be&padID=emacsconf-2022
{"code":0,"message":"ok","data":null}
:end:
+** TODO Set etherpad_database_password and etherpad_api_key using the vault or the command-line
+ansible-playbook .... --extra-vars prod-vars.yml
diff --git a/docker-destroy.yaml b/docker-destroy.yaml
index 4a915c3..f401dba 100644
--- a/docker-destroy.yaml
+++ b/docker-destroy.yaml
@@ -3,7 +3,7 @@
hosts: localhost
tags: front
tasks:
- - name: destroy docker container
+ - name: destroy front docker container
docker_container:
name: emacsconf-front
state: absent
@@ -12,7 +12,7 @@
hosts: localhost
tags: pad
tasks:
- - name: destroy docker container
+ - name: destroy pad docker container
docker_container:
name: emacsconf-pad
state: absent
diff --git a/docker-inventory.yaml b/docker-inventory.yaml
index 6caa3e3..b1be91e 100644
--- a/docker-inventory.yaml
+++ b/docker-inventory.yaml
@@ -8,3 +8,4 @@ all:
ansible_python_interpreter: /usr/bin/python3
localhost:
ansible_connection: local
+
diff --git a/docker-reuse-playbook.yaml b/docker-reuse-playbook.yaml
index 742a193..c187817 100644
--- a/docker-reuse-playbook.yaml
+++ b/docker-reuse-playbook.yaml
@@ -1,4 +1,12 @@
---
+- name: Load talks
+ hosts: localhost
+ tags: create-pads
+ tasks:
+ - include_vars:
+ file: talks.json
+ name: talks
+
- name: Set up the emacsconf-front wiki
hosts: emacsconf-front
tags: front
@@ -10,4 +18,4 @@
tags: pad
roles:
- pad
-
+
diff --git a/inventory.yaml b/inventory.yaml
index 964b8d5..69b3213 100644
--- a/inventory.yaml
+++ b/inventory.yaml
@@ -6,3 +6,6 @@ prod:
publish:
ansible_host: front0.emacsconf.org
remote_user: orga
+ pad:
+ ansible_host: front0.emacsconf.org
+ remote_user: orga
diff --git a/pad/defaults/main.yml b/pad/defaults/main.yml
index 398c9a2..386abf7 100644
--- a/pad/defaults/main.yml
+++ b/pad/defaults/main.yml
@@ -6,3 +6,6 @@ etherpad_database_name: emacsconf_pad
etherpad_database_user: etherpad
etherpad_user: etherpad
etherpad_group: etherpad
+etherpad_api_key: b7a15dc34cc7f6917cca6cd9a2b4b92145af7c7cd9b341af34869ab8cd3568be
+etherpad_base: emacsconf
+etherpad_year: 2022
diff --git a/pad/tasks/main.yml b/pad/tasks/main.yml
index ab15984..8637406 100644
--- a/pad/tasks/main.yml
+++ b/pad/tasks/main.yml
@@ -12,6 +12,7 @@
apt:
update_cache: yes
name:
+ - systemd
- sudo
- nodejs
- mariadb-server
@@ -34,14 +35,57 @@
template:
src: templates/settings.json
dest: "{{ etherpad_path }}/settings.json"
-# - name: Install etherpad plugins
-# npm:
-# name: ep_stats
-# path: "{{ etherpad_path }}"
-# become: true
-# become_user: etherpad
+- name: Set etherpad API key
+ copy:
+ content: "{{ etherpad_api_key }}"
+ dest: "{{ etherpad_path }}/APIKEY.txt"
+ owner: "{{ etherpad_user }}"
+ mode: "0600"
- name: Install dependencies
shell: cd {{ etherpad_path }}; . src/bin/functions.sh; src/bin/installDeps.sh
become: true
become_user: etherpad
+- name: Install etherpad plugins
+ npm:
+ name: ep_pad-lister
+ path: "{{ etherpad_path }}"
+ become: true
+ become_user: etherpad
- include: mariadb.yml
+- name: Install init.d configuration
+ tags: system
+ template:
+ src: etherpad.init.d
+ dest: /etc/init.d/etherpad
+ owner: root
+ group: root
+ mode: 0755
+- name: Start Etherpad
+ tags: system
+ service:
+ name: etherpad
+ state: started
+- tags: create-pads
+ include_vars:
+ file: talks.json
+ name: talks
+- name: Set slugs
+ tags: create-pads
+ set_fact:
+ slugs:
+- name: Wait for OK
+ tags: create-pads
+ uri:
+ url: "http://localhost:9001/api/1/createPad?apikey={{ etherpad_api_key }}&padID={{etherpad_base}}-{{etherpad_year}}"
+ register: _result
+ until: _result.status == 200
+ retries: 720
+ delay: 5
+- debug:
+ var: _result
+ tags: create-pads
+- name: Create pads
+ tags: create-pads
+ uri:
+ url: "http://localhost:9001/api/1/createPad?apikey={{ etherpad_api_key }}&padID={{etherpad_base}}-{{etherpad_year}}-{{ item.slug }}"
+ loop: "{{ talks | json_query('talks[*]') }}"
diff --git a/pad/templates/etherpad.init.d b/pad/templates/etherpad.init.d
new file mode 100755
index 0000000..420ae27
--- /dev/null
+++ b/pad/templates/etherpad.init.d
@@ -0,0 +1,79 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides: etherpad-lite
+# Required-Start: $local_fs $remote_fs $network $syslog
+# Required-Stop: $local_fs $remote_fs $network $syslog
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: starts etherpad lite
+# Description: starts etherpad lite using start-stop-daemon
+### END INIT INFO
+
+PATH="/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
+LOGFILE="{{ etherpad_path }}/etherpad-lite.log"
+EPLITE_DIR="{{ etherpad_path }}"
+EPLITE_BIN="src/bin/safeRun.sh"
+USER="{{ etherpad_user }}"
+GROUP="{{ etherpad_group }}"
+DESC="Etherpad Lite"
+NAME="etherpad"
+
+set -e
+
+. /lib/lsb/init-functions
+
+start() {
+ echo "Starting $DESC... "
+
+ start-stop-daemon --start --chuid "$USER:$GROUP" -d $EPLITE_DIR --background --make-pidfile --pidfile /var/run/$NAME.pid --exec $EPLITE_DIR/$EPLITE_BIN -- $LOGFILE || true
+ echo "done"
+}
+
+#We need this function to ensure the whole process tree will be killed
+killtree() {
+ local _pid=$1
+ local _sig=${2-TERM}
+ for _child in $(ps -o pid --no-headers --ppid ${_pid}); do
+ killtree ${_child} ${_sig}
+ done
+ kill -${_sig} ${_pid}
+}
+
+stop() {
+ echo "Stopping $DESC... "
+ if test -f /var/run/$NAME.pid; then
+ while test -d /proc/$(cat /var/run/$NAME.pid); do
+ killtree $(cat /var/run/$NAME.pid) 15
+ sleep 0.5
+ done
+ rm /var/run/$NAME.pid
+ fi
+ echo "done"
+}
+
+status() {
+ status_of_proc -p /var/run/$NAME.pid "" "etherpad-lite" && exit 0 || exit $?
+}
+
+case "$1" in
+ start)
+ start
+ ;;
+ stop)
+ stop
+ ;;
+ restart)
+ stop
+ start
+ ;;
+ status)
+ status
+ ;;
+ *)
+ echo "Usage: $NAME {start|stop|restart|status}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0 \ No newline at end of file
diff --git a/pad/templates/settings.json b/pad/templates/settings.json
index 137ce03..1832ef3 100644
--- a/pad/templates/settings.json
+++ b/pad/templates/settings.json
@@ -215,7 +215,7 @@
/*
* The default text of a pad
*/
- "defaultPadText" : "Conference info, how to watch/participate: https://emacsconf.org/2022/\nGuidelines for conduct: https://emacsconf.org/conduct/\nIRC: channel #emacsconf via https://chat.emacsconf.org or libera.chat network\n\nExcept where otherwise noted, the material on the EmacsConf pad are dual-licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International Public License; and the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) an later version. Copies of these two licenses are included in the EmacsConf wiki repository, in the COPYING.GPL and COPYING.CC-BY-SA files (https://emacsconf.org/COPYING/)\n\nBy contributing to this pad, you agree to make your contributions available under the above licenses. You are also promising that you are the author of your changes, or that you copied them from a work in the public domain or a work released under a free license that is compatible with the above two licenses. DO NOT SUBMIT COPYRIGHTED WORK WITHOUT PERMISSION.",
+ "defaultPadText" : "Conference info, how to watch/participate: https://emacsconf.org/2022/\nGuidelines for conduct: https://emacsconf.org/conduct/\nIRC: channel #emacsconf via https://chat.emacsconf.org or libera.chat network\nSee end of file for license (CC Attribution-ShareAlike 4.0 + GPLv3 or later)\n----------------------------------------------------------------\nQuestions and discussion go here:\n<ul><li></li><li></li><li></li></ul>\n----------------------------------------------------------------\nExcept where otherwise noted, the material on the EmacsConf pad are dual-licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 International Public License; and the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) an later version. Copies of these two licenses are included in the EmacsConf wiki repository, in the COPYING.GPL and COPYING.CC-BY-SA files (https://emacsconf.org/COPYING/)\n\nBy contributing to this pad, you agree to make your contributions available under the above licenses. You are also promising that you are the author of your changes, or that you copied them from a work in the public domain or a work released under a free license that is compatible with the above two licenses. DO NOT SUBMIT COPYRIGHTED WORK WITHOUT PERMISSION.",
/*
* Default Pad behavior.
diff --git a/prod-playbook.yaml b/prod-playbook.yaml
new file mode 100644
index 0000000..83f7e7f
--- /dev/null
+++ b/prod-playbook.yaml
@@ -0,0 +1,9 @@
+- name: Set up wiki for publishing
+ hosts: publish
+ roles:
+ - wiki-edit
+- name: Set up pad
+ hosts: pad
+ vars_files: prod-vars.yml
+ roles:
+ - pad
diff --git a/prod-vars.yml.sample b/prod-vars.yml.sample
new file mode 100644
index 0000000..4fd2df4
--- /dev/null
+++ b/prod-vars.yml.sample
@@ -0,0 +1,2 @@
+etherpad_api_key: CHANGEME
+etherpad_database_password: CHANGEME
diff --git a/talks.json b/talks.json
new file mode 100644
index 0000000..85c716d
--- /dev/null
+++ b/talks.json
@@ -0,0 +1 @@
+{"talks":[{"start-time":"2022-12-03T14:05:00+0000","end-time":"2022-12-03T14:25:00+0000","slug":"journalism","title":"Emacs journalism (or everything's a nail if you hit it with Emacs)","speakers":"Alfred Zanini","pronouns":"he/they","pronunciation":null,"url":"2022/talks/journalism","track":"General"},{"start-time":"2022-12-03T14:45:00+0000","end-time":"2022-12-03T15:05:00+0000","slug":"school","title":"Back to school with Emacs","speakers":"Daniel Rösel","pronouns":null,"pronunciation":"ˈrøːzl̩","url":"2022/talks/school","track":"General"},{"start-time":"2022-12-03T15:15:00+0000","end-time":"2022-12-03T15:25:00+0000","slug":"handwritten","title":"How to incorporate handwritten notes into Emacs Orgmode","speakers":"Bala Ramadurai","pronouns":"his/him","pronunciation":null,"url":"2022/talks/handwritten","track":"General"},{"start-time":"2022-12-03T15:55:00+0000","end-time":"2022-12-03T16:15:00+0000","slug":"science","title":"Writing and organizing literature notes for scientific writing","speakers":"Vidianos","pronouns":"nil","pronunciation":null,"url":"2022/talks/science","track":"General"},{"start-time":"2022-12-03T18:00:00+0000","end-time":"2022-12-03T18:10:00+0000","slug":"meetups","title":"Attending and organizing Emacs meetups","speakers":"Bhavin Gandhi","pronouns":"he/him","pronunciation":null,"url":"2022/talks/meetups","track":"General"},{"start-time":"2022-12-03T18:30:00+0000","end-time":"2022-12-03T18:40:00+0000","slug":"buddy","title":"The Emacs Buddy initiative","speakers":"Andrea","pronouns":null,"pronunciation":null,"url":"2022/talks/buddy","track":"General"},{"start-time":"2022-12-03T18:50:00+0000","end-time":"2022-12-03T19:20:00+0000","slug":"community","title":"The ship that builds itself: How we used Emacs to develop a workshop for communities","speakers":"Noorah Alhasan, Joseph Corneli, Leo Vivier","pronouns":"Noorah: she/her, Joseph: he/him, Leo: he/him","pronunciation":null,"url":"2022/talks/community","track":"General"},{"start-time":"2022-12-03T19:50:00+0000","end-time":"2022-12-03T20:10:00+0000","slug":"realestate","title":"Real estate and Org table formulas","speakers":"Daniel Gopar","pronouns":"He/Him","pronunciation":null,"url":"2022/talks/realestate","track":"General"},{"start-time":"2022-12-03T20:20:00+0000","end-time":"2022-12-03T20:40:00+0000","slug":"health","title":"Health data journaling and visualization with Org Mode and GNUplot","speakers":"David O'Toole","pronouns":"he/him","pronunciation":null,"url":"2022/talks/health","track":"General"},{"start-time":"2022-12-03T21:00:00+0000","end-time":"2022-12-03T21:10:00+0000","slug":"jupyter","title":"Edit live Jupyter notebook cells with Emacs","speakers":"Blaine Mooers","pronouns":"he/him","pronunciation":"Blane Moors","url":"2022/talks/jupyter","track":"General"},{"start-time":"2022-12-04T14:05:00+0000","end-time":"2022-12-04T14:25:00+0000","slug":"survey","title":"Results of the 2022 Emacs Survey","speakers":"Timothy","pronouns":"he/him","pronunciation":null,"url":"2022/talks/survey","track":"General"},{"start-time":"2022-12-04T14:35:00+0000","end-time":"2022-12-04T14:45:00+0000","slug":"orgyear","title":"This Year in Org","speakers":"Timothy","pronouns":"he/him","pronunciation":null,"url":"2022/talks/orgyear","track":"General"},{"start-time":"2022-12-04T15:00:00+0000","end-time":"2022-12-04T15:20:00+0000","slug":"rolodex","title":"Build a Zettelkasten with the Hyperbole Rolodex","speakers":"Ramin Honary","pronouns":"he/him","pronunciation":"\"Rah-mean\" (hard-H) \"Ho-na-ree\"","url":"2022/talks/rolodex","track":"General"},{"start-time":"2022-12-04T15:40:00+0000","end-time":"2022-12-04T15:50:00+0000","slug":"orgsuperlinks","title":"Linking headings with org-super-links (poor-man's Zettelkasten)","speakers":"Karl Voit","pronouns":"he/him","pronunciation":null,"url":"2022/talks/orgsuperlinks","track":"General"},{"start-time":"2022-12-04T16:10:00+0000","end-time":"2022-12-04T16:20:00+0000","slug":"buttons","title":"Linking personal info with Hyperbole implicit buttons","speakers":"Mats Lidell","pronouns":"He,Him,His","pronunciation":null,"url":"2022/talks/buttons","track":"General"},{"start-time":"2022-12-04T18:00:00+0000","end-time":"2022-12-04T18:30:00+0000","slug":"hyperorg","title":"Powerful productivity with Hyperbole and Org Mode","speakers":"Robert Weiner","pronouns":null,"pronunciation":"wine-er","url":"2022/talks/hyperorg","track":"General"},{"start-time":"2022-12-04T18:50:00+0000","end-time":"2022-12-04T19:10:00+0000","slug":"workflows","title":"Org workflows for developers","speakers":"George Mauer","pronouns":"he/him/they/ze","pronunciation":null,"url":"2022/talks/workflows","track":"General"},{"start-time":"2022-12-04T19:30:00+0000","end-time":"2022-12-04T19:50:00+0000","slug":"grail","title":"GRAIL---A Generalized Representation and Aggregation of Information Layers","speakers":"Sameer Pradhan","pronouns":"he/him","pronunciation":null,"url":"2022/talks/grail","track":"General"},{"start-time":"2022-12-03T21:30:00+0000","end-time":"2022-12-03T21:40:00+0000","slug":"orgvm","title":"orgvm: a simple HTTP server for org","speakers":"Corwin Brust","pronouns":"he/him/any","pronunciation":null,"url":"2022/talks/orgvm","track":"General"},{"start-time":"2022-12-04T20:20:00+0000","end-time":"2022-12-04T20:40:00+0000","slug":"indieweb","title":"Putting Org Mode on the Indieweb","speakers":"Michael Herstine","pronouns":null,"pronunciation":null,"url":"2022/talks/indieweb","track":"General"},{"start-time":"2022-12-04T21:00:00+0000","end-time":"2022-12-04T21:10:00+0000","slug":"fanfare","title":"Fanfare for the Common Emacs User","speakers":"John Cummings","pronouns":null,"pronunciation":null,"url":"2022/talks/fanfare","track":"General"},{"start-time":"2022-12-04T21:00:00+0000","end-time":"2022-12-04T21:20:00+0000","slug":"localizing","title":"Pre-localizing Emacs","speakers":"Jean-Christophe Helary","pronouns":"he/him","pronunciation":null,"url":"2022/talks/localizing","track":"Development"},{"start-time":"2022-12-03T15:00:00+0000","end-time":"2022-12-03T15:10:00+0000","slug":"treesitter","title":"Tree-sitter beyond syntax highlighting","speakers":"Abin Simon","pronouns":"nil","pronunciation":null,"url":"2022/talks/treesitter","track":"Development"},{"start-time":"2022-12-03T15:20:00+0000","end-time":"2022-12-03T15:40:00+0000","slug":"lspbridge","title":"lsp-bridge: complete asynchronous LSP client","speakers":"Andy Stewart, Matthew Zeng","pronouns":"nil","pronunciation":null,"url":"2022/talks/lspbridge","track":"Development"},{"start-time":"2022-12-03T18:00:00+0000","end-time":"2022-12-03T18:20:00+0000","slug":"sqlite","title":"Using SQLite as a data source: a framework and an example","speakers":"Andrew Hyatt","pronouns":"he/him","pronunciation":null,"url":"2022/talks/sqlite","track":"Development"},{"start-time":"2022-12-03T18:45:00+0000","end-time":"2022-12-03T19:15:00+0000","slug":"mail","title":"Revisiting the anatomy of Emacs mail user agents","speakers":"Mohsen BANAN","pronouns":"he/him","pronunciation":"MO-HH-SS-EN","url":"2022/talks/mail","track":"Development"},{"start-time":"2022-12-03T20:35:00+0000","end-time":"2022-12-03T20:40:00+0000","slug":"eev","title":"Bidirectional links with eev","speakers":"Eduardo Ochs","pronouns":"nil","pronunciation":null,"url":"2022/talks/eev","track":"Development"},{"start-time":"2022-12-03T20:50:00+0000","end-time":"2022-12-03T20:55:00+0000","slug":"python","title":"Short hyperlinks to Python docs","speakers":"Eduardo Ochs","pronouns":null,"pronunciation":null,"url":"2022/talks/python","track":"Development"},{"start-time":"2022-12-03T19:50:00+0000","end-time":"2022-12-03T20:10:00+0000","slug":"maint","title":"Maintaining the Maintainers: Attribution as an Economic Model for Open Source","speakers":"Sid Kasivajhula","pronouns":"any pronouns, commonly he/him","pronunciation":null,"url":"2022/talks/maint","track":"Development"},{"start-time":"2022-12-03T21:05:00+0000","end-time":"2022-12-03T21:35:00+0000","slug":"haskell","title":"Haskell code exploration with Emacs","speakers":"Yuchen Pei","pronouns":null,"pronunciation":"he/him/himself/his/his","url":"2022/talks/haskell","track":"Development"},{"start-time":"2022-12-04T15:00:00+0000","end-time":"2022-12-04T15:20:00+0000","slug":"rde","title":"rde Emacs introduction","speakers":"Andrew Tropin","pronouns":"he/him","pronunciation":null,"url":"2022/talks/rde","track":"Development"},{"start-time":"2022-12-04T15:45:00+0000","end-time":"2022-12-04T15:55:00+0000","slug":"justl","title":"justl: Driving recipes within Emacs","speakers":"Sibi Prabakaran","pronouns":"He/Him","pronunciation":null,"url":"2022/talks/justl","track":"Development"},{"start-time":"2022-12-04T16:05:00+0000","end-time":"2022-12-04T16:35:00+0000","slug":"tramp","title":"Elisp and the TRAMP: How to NOT write code you don't have to","speakers":"Grant Shangreaux","pronouns":"he/him","pronunciation":"Shang-groo or Shang-grow are fine","url":"2022/talks/tramp","track":"Development"},{"start-time":"2022-12-04T18:00:00+0000","end-time":"2022-12-04T18:10:00+0000","slug":"detached","title":"Getting detached from Emacs","speakers":"Niklas Eklund","pronouns":"he/him","pronunciation":null,"url":"2022/talks/detached","track":"Development"},{"start-time":"2022-12-04T18:35:00+0000","end-time":"2022-12-04T18:45:00+0000","slug":"eshell","title":"Top 10 reasons why you should be using Eshell","speakers":"Howard Abrams","pronouns":"he/him","pronunciation":null,"url":"2022/talks/eshell","track":"Development"},{"start-time":"2022-12-04T19:10:00+0000","end-time":"2022-12-04T19:30:00+0000","slug":"async","title":"Emacs was async before async was cool","speakers":"Michael Herstine","pronouns":null,"pronunciation":null,"url":"2022/talks/async","track":"Development"},{"start-time":"2022-12-03T15:50:00+0000","end-time":"2022-12-03T16:00:00+0000","slug":"asmblox","title":"asm-blox: a game based on WebAssembly that no one asked for","speakers":"Zachary Romero","pronouns":"nil","pronunciation":null,"url":"2022/talks/asmblox","track":"Development"},{"start-time":"2022-12-04T20:05:00+0000","end-time":"2022-12-04T20:25:00+0000","slug":"dbus","title":"The Wheels on D-Bus","speakers":"Ian Eure","pronouns":"he/him/his","pronunciation":"ee-uhn you-er","url":"2022/talks/dbus","track":"Development"},{"start-time":"2022-12-03T16:25:00+0000","end-time":"2022-12-03T16:35:00+0000","slug":"wayland","title":"Emacs should become a Wayland compositor","speakers":"Michael Bauer","pronouns":null,"pronunciation":null,"url":"2022/talks/wayland","track":"Development"},{"start-time":"2022-12-04T23:00:00+0000","end-time":"2022-12-04T23:05:00+0000","slug":"news","title":"Emacs News highlights","speakers":"Sacha Chua","pronouns":"she/her","pronunciation":"SA-sha CHEW-ah","url":"2022/talks/news","track":null}]} \ No newline at end of file
diff --git a/wiki-edit/tasks/main.yaml b/wiki-edit/tasks/main.yaml
new file mode 100644
index 0000000..570c953
--- /dev/null
+++ b/wiki-edit/tasks/main.yaml
@@ -0,0 +1,42 @@
+---
+- name: Set up packages
+ become: yes
+ block:
+ - name: Add snapshot repository
+ ansible.builtin.apt_repository:
+ repo: deb http://emacs.ganneff.de/ buster main
+ - name: Remove old package
+ ansible.builtin.apt:
+ name: emacs
+ state: absent
+ - name: Install Emacs snapshot
+ ansible.builtin.apt:
+ name: emacs-snapshot-nox
+ state: present
+- name: Set up or update repositories
+ block:
+ - name: Check out wiki repository
+ ansible.builtin.git:
+ repo: git://git.emacsconf.org/emacsconf-wiki
+ dest: ~/emacsconf-wiki
+ - name: Check out emacsconf-el
+ ansible.builtin.git:
+ repo: git@git.emacsconf.org:pub/emacsconf-el
+ dest: ~/emacsconf-el
+ - name: Check out emacsconf-2022-private
+ ansible.builtin.git:
+ repo: git@git.emacsconf.org:emacsconf-2022-private
+ dest: ~/emacsconf-2022-private
+ register: private
+- name: Publish
+ tags: publish
+ block:
+ - name: Publish the schedule
+ command: emacs -l ~/.emacs.d/init.el --batch --exec '(emacsconf-generate-main-schedule)'
+ when: private.changed and slug is not defined
+ - name: Update a specific talk's nav page
+ tags: publish-talk
+ command: emacs -l ~/.emacs.d/init.el --batch --exec '(emacsconf-with-talk-heading "{{ slug }}" (emacsconf-update-talk))'
+ when: slug is defined
+ - name: Commit the wiki and push
+ shell: cd ~/emacsconf-wiki; git commit -m 'Update from ansible' -a; git push
diff --git a/wiki/tasks/main.yaml b/wiki/tasks/main.yaml
new file mode 100644
index 0000000..c685515
--- /dev/null
+++ b/wiki/tasks/main.yaml
@@ -0,0 +1,33 @@
+---
+- name: Set up packages
+ ansible.builtin.apt:
+ pkg:
+ - ikiwiki
+ - git
+ - openssh-server
+ - wget
+ state: present
+- name: Create ikiwiki user
+ user:
+ name: ikiwiki
+- name: Set up SSH directory
+ ansible.builtin.file:
+ path: /home/ikiwiki/.ssh
+ state: directory
+ mode: '0700'
+- name: Install SSH key for EmacsConf wiki
+ ansible.builtin.get_url:
+ url: https://emacsconf.org/id_rsa_anon_git_emacsconf
+ dest: /home/ikiwiki/.ssh/id_rsa_anon_git_emacsconf
+ mode: '0600'
+ owner: 'ikiwiki'
+- name: Set up or update repositories
+ become: ikiwiki
+ ansible.builtin.git:
+ repo: git://git.emacsconf.org/emacsconf-wiki
+ dest: /home/ikiwiki/emacsconf-wiki
+- name: Template the config
+ ansible.builtin.template:
+ src: /templates/emacsconf.setup
+ dest: /home/ikiwiki/emacsconf.setup
+ owner: ikiwiki
diff --git a/wiki/templates/Scrubber.pm b/wiki/templates/Scrubber.pm
new file mode 100644
index 0000000..2efaa10
--- /dev/null
+++ b/wiki/templates/Scrubber.pm
@@ -0,0 +1,749 @@
+package HTML::Scrubber;
+
+# ABSTRACT: Perl extension for scrubbing/sanitizing html
+
+
+use 5.008; # enforce minimum perl version of 5.8
+use strict;
+use warnings;
+use HTML::Parser 3.47 ();
+use HTML::Entities;
+use Scalar::Util ('weaken');
+
+our ( @_scrub, @_scrub_fh );
+
+our $VERSION = '0.15'; # VERSION
+our $AUTHORITY = 'cpan:NIGELM'; # AUTHORITY
+
+# my my my my, these here to prevent foolishness like
+# http://perlmonks.org/index.pl?node_id=251127#Stealing+Lexicals
+(@_scrub) = ( \&_scrub, "self, event, tagname, attr, attrseq, text" );
+(@_scrub_fh) = ( \&_scrub_fh, "self, event, tagname, attr, attrseq, text" );
+
+sub new {
+ my $package = shift;
+ my $p = HTML::Parser->new(
+ api_version => 3,
+ default_h => \@_scrub,
+ marked_sections => 0,
+ strict_comment => 0,
+ unbroken_text => 1,
+ case_sensitive => 0,
+ boolean_attribute_value => undef,
+ empty_element_tags => 1,
+ );
+
+ my $self = {
+ _p => $p,
+ _rules => { '*' => 0, },
+ _comment => 0,
+ _process => 0,
+ _r => "",
+ _optimize => 1,
+ _script => 0,
+ _style => 0,
+ };
+
+ $p->{"\0_s"} = bless $self, $package;
+ weaken( $p->{"\0_s"} );
+
+ return $self unless @_;
+
+ my (%args) = @_;
+
+ for my $f (qw[ default allow deny rules process comment ]) {
+ next unless exists $args{$f};
+ if ( ref $args{$f} ) {
+ $self->$f( @{ $args{$f} } );
+ }
+ else {
+ $self->$f( $args{$f} );
+ }
+ }
+
+ return $self;
+}
+
+
+sub comment {
+ return $_[0]->{_comment}
+ if @_ == 1;
+ $_[0]->{_comment} = $_[1];
+ return;
+}
+
+
+sub process {
+ return $_[0]->{_process}
+ if @_ == 1;
+ $_[0]->{_process} = $_[1];
+ return;
+}
+
+
+sub script {
+ return $_[0]->{_script}
+ if @_ == 1;
+ $_[0]->{_script} = $_[1];
+ return;
+}
+
+
+sub style {
+ return $_[0]->{_style}
+ if @_ == 1;
+ $_[0]->{_style} = $_[1];
+ return;
+}
+
+
+sub allow {
+ my $self = shift;
+ for my $k (@_) {
+ $self->{_rules}{ lc $k } = 1;
+ }
+ $self->{_optimize} = 1; # each time a rule changes, reoptimize when parse
+
+ return;
+}
+
+
+sub deny {
+ my $self = shift;
+
+ for my $k (@_) {
+ $self->{_rules}{ lc $k } = 0;
+ }
+
+ $self->{_optimize} = 1; # each time a rule changes, reoptimize when parse
+
+ return;
+}
+
+
+sub rules {
+ my $self = shift;
+ my (%rules) = @_;
+ for my $k ( keys %rules ) {
+ $self->{_rules}{ lc $k } = $rules{$k};
+ }
+
+ $self->{_optimize} = 1; # each time a rule changes, reoptimize when parse
+
+ return;
+}
+
+
+sub default {
+ return $_[0]->{_rules}{'*'}
+ if @_ == 1;
+
+ $_[0]->{_rules}{'*'} = $_[1] if defined $_[1];
+ $_[0]->{_rules}{'_'} = $_[2] if defined $_[2] and ref $_[2];
+ $_[0]->{_optimize} = 1; # each time a rule changes, reoptimize when parse
+
+ return;
+}
+
+
+sub scrub_file {
+ if ( @_ > 2 ) {
+ return unless defined $_[0]->_out( $_[2] );
+ }
+ else {
+ $_[0]->{_p}->handler( default => @_scrub );
+ }
+
+ $_[0]->_optimize(); #if $_[0]->{_optimize};
+
+ $_[0]->{_p}->parse_file( $_[1] );
+
+ return delete $_[0]->{_r} unless exists $_[0]->{_out};
+ print { $_[0]->{_out} } $_[0]->{_r} if length $_[0]->{_r};
+ delete $_[0]->{_out};
+ return 1;
+}
+
+
+sub scrub {
+ if ( @_ > 2 ) {
+ return unless defined $_[0]->_out( $_[2] );
+ }
+ else {
+ $_[0]->{_p}->handler( default => @_scrub );
+ }
+
+ $_[0]->_optimize(); # if $_[0]->{_optimize};
+
+ $_[0]->{_p}->parse( $_[1] ) if defined( $_[1] );
+ $_[0]->{_p}->eof();
+
+ return delete $_[0]->{_r} unless exists $_[0]->{_out};
+ delete $_[0]->{_out};
+ return 1;
+}
+
+
+sub _out {
+ my ( $self, $o ) = @_;
+
+ unless ( ref $o and ref \$o ne 'GLOB' ) {
+ open my $F, '>', $o or return;
+ binmode $F;
+ $self->{_out} = $F;
+ }
+ else {
+ $self->{_out} = $o;
+ }
+
+ $self->{_p}->handler( default => @_scrub_fh );
+
+ return 1;
+}
+
+
+sub _validate {
+ my ( $s, $t, $r, $a, $as ) = @_;
+ return "<$t>" unless %$a;
+
+ $r = $s->{_rules}->{$r};
+ my %f;
+
+ for my $k ( keys %$a ) {
+ my $check = exists $r->{$k} ? $r->{$k} : exists $r->{'*'} ? $r->{'*'} : next;
+
+ if ( ref $check eq 'CODE' ) {
+ my @v = $check->( $s, $t, $k, $a->{$k}, $a, \%f );
+ next unless @v;
+ $f{$k} = shift @v;
+ }
+ elsif ( ref $check || length($check) > 1 ) {
+ $f{$k} = $a->{$k} if $a->{$k} =~ m{$check};
+ }
+ elsif ($check) {
+ $f{$k} = $a->{$k};
+ }
+ }
+
+ if (%f) {
+ my %seen;
+ return "<$t $r>"
+ if $r = join ' ', map {
+ defined $f{$_}
+ ? qq[$_="] . encode_entities( $f{$_} ) . q["]
+ : $_; # boolean attribute (TODO?)
+ } grep { exists $f{$_} and !$seen{$_}++; } @$as;
+ }
+
+ return "<$t>";
+}
+
+
+sub _scrub_str {
+ my ( $p, $e, $t, $a, $as, $text ) = @_;
+
+ my $s = $p->{"\0_s"};
+ my $outstr = '';
+
+ if ( $e eq 'start' ) {
+ if ( exists $s->{_rules}->{$t} ) # is there a specific rule
+ {
+ if ( ref $s->{_rules}->{$t} ) # is it complicated?(not simple;)
+ {
+ $outstr .= $s->_validate( $t, $t, $a, $as );
+ }
+ elsif ( $s->{_rules}->{$t} ) # validate using default attribute rule
+ {
+ $outstr .= $s->_validate( $t, '_', $a, $as );
+ }
+ }
+ elsif ( $s->{_rules}->{'*'} ) # default allow tags
+ {
+ $outstr .= $s->_validate( $t, '_', $a, $as );
+ }
+ }
+ elsif ( $e eq 'end' ) {
+ my $place = 0;
+ if ( exists $s->{_rules}->{$t} ) {
+ $place = 1 if $s->{_rules}->{$t};
+ }
+ elsif ( $s->{_rules}->{'*'} ) {
+ $place = 1;
+ }
+ if ($place) {
+ if ( length $text ) {
+ $outstr .= "</$t>";
+ }
+ else {
+ substr $s->{_r}, -1, 0, ' /';
+ }
+ }
+ }
+ elsif ( $e eq 'comment' ) {
+ if ( $s->{_comment} ) {
+
+ # only copy comments through if they are well formed...
+ $outstr .= $text if ( $text =~ m|^<!--.*-->$|ms );
+ }
+ }
+ elsif ( $e eq 'process' ) {
+ $outstr .= $text if $s->{_process};
+ }
+ elsif ( $e eq 'text' or $e eq 'default' ) {
+ $text =~ s/</&lt;/g; #https://rt.cpan.org/Ticket/Attachment/8716/10332/scrubber.patch
+ $text =~ s/>/&gt;/g;
+
+ $outstr .= $text;
+ }
+ elsif ( $e eq 'start_document' ) {
+ $outstr = "";
+ }
+
+ return $outstr;
+}
+
+
+sub _scrub_fh {
+ my $self = $_[0]->{"\0_s"};
+ print { $self->{_out} } $self->{'_r'} if length $self->{_r};
+ $self->{'_r'} = _scrub_str(@_);
+}
+
+
+sub _scrub {
+
+ $_[0]->{"\0_s"}->{_r} .= _scrub_str(@_);
+}
+
+sub _optimize {
+ my ($self) = @_;
+
+ my (@ignore_elements) = grep { not $self->{"_$_"} } qw(script style);
+ $self->{_p}->ignore_elements(@ignore_elements); # if @ is empty, we reset ;)
+
+ return unless $self->{_optimize};
+
+ #sub allow
+ # return unless $self->{_optimize}; # till I figure it out (huh)
+
+ if ( $self->{_rules}{'*'} ) { # default allow
+ $self->{_p}->report_tags(); # so clear it
+ }
+ else {
+
+ my (@reports) =
+ grep { # report only tags we want
+ $self->{_rules}{$_}
+ } keys %{ $self->{_rules} };
+
+ $self->{_p}->report_tags( # default deny, so optimize
+ @reports
+ ) if @reports;
+ }
+
+ # sub deny
+ # return unless $self->{_optimize}; # till I figure it out (huh)
+ my (@ignores) =
+ grep { not $self->{_rules}{$_} } grep { $_ ne '*' } keys %{ $self->{_rules} };
+
+ $self->{_p}->ignore_tags( # always ignore stuff we don't want
+ @ignores
+ ) if @ignores;
+
+ $self->{_optimize} = 0;
+ return;
+}
+
+1;
+
+#print sprintf q[ '%-12s => %s,], "$_'", $h{$_} for sort keys %h;# perl!
+#perl -ne"chomp;print $_;print qq'\t\t# test ', ++$a if /ok\(/;print $/" test.pl >test2.pl
+#perl -ne"chomp;print $_;if( /ok\(/ ){s/\#test \d+$//;print qq'\t\t# test ', ++$a }print $/" test.pl >test2.pl
+#perl -ne"chomp;if(/ok\(/){s/# test .*$//;print$_,qq'\t\t# test ',++$a}else{print$_}print$/" test.pl >test2.pl
+
+__END__
+
+=pod
+
+=for stopwords html cpan callback homepage Perlbrew perltidy respository
+
+=head1 NAME
+
+HTML::Scrubber - Perl extension for scrubbing/sanitizing html
+
+=head1 VERSION
+
+version 0.15
+
+=head1 SYNOPSIS
+
+ use HTML::Scrubber;
+
+ my $scrubber = HTML::Scrubber->new( allow => [ qw[ p b i u hr br ] ] );
+ print $scrubber->scrub('<p><b>bold</b> <em>missing</em></p>');
+ # output is: <p><b>bold</b> </p>
+
+ # more complex input
+ my $html = q[
+ <style type="text/css"> BAD { background: #666; color: #666;} </style>
+ <script language="javascript"> alert("Hello, I am EVIL!"); </script>
+ <HR>
+ a => <a href=1>link </a>
+ br => <br>
+ b => <B> bold </B>
+ u => <U> UNDERLINE </U>
+ ];
+
+ print $scrubber->scrub($html);
+
+ $scrubber->deny( qw[ p b i u hr br ] );
+
+ print $scrubber->scrub($html);
+
+=head1 DESCRIPTION
+
+If you want to "scrub" or "sanitize" html input in a reliable and flexible
+fashion, then this module is for you.
+
+I wasn't satisfied with HTML::Sanitizer because it is based on
+HTML::TreeBuilder, so I thought I'd write something similar that works directly
+with HTML::Parser.
+
+=head1 METHODS
+
+First a note on documentation: just study the L<EXAMPLE|"EXAMPLE"> below. It's
+all the documentation you could need
+
+Also, be sure to read all the comments as well as L<How does it work?|"How does
+it work?">.
+
+If you're new to perl, good luck to you.
+
+=head2 comment
+
+ warn "comments are ", $p->comment ? 'allowed' : 'not allowed';
+ $p->comment(0); # off by default
+
+=head2 process
+
+ warn "process instructions are ", $p->process ? 'allowed' : 'not allowed';
+ $p->process(0); # off by default
+
+=head2 script
+
+ warn "script tags (and everything in between) are supressed"
+ if $p->script; # off by default
+ $p->script( 0 || 1 );
+
+B<**> Please note that this is implemented using HTML::Parser's ignore_elements
+function, so if C<script> is set to true, all script tags encountered will be
+validated like all other tags.
+
+=head2 style
+
+ warn "style tags (and everything in between) are supressed"
+ if $p->style; # off by default
+ $p->style( 0 || 1 );
+
+B<**> Please note that this is implemented using HTML::Parser's ignore_elements
+function, so if C<style> is set to true, all style tags encountered will be
+validated like all other tags.
+
+=head2 allow
+
+ $p->allow(qw[ t a g s ]);
+
+=head2 deny
+
+ $p->deny(qw[ t a g s ]);
+
+=head2 rules
+
+ $p->rules(
+ img => {
+ src => qr{^(?!http://)}i, # only relative image links allowed
+ alt => 1, # alt attribute allowed
+ '*' => 0, # deny all other attributes
+ },
+ a => {
+ href => sub { ... }, # check or adjust with a callback
+ },
+ b => 1,
+ ...
+ );
+
+Updates set of attribute rules. Each rule can be 1/0, regular expression or a
+callback. Values longer than 1 char are treated as regexps. Callback is called
+with the following arguments: this object, tag name, attribute name and
+attribute value, should return empty list to drop attribute, C<undef> to keep
+it without value or a new scalar value.
+
+=head2 default
+
+ print "default is ", $p->default();
+ $p->default(1); # allow tags by default
+ $p->default(
+ undef, # don't change
+ { # default attribute rules
+ '*' => 1, # allow attributes by default
+ }
+ );
+
+=head2 scrub_file
+
+ $html = $scrubber->scrub_file('foo.html'); ## returns giant string
+ die "Eeek $!" unless defined $html; ## opening foo.html may have failed
+ $scrubber->scrub_file('foo.html', 'new.html') or die "Eeek $!";
+ $scrubber->scrub_file('foo.html', *STDOUT)
+ or die "Eeek $!"
+ if fileno STDOUT;
+
+=head2 scrub
+
+ print $scrubber->scrub($html); ## returns giant string
+ $scrubber->scrub($html, 'new.html') or die "Eeek $!";
+ $scrubber->scrub($html', *STDOUT)
+ or die "Eeek $!"
+ if fileno STDOUT;
+
+=for comment _out
+ $scrubber->_out(*STDOUT) if fileno STDOUT;
+ $scrubber->_out('foo.html') or die "eeek $!";
+
+=for comment _validate
+Uses $self->{_rules} to do attribute validation.
+Takes tag, rule('_' || $tag), attrref.
+
+=for comment _scrub_str
+
+I<default> handler, used by both _scrub and _scrub_fh Moved all the common code
+(basically all of it) into a single routine for ease of maintenance
+
+=for comment _scrub_fh
+
+I<default> handler, does the scrubbing if we're scrubbing out to a file. Now
+calls _scrub_str and pushes that out to a file.
+
+=for comment _scrub
+
+I<default> handler, does the scrubbing if we're returning a giant string. Now
+calls _scrub_str and appends that to the output string.
+
+=head1 How does it work?
+
+When a tag is encountered, HTML::Scrubber allows/denies the tag using the
+explicit rule if one exists.
+
+If no explicit rule exists, Scrubber applies the default rule.
+
+If an explicit rule exists, but it's a simple rule(1), the default attribute
+rule is applied.
+
+=head2 EXAMPLE
+
+=for example begin
+
+ #!/usr/bin/perl -w
+ use HTML::Scrubber;
+ use strict;
+
+ my @allow = qw[ br hr b a ];
+
+ my @rules = (
+ script => 0,
+ img => {
+ src => qr{^(?!http://)}i, # only relative image links allowed
+ alt => 1, # alt attribute allowed
+ '*' => 0, # deny all other attributes
+ },
+ );
+
+ my @default = (
+ 0 => # default rule, deny all tags
+ {
+ '*' => 1, # default rule, allow all attributes
+ 'href' => qr{^(?:http|https|ftp)://}i,
+ 'src' => qr{^(?:http|https|ftp)://}i,
+
+ # If your perl doesn't have qr
+ # just use a string with length greater than 1
+ 'cite' => '(?i-xsm:^(?:http|https|ftp):)',
+ 'language' => 0,
+ 'name' => 1, # could be sneaky, but hey ;)
+ 'onblur' => 0,
+ 'onchange' => 0,
+ 'onclick' => 0,
+ 'ondblclick' => 0,
+ 'onerror' => 0,
+ 'onfocus' => 0,
+ 'onkeydown' => 0,
+ 'onkeypress' => 0,
+ 'onkeyup' => 0,
+ 'onload' => 0,
+ 'onmousedown' => 0,
+ 'onmousemove' => 0,
+ 'onmouseout' => 0,
+ 'onmouseover' => 0,
+ 'onmouseup' => 0,
+ 'onreset' => 0,
+ 'onselect' => 0,
+ 'onsubmit' => 0,
+ 'onunload' => 0,
+ 'src' => 0,
+ 'type' => 0,
+ }
+ );
+
+ my $scrubber = HTML::Scrubber->new();
+ $scrubber->allow(@allow);
+ $scrubber->rules(@rules); # key/value pairs
+ $scrubber->default(@default);
+ $scrubber->comment(1); # 1 allow, 0 deny
+
+ ## preferred way to create the same object
+ $scrubber = HTML::Scrubber->new(
+ allow => \@allow,
+ rules => \@rules,
+ default => \@default,
+ comment => 1,
+ process => 0,
+ );
+
+ require Data::Dumper, die Data::Dumper::Dumper($scrubber) if @ARGV;
+
+ my $it = q[
+ <?php echo(" EVIL EVIL EVIL "); ?> <!-- asdf -->
+ <hr>
+ <I FAKE="attribute" > IN ITALICS WITH FAKE="attribute" </I><br>
+ <B> IN BOLD </B><br>
+ <A NAME="evil">
+ <A HREF="javascript:alert('die die die');">HREF=JAVA &lt;!&gt;</A>
+ <br>
+ <A HREF="image/bigone.jpg" ONMOUSEOVER="alert('die die die');">
+ <IMG SRC="image/smallone.jpg" ALT="ONMOUSEOVER JAVASCRIPT">
+ </A>
+ </A> <br>
+ ];
+
+ print "#original text", $/, $it, $/;
+ print
+ "#scrubbed text (default ", $scrubber->default(), # no arguments returns the current value
+ " comment ", $scrubber->comment(), " process ", $scrubber->process(), " )", $/, $scrubber->scrub($it), $/;
+
+ $scrubber->default(1); # allow all tags by default
+ $scrubber->comment(0); # deny comments
+
+ print
+ "#scrubbed text (default ",
+ $scrubber->default(),
+ " comment ",
+ $scrubber->comment(),
+ " process ",
+ $scrubber->process(),
+ " )", $/,
+ $scrubber->scrub($it),
+ $/;
+
+ $scrubber->process(1); # allow process instructions (dangerous)
+ $default[0] = 1; # allow all tags by default
+ $default[1]->{'*'} = 0; # deny all attributes by default
+ $scrubber->default(@default); # set the default again
+
+ print
+ "#scrubbed text (default ",
+ $scrubber->default(),
+ " comment ",
+ $scrubber->comment(),
+ " process ",
+ $scrubber->process(),
+ " )", $/,
+ $scrubber->scrub($it),
+ $/;
+
+=for example end
+
+=head2 FUN
+
+If you have Test::Inline (and you've installed HTML::Scrubber), try
+
+ pod2test Scrubber.pm >scrubber.t
+ perl scrubber.t
+
+=head1 SEE ALSO
+
+L<HTML::Parser>, L<Test::Inline>.
+
+The C<HTML::Sanitizer> module is no longer available on CPAN.
+
+=head1 VERSION REQUIREMENTS
+
+As of version 0.14 I have added a perl minimum version requirement of 5.8. This
+is basically due to failures on the smokers perl 5.6 installations - which
+appears to be down to installation mechanisms and requirements.
+
+Since I don't want to spend the time supporting a version that is so old (and
+may not work for reasons on UTF support etc), I have added a C<use 5.008;> to
+the main module.
+
+If this is problematic I am very willing to accept patches to fix this up,
+although I do not personally see a good reason to support a release that has
+been obsolete for 13 years.
+
+=head1 CONTRIBUTING
+
+If you want to contribute to the development of this module, the code is on
+L<GitHub|http://github.com/nigelm/html-scrubber>. You'll need a perl
+environment with L<Dist::Zilla>, and if you're just getting started, there's
+some documentation on using Vagrant and Perlbrew
+L<here|http://mrcaron.github.io/2015/03/06/Perl-CPAN-Pull-Request.html>.
+
+There is now a C<.perltidyrc> and a <.tidyallrc> file within the respository
+for the standard perltidy settings used - I will apply these before new
+releases. Please do not let formatting prevent you from sending in patches etc
+- this can be sorted out as part of the release process. Info on C<tidyall>
+can be found at
+L<https://metacpan.org/pod/distribution/Code-TidyAll/bin/tidyall>.
+
+=head1 INSTALLATION
+
+See perlmodinstall for information and options on installing Perl modules.
+
+=head1 BUGS AND LIMITATIONS
+
+You can make new bug reports, and view existing ones, through the
+web interface at L<http://rt.cpan.org/Public/Dist/Display.html?Name=HTML-Scrubber>.
+
+=head1 AVAILABILITY
+
+The project homepage is L<https://metacpan.org/release/HTML-Scrubber>.
+
+The latest version of this module is available from the Comprehensive Perl
+Archive Network (CPAN). Visit L<http://www.perl.com/CPAN/> to find a CPAN
+site near you, or see L<https://metacpan.org/module/HTML::Scrubber/>.
+
+=head1 AUTHORS
+
+=over 4
+
+=item *
+
+Ruslan Zakirov <Ruslan.Zakirov@gmail.com>
+
+=item *
+
+Nigel Metheringham <nigelm@cpan.org>
+
+=item *
+
+D. H. <podmaster@cpan.org>
+
+=back
+
+=head1 COPYRIGHT AND LICENSE
+
+This software is copyright (c) 2015 by Ruslan Zakirov, Nigel Metheringham, 2003-2004 D. H..
+
+This is free software; you can redistribute it and/or modify it under
+the same terms as the Perl 5 programming language system itself.
+
+=cut
diff --git a/wiki/templates/copyright.pm b/wiki/templates/copyright.pm
new file mode 100644
index 0000000..16acacc
--- /dev/null
+++ b/wiki/templates/copyright.pm
@@ -0,0 +1,60 @@
+# A plugin for ikiwiki to implement adding a footer with copyright information
+# based on a default value taken out of a file.
+
+# Copyright © 2007, 2008 Thomas Schwinge <tschwinge@gnu.org>
+#
+# 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 2, 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+# Unless overridden with the `meta' plugin, a footer with copyright information
+# will be added to every page using a source file `copyright' (e.g.,
+# `copyright.mdwn') (using the same ``locating rules'' as for the sidebar
+# plugin).
+#
+# The state which page's copyright text was gathered from which source is not
+# tracked, so you'll need a full wiki-rebuild if the `copyright' file is
+# changed.
+
+package IkiWiki::Plugin::copyright;
+
+use warnings;
+use strict;
+use IkiWiki 2.00;
+
+my %copyright;
+
+sub import
+{
+ hook (type => "scan", id => "copyright", call => \&scan);
+}
+
+sub scan (@)
+{
+ my %params = @_;
+ my $page = $params{page};
+
+ return if defined $pagestate{$page}{meta}{copyright};
+
+ my $content;
+ my $copyright_page = bestlink ($page, "copyright") || return;
+ my $copyright_file = $pagesources{$copyright_page} || return;
+
+ # Only an optimization to avoid reading the same file again and again.
+ $copyright{$copyright_file} = readfile (srcfile ($copyright_file))
+ unless defined $copyright{$copyright_file};
+
+ $pagestate{$page}{meta}{copyright} = $copyright{$copyright_file};
+}
+
+1
diff --git a/wiki/templates/emacsconf.setup b/wiki/templates/emacsconf.setup
new file mode 100644
index 0000000..324f5b7
--- /dev/null
+++ b/wiki/templates/emacsconf.setup
@@ -0,0 +1,443 @@
+# IkiWiki::Setup::Yaml - YAML formatted setup file
+#
+# Setup file for ikiwiki.
+#
+# Passing this to ikiwiki --setup will make ikiwiki generate
+# wrappers and build the wiki.
+#
+# Remember to re-run ikiwiki --setup any time you edit this file.
+#
+# name of the wiki
+wikiname: EmacsConf
+# contact email for wiki
+adminemail: bandali@gnu.org
+# users who are wiki admins
+adminuser:
+- bandali
+# users who are banned from the wiki
+banned_users: []
+# where the source of the wiki is located
+srcdir: /home/ikiwiki/emacsconf
+# where to build the wiki
+#destdir: /home/ikiwiki/public_html/emacsconf
+destdir: /var/www/emacsconf.org
+# base url to the wiki
+url: https://emacsconf.org
+# url to the ikiwiki.cgi
+#cgiurl: https://emacsconf.org/ikiwiki.cgi
+# do not adjust cgiurl if CGI is accessed via different URL
+reverse_proxy: 0
+# filename of cgi wrapper to generate
+#cgi_wrapper: /home/ikiwiki/public_html/emacsconf/ikiwiki.cgi
+cgi_wrapper: /var/www/emacsconf.org/ikiwiki.cgi
+# mode for cgi_wrapper (can safely be made suid)
+cgi_wrappermode: 06755
+# number of seconds to delay CGI requests when overloaded
+cgi_overload_delay: ''
+# message to display when overloaded (may contain html)
+cgi_overload_message: ''
+# enable optimization of only refreshing committed changes?
+only_committed_changes: 0
+# rcs backend to use
+rcs: git
+# plugins to add to the default configuration
+add_plugins:
+- goodstuff
+- theme
+#- websetup
+- license
+#- format
+- sidebar
+- attachment
+- remove
+# plugins to disable
+disable_plugins: []
+#disable_plugins:
+#- editpage
+# additional directory to search for template files
+#templatedir: /usr/share/ikiwiki/templates
+#templatedir: /home/ikiwiki/.ikiwiki/templates
+# base wiki source location
+underlaydir: /usr/share/ikiwiki/basewiki
+# display verbose messages?
+#verbose: 1
+# log to syslog?
+syslog: 1
+# create output files named page/index.html?
+usedirs: 1
+# use '!'-prefixed preprocessor directives?
+prefix_directives: 1
+# use page/index.mdwn source files
+indexpages: 0
+# enable Discussion pages?
+discussion: 1
+# name of Discussion pages
+discussionpage: discussion
+# use elements new in HTML5 like <section>?
+html5: 0
+# only send cookies over SSL connections?
+sslcookie: 0
+# extension to use for new pages
+default_pageext: md
+# extension to use for html files
+htmlext: html
+# strftime format string to display date
+#timeformat: '%c'
+#timeformat: '%a %d %b %Y %r %Z'
+timeformat: '%A %e %B %Y at %k:%M (%Z)'
+# UTF-8 locale to use
+#locale: en_US.UTF-8
+#locale: en_CA.UTF-8
+# put user pages below specified page
+userdir: ''
+# how many backlinks to show before hiding excess (0 to show all)
+numbacklinks: 10
+# attempt to hardlink source files? (optimisation for large files)
+hardlink: 0
+# force ikiwiki to use a particular umask (keywords public, group or private, or a number)
+#umask: public
+# group for wrappers to run in
+#wrappergroup: ikiwiki
+# extra library and plugin directories
+libdirs: []
+# extra library and plugin directory (searched after libdirs)
+libdir: /home/ikiwiki/.ikiwiki
+# environment variables
+ENV: {}
+# time zone name
+timezone: :/etc/localtime
+# regexp of normally excluded files to include
+#include: ^\.htaccess$
+# regexp of files that should be skipped
+#exclude: ^(*\.private|Makefile)$
+# specifies the characters that are allowed in source filenames
+wiki_file_chars: -[:alnum:]+/.:_
+# allow symlinks in the path leading to the srcdir (potentially insecure)
+allow_symlinks_before_srcdir: 0
+# cookie control
+cookiejar:
+ file: /home/ikiwiki/.ikiwiki/cookies
+# set custom user agent string for outbound HTTP requests e.g. when fetching aggregated RSS feeds
+useragent: ikiwiki/3.20190228-1
+# theme has a responsive layout? (mobile-optimized)
+responsive_layout: 1
+# try harder to produce deterministic output
+deterministic: 0
+
+######################################################################
+# core plugins
+# (editpage, git, htmlscrubber, inline, link, meta, parentlinks,
+# templatebody)
+######################################################################
+
+# git plugin
+# git hook to generate
+#git_wrapper: /home/ikiwiki/emacsconf.git/hooks/post-update
+git_wrapper: /home/ikiwiki/hooks/emacsconf
+# shell command for git_wrapper to run, in the background
+#git_wrapper_background_command: git push github
+# mode for git_wrapper (can safely be made suid)
+#git_wrappermode: 06755
+# git pre-receive hook to generate
+#git_test_receive_wrapper: /git/wiki.git/hooks/pre-receive
+git_test_receive_wrapper: /home/ikiwiki/hooks/emacsconf-pre
+# unix users whose commits should be checked by the pre-receive hook
+#untrusted_committers: []
+untrusted_committers:
+- anon
+# gitweb url to show file history ([[file]] substituted)
+#historyurl: http://git.example.com/gitweb.cgi?p=wiki.git;a=history;f=[[file]];hb=HEAD
+historyurl: //git.emacsconf.org/emacsconf-wiki/log/[[file]]
+# gitweb url to show a diff ([[file]], [[sha1_to]], [[sha1_from]], [[sha1_commit]], and [[sha1_parent]] substituted)
+#diffurl: http://git.example.com/gitweb.cgi?p=wiki.git;a=blobdiff;f=[[file]];h=[[sha1_to]];hp=[[sha1_from]];hb=[[sha1_commit]];hpb=[[sha1_parent]]
+diffurl: //git.emacsconf.org/emacsconf-wiki/diff/[[file]]?id=[[sha1_commit]]&id2=[[sha1_parent]]
+# where to pull and push changes (set to empty string to disable)
+#gitorigin_branch: origin
+# branch that the wiki is stored in
+#gitmaster_branch: master
+
+# htmlscrubber plugin
+# PageSpec specifying pages not to scrub
+#htmlscrubber_skip: '!*/Discussion'
+
+# inline plugin
+# enable rss feeds by default?
+rss: 1
+# enable atom feeds by default?
+atom: 1
+# allow rss feeds to be used?
+#allowrss: 0
+# allow atom feeds to be used?
+#allowatom: 0
+# urls to ping (using XML-RPC) on feed update
+#pingurl: http://rpc.technorati.com/rpc/ping
+
+######################################################################
+# auth plugins
+# (anonok, blogspam, emailauth, httpauth, lockedit, moderatedcomments,
+# opendiscussion, openid, passwordauth, signinedit)
+######################################################################
+
+# anonok plugin
+# PageSpec to limit which pages anonymous users can edit
+#anonok_pagespec: '*/discussion'
+
+# blogspam plugin
+# PageSpec of pages to check for spam
+#blogspam_pagespec: postcomment(*)
+# options to send to blogspam server
+#blogspam_options: blacklist=1.2.3.4,blacklist=8.7.6.5,max-links=10
+# blogspam server JSON url
+#blogspam_server: ''
+
+# emailauth plugin
+# email address to send emailauth mails as (default: adminemail)
+#emailauth_sender: ''
+
+# httpauth plugin
+# url to redirect to when authentication is needed
+#cgiauthurl: http://example.com/wiki/auth/ikiwiki.cgi
+# PageSpec of pages where only httpauth will be used for authentication
+#httpauth_pagespec: '!*/Discussion'
+
+# lockedit plugin
+# PageSpec controlling which pages are locked
+#locked_pages: '!*/Discussion'
+#locked_pages: 'index or edit'
+locked_pages: 'edit'
+
+# moderatedcomments plugin
+# PageSpec matching users or comment locations to moderate
+#moderate_pagespec: '*'
+
+# openid plugin
+# url pattern of openid realm (default is cgiurl)
+#openid_realm: ''
+# url to ikiwiki cgi to use for openid authentication (default is cgiurl)
+#openid_cgiurl: ''
+
+# passwordauth plugin
+# a password that must be entered when signing up for an account
+#account_creation_password: s3cr1t
+# cost of generating a password using Authen::Passphrase::BlowfishCrypt
+#password_cost: 8
+
+######################################################################
+# format plugins
+# (creole, highlight, hnb, html, mdwn, otl, rawhtml, rst, textile, txt)
+######################################################################
+
+# highlight plugin
+# types of source files to syntax highlight
+#tohighlight: .c .h .cpp .pl .py Makefile:make
+# location of highlight's filetypes.conf
+#filetypes_conf: /etc/highlight/filetypes.conf
+# location of highlight's langDefs directory
+#langdefdir: /usr/share/highlight/langDefs
+
+# mdwn plugin
+# enable multimarkdown features?
+#multimarkdown: 0
+# disable use of markdown discount?
+#nodiscount: 0
+# enable footnotes in Markdown (where supported)?
+#mdwn_footnotes: 1
+# interpret line like 'A. First item' as ordered list when using Discount?
+#mdwn_alpha_lists: 0
+
+######################################################################
+# special-purpose plugins
+# (osm, underlay)
+######################################################################
+
+# osm plugin
+# the default zoom when you click on the map link
+#osm_default_zoom: 15
+# the icon shown on links and on the main map
+#osm_default_icon: ikiwiki/images/osm.png
+# the alt tag of links, defaults to empty
+#osm_alt: ''
+# the output format for waypoints, can be KML, GeoJSON or CSV (one or many, comma-separated)
+#osm_format: KML
+# the icon attached to a tag, displayed on the map for tagged pages
+#osm_tag_default_icon: icon.png
+# Url for the OpenLayers.js file
+#osm_openlayers_url: http://www.openlayers.org/api/OpenLayers.js
+# Layers to use in the map. Can be either the 'OSM' string or a type option for Google maps (GoogleNormal, GoogleSatellite, GoogleHybrid or GooglePhysical). It can also be an arbitrary URL in a syntax acceptable for OpenLayers.Layer.OSM.url parameter.
+#osm_layers:
+# OSM: GoogleSatellite
+# Google maps API key, Google layer not used if missing, see https://code.google.com/apis/console/ to get an API key
+#osm_google_apikey: ''
+
+# underlay plugin
+# extra underlay directories to add
+#add_underlays:
+#- /home/ikiwiki/wiki.underlay
+
+######################################################################
+# web plugins
+# (404, attachment, comments, editdiff, edittemplate, getsource, google,
+# goto, mirrorlist, remove, rename, repolist, search, theme, userlist,
+# websetup, wmd)
+######################################################################
+
+# attachment plugin
+# enhanced PageSpec specifying what attachments are allowed
+#allowed_attachments: virusfree() and mimetype(image/*) and maxsize(50kb)
+allowed_attachments: (mimetype(text/*) or *.m3u or *.svg) and maxsize(300kb)
+# virus checker program (reads STDIN, returns nonzero if virus found)
+#virus_checker: clamdscan -
+
+# comments plugin
+# PageSpec of pages where comments are allowed
+#comments_pagespec: blog/* and !*/Discussion
+# PageSpec of pages where posting new comments is not allowed
+#comments_closed_pagespec: blog/controversial or blog/flamewar
+# Base name for comments, e.g. "comment_" for pages like "sandbox/comment_12"
+#comments_pagename: ''
+# Interpret directives in comments?
+#comments_allowdirectives: 0
+# Allow anonymous commenters to set an author name?
+#comments_allowauthor: 0
+# commit comments to the VCS
+#comments_commit: 1
+# Restrict formats for comments to (no restriction if empty)
+#comments_allowformats: mdwn txt
+
+# getsource plugin
+# Mime type for returned source.
+#getsource_mimetype: text/plain; charset=utf-8
+
+# mirrorlist plugin
+# list of mirrors
+#mirrorlist: {}
+# generate links that point to the mirrors' ikiwiki CGI
+#mirrorlist_use_cgi: 1
+
+# repolist plugin
+# URIs of repositories containing the wiki's source
+#repositories:
+#- svn://svn.example.org/wiki/trunk
+repositories:
+- https://git.emacsconf.org/emacsconf-wiki
+- git://git.emacsconf.org/emacsconf-wiki
+- ssh://anon@git.emacsconf.org:emacsconf-wiki
+
+# search plugin
+# path to the omega cgi program
+#omega_cgi: /usr/lib/cgi-bin/omega/omega
+# use google site search rather than internal xapian index?
+#google_search: 1
+
+# theme plugin
+# name of theme to enable
+#theme: actiontabs
+theme: actiontabs
+
+# websetup plugin
+# list of plugins that cannot be enabled/disabled via the web interface
+#websetup_force_plugins: []
+# list of additional setup field keys to treat as unsafe
+#websetup_unsafe: []
+# show unsafe settings, read-only, in web interface?
+#websetup_show_unsafe: 1
+
+######################################################################
+# widget plugins
+# (calendar, color, conditional, cutpaste, date, format, fortune,
+# graphviz, haiku, headinganchors, img, linkmap, listdirectives, map,
+# more, orphans, pagecount, pagestats, poll, polygen, postsparkline,
+# progress, shortcut, sparkline, table, template, teximg, toc, toggle,
+# version)
+######################################################################
+
+# calendar plugin
+# base of the archives hierarchy
+#archivebase: archives
+# PageSpec of pages to include in the archives, if option `calendar_autocreate` is true.
+#archive_pagespec: page(posts/*) and !*/Discussion
+# autocreate new calendar pages?
+#calendar_autocreate: 1
+# if set, when building calendar pages, also build pages of year and month when no pages were published (building empty calendars).
+#calendar_fill_gaps: 1
+
+# img plugin
+# Image formats to process (jpeg, png, gif, svg, pdf or 'everything' to accept all)
+#img_allowed_formats: ''
+
+# listdirectives plugin
+# directory in srcdir that contains directive descriptions
+#directive_description_dir: ikiwiki/directive
+
+# teximg plugin
+# Should teximg use dvipng to render, or dvips and convert?
+#teximg_dvipng: ''
+# LaTeX prefix for teximg plugin
+#teximg_prefix: |
+# \documentclass{article}
+# \usepackage[utf8]{inputenc}
+# \usepackage{amsmath}
+# \usepackage{amsfonts}
+# \usepackage{amssymb}
+# \pagestyle{empty}
+# \begin{document}
+# LaTeX postfix for teximg plugin
+#teximg_postfix: \end{document}
+
+######################################################################
+# other plugins
+# (aggregate, autoindex, brokenlinks, camelcase, ddate, embed, favicon,
+# filecheck, flattr, goodstuff, htmlbalance, localstyle, loginselector,
+# notifyemail, pagetemplate, pingee, pinger, prettydate, recentchanges,
+# recentchangesdiff, relativedate, rsync, sidebar, smiley,
+# sortnaturally, tag, testpagespec, trail, transient)
+######################################################################
+
+# aggregate plugin
+# enable aggregation to internal pages?
+#aggregateinternal: 1
+# allow aggregation to be triggered via the web?
+#aggregate_webtrigger: 0
+
+# autoindex plugin
+# commit autocreated index pages
+#autoindex_commit: 1
+
+# camelcase plugin
+# list of words to not turn into links
+#camelcase_ignore: []
+
+# flattr plugin
+# userid or user name to use by default for Flattr buttons
+#flattr_userid: joeyh
+
+# pinger plugin
+# how many seconds to try pinging before timing out
+#pinger_timeout: 15
+
+# prettydate plugin
+# format to use to display date
+#prettydateformat: '%X, %B %o, %Y'
+
+# recentchanges plugin
+# name of the recentchanges page
+#recentchangespage: recentchanges
+recentchangespage: recent-changes
+# number of changes to track
+#recentchangesnum: 100
+
+# rsync plugin
+# command to run to sync updated pages
+#rsync_command: rsync -qa --delete . user@host:/path/to/docroot/
+
+# sidebar plugin
+# show sidebar page on all pages?
+#global_sidebars: 1
+
+# tag plugin
+# parent page tags are located under
+#tagbase: tag
+# autocreate new tag pages?
+tag_autocreate: 1
+# commit autocreated tag pages
+#tag_autocreate_commit: 1
diff --git a/wiki/templates/htmlscrubber.pm b/wiki/templates/htmlscrubber.pm
new file mode 100755
index 0000000..875b20e
--- /dev/null
+++ b/wiki/templates/htmlscrubber.pm
@@ -0,0 +1,132 @@
+#!/usr/bin/perl
+package IkiWiki::Plugin::htmlscrubber;
+
+use warnings;
+use strict;
+use IkiWiki 3.00;
+
+# This regexp matches urls that are in a known safe scheme.
+# Feel free to use it from other plugins.
+our $safe_url_regexp;
+
+sub import {
+ hook(type => "getsetup", id => "htmlscrubber", call => \&getsetup);
+ hook(type => "sanitize", id => "htmlscrubber", call => \&sanitize);
+
+ # Only known uri schemes are allowed to avoid all the ways of
+ # embedding javascrpt.
+ # List at http://en.wikipedia.org/wiki/URI_scheme
+ my $uri_schemes=join("|", map quotemeta,
+ # IANA registered schemes
+ "http", "https", "ftp", "mailto", "file", "telnet", "gopher",
+ "aaa", "aaas", "acap", "cap", "cid", "crid",
+ "dav", "dict", "dns", "fax", "go", "h323", "im", "imap",
+ "ldap", "mid", "news", "nfs", "nntp", "pop", "pres",
+ "sip", "sips", "snmp", "tel", "urn", "wais", "xmpp",
+ "z39.50r", "z39.50s",
+ # Selected unofficial schemes
+ "aim", "callto", "cvs", "ed2k", "feed", "fish", "gg",
+ "irc", "ircs", "lastfm", "ldaps", "magnet", "mms",
+ "msnim", "notes", "rsync", "secondlife", "skype", "ssh",
+ "sftp", "smb", "sms", "snews", "webcal", "ymsgr",
+ "bitcoin", "git", "svn", "bzr", "darcs", "hg"
+ );
+ # data is a special case. Allow a few data:image/ types,
+ # but disallow data:text/javascript and everything else.
+ $safe_url_regexp=qr/^(?:(?:$uri_schemes):|data:image\/(?:png|jpeg|gif)|[^:]+(?:$|[\/\?#]))|^#/i;
+}
+
+sub getsetup () {
+ return
+ plugin => {
+ safe => 1,
+ rebuild => undef,
+ section => "core",
+ },
+ htmlscrubber_skip => {
+ type => "pagespec",
+ example => "!*/Discussion",
+ description => "PageSpec specifying pages not to scrub",
+ link => "ikiwiki/PageSpec",
+ safe => 1,
+ rebuild => undef,
+ },
+}
+
+sub sanitize (@) {
+ my %params=@_;
+
+ if (exists $config{htmlscrubber_skip} &&
+ length $config{htmlscrubber_skip} &&
+ exists $params{page} &&
+ pagespec_match($params{page}, $config{htmlscrubber_skip})) {
+ return $params{content};
+ }
+
+ return scrubber()->scrub($params{content});
+}
+
+my $_scrubber;
+sub scrubber {
+ return $_scrubber if defined $_scrubber;
+
+ eval q{use HTML::Scrubber};
+ error($@) if $@;
+ # Lists based on http://feedparser.org/docs/html-sanitization.html
+ # With html5 tags added.
+ $_scrubber = HTML::Scrubber->new(
+ allow => [qw{
+ svg rect text g title
+ a abbr acronym address area b big blockquote br br/
+ button caption center cite code col colgroup dd del
+ dfn dir div dl dt em fieldset font form h1 h2 h3 h4
+ h5 h6 hr hr/ i img input ins kbd label legend li map
+ track
+ menu ol optgroup option p p/ pre q s samp select small
+ span strike strong sub sup table tbody td textarea
+ tfoot th thead tr tt u ul var
+ video audio source section nav article aside hgroup
+ header footer figure figcaption time mark canvas
+ datalist progress meter ruby rt rp details summary
+ }],
+ default => [undef, { (
+ map { $_ => 1 } qw{
+ version xmlns x y fill font-size stroke-dasharray transform
+ data-start data-end data-video data-target data-tracks
+ kind label srclang default
+ abbr accept accept-charset accesskey
+ align alt axis border cellpadding cellspacing
+ char charoff charset checked class
+ clear cols colspan color compact coords
+ datetime dir disabled enctype for frame
+ headers height hreflang hspace id ismap
+ kind srclang src default
+ label lang maxlength media method
+ multiple name nohref noshade nowrap prompt
+ readonly rel rev rows rowspan rules scope
+ selected shape size span start summary
+ tabindex target title type valign
+ value vspace width
+ autofocus autoplay preload loopstart
+ loopend end playcount controls pubdate
+ loop muted
+ placeholder min max step low high optimum
+ form required autocomplete novalidate pattern
+ list formenctype formmethod formnovalidate
+ formtarget reversed spellcheck open hidden
+ } ),
+ "/" => 1, # emit proper <hr /> XHTML
+ href => $safe_url_regexp,
+ src => $safe_url_regexp,
+ action => $safe_url_regexp,
+ formaction => $safe_url_regexp,
+ cite => $safe_url_regexp,
+ longdesc => $safe_url_regexp,
+ poster => $safe_url_regexp,
+ usemap => $safe_url_regexp
+ }],
+ );
+ return $_scrubber;
+}
+
+1
diff --git a/wiki/templates/license.pm b/wiki/templates/license.pm
new file mode 100644
index 0000000..651c039
--- /dev/null
+++ b/wiki/templates/license.pm
@@ -0,0 +1,59 @@
+# A plugin for ikiwiki to implement adding a footer with licensing information
+# based on a default value taken out of a file.
+
+# Copyright © 2007, 2008 Thomas Schwinge <tschwinge@gnu.org>
+#
+# 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 2, 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, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+# Unless overridden with the `meta' plugin, a footer with licensing information
+# will be added to every page using a source file `license' (e.g.,
+# `license.mdwn') (using the same ``locating rules'' as for the sidebar
+# plugin).
+#
+# The state which page's license text was gathered from which source is not
+# tracked, so you'll need a full wiki-rebuild if the `license' file is changed.
+
+package IkiWiki::Plugin::license;
+
+use warnings;
+use strict;
+use IkiWiki 2.00;
+
+my %license;
+
+sub import
+{
+ hook (type => "scan", id => "license", call => \&scan);
+}
+
+sub scan (@)
+{
+ my %params = @_;
+ my $page = $params{page};
+
+ return if defined $pagestate{$page}{meta}{license};
+
+ my $content;
+ my $license_page = bestlink ($page, "license") || return;
+ my $license_file = $pagesources{$license_page} || return;
+
+ # Only an optimization to avoid reading the same file again and again.
+ $license{$license_file} = readfile (srcfile ($license_file))
+ unless defined $license{$license_file};
+
+ $pagestate{$page}{meta}{license} = $license{$license_file};
+}
+
+1