update packages
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
;;; ox-icalendar.el --- iCalendar Back-End for Org Export Engine -*- lexical-binding: t; -*-
|
||||
;;; ox-icalendar.el --- iCalendar Backend for Org Export Engine -*- lexical-binding: t; -*-
|
||||
|
||||
;; Copyright (C) 2004-2023 Free Software Foundation, Inc.
|
||||
;; Copyright (C) 2004-2025 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Carsten Dominik <carsten.dominik@gmail.com>
|
||||
;; Nicolas Goaziou <mail@nicolasgoaziou.fr>
|
||||
;; Maintainer: Nicolas Goaziou <mail@nicolasgoaziou.fr>
|
||||
;; Keywords: outlines, hypermedia, calendar, wp
|
||||
;; Maintainer: Jack Kamm <jackkamm@gmail.com>
|
||||
;; Keywords: outlines, hypermedia, calendar, text
|
||||
;; URL: https://orgmode.org
|
||||
|
||||
;; This file is part of GNU Emacs.
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
;;; Commentary:
|
||||
;;
|
||||
;; This library implements an iCalendar back-end for Org generic
|
||||
;; This library implements an iCalendar backend for Org generic
|
||||
;; exporter. See Org manual for more information.
|
||||
;;
|
||||
;; It is expected to conform to RFC 5545.
|
||||
@@ -48,7 +48,7 @@
|
||||
;;; User-Configurable Variables
|
||||
|
||||
(defgroup org-export-icalendar nil
|
||||
"Options specific for iCalendar export back-end."
|
||||
"Options specific for iCalendar export backend."
|
||||
:tag "Org Export iCalendar"
|
||||
:group 'org-export)
|
||||
|
||||
@@ -96,7 +96,7 @@ and will fire at the event start."
|
||||
(defcustom org-icalendar-exclude-tags nil
|
||||
"Tags that exclude a tree from export.
|
||||
This variable allows specifying different exclude tags from other
|
||||
back-ends. It can also be set with the ICALENDAR_EXCLUDE_TAGS
|
||||
backends. It can also be set with the ICALENDAR_EXCLUDE_TAGS
|
||||
keyword."
|
||||
:group 'org-export-icalendar
|
||||
:type '(repeat (string :tag "Tag")))
|
||||
@@ -168,8 +168,9 @@ This is a list with possibly several symbols in it. Valid symbols are:
|
||||
|
||||
`todo-start'
|
||||
|
||||
Scheduling time stamps in TODO entries become start date. Some
|
||||
calendar applications show TODO entries only after that date."
|
||||
Scheduling time stamps in TODO entries become start date. (See
|
||||
also `org-icalendar-todo-unscheduled-start', which controls the
|
||||
start date for TODO entries without a scheduling time stamp)"
|
||||
:group 'org-export-icalendar
|
||||
:type
|
||||
'(set :greedy t
|
||||
@@ -221,7 +222,8 @@ Valid values are:
|
||||
nil don't include any task.
|
||||
t include tasks that are not in DONE state.
|
||||
`unblocked' include all TODO items that are not blocked.
|
||||
`all' include both done and not done items."
|
||||
`all' include both done and not done items.
|
||||
\\(\"TODO\" ...) include specific TODO keywords."
|
||||
:group 'org-export-icalendar
|
||||
:type '(choice
|
||||
(const :tag "None" nil)
|
||||
@@ -231,6 +233,38 @@ t include tasks that are not in DONE state.
|
||||
(repeat :tag "Specific TODO keywords"
|
||||
(string :tag "Keyword"))))
|
||||
|
||||
(defcustom org-icalendar-todo-unscheduled-start 'recurring-deadline-warning
|
||||
"Exported start date of unscheduled TODOs.
|
||||
|
||||
If `org-icalendar-use-scheduled' contains `todo-start' and a task
|
||||
has a \"SCHEDULED\" timestamp, that is always used as the start
|
||||
date. Otherwise, this variable controls whether a start date is
|
||||
exported and what its value is.
|
||||
|
||||
Note that the iCalendar spec RFC 5545 does not generally require
|
||||
tasks to have a start date, except for repeating tasks which do
|
||||
require a start date. However some iCalendar programs ignore the
|
||||
requirement for repeating tasks, and allow repeating deadlines
|
||||
without a matching start date.
|
||||
|
||||
This variable has no effect when `org-icalendar-include-todo' is nil.
|
||||
|
||||
Valid values are:
|
||||
`recurring-deadline-warning' If deadline repeater present,
|
||||
use `org-deadline-warning-days' as start.
|
||||
`deadline-warning' If deadline present,
|
||||
use `org-deadline-warning-days' as start.
|
||||
`current-datetime' Use the current date-time as start.
|
||||
nil Never add a start time for unscheduled tasks."
|
||||
:group 'org-export-icalendar
|
||||
:type '(choice
|
||||
(const :tag "Warning days if deadline recurring" recurring-deadline-warning)
|
||||
(const :tag "Warning days if deadline present" deadline-warning)
|
||||
(const :tag "Now" current-datetime)
|
||||
(const :tag "No start date" nil))
|
||||
:package-version '(Org . "9.7")
|
||||
:safe #'symbolp)
|
||||
|
||||
(defcustom org-icalendar-include-bbdb-anniversaries nil
|
||||
"Non-nil means a combined iCalendar file should include anniversaries.
|
||||
The anniversaries are defined in the BBDB database."
|
||||
@@ -297,6 +331,32 @@ Interesting value are:
|
||||
(const :tag "Universal time" ":%Y%m%dT%H%M%SZ")
|
||||
(string :tag "Explicit format")))
|
||||
|
||||
(defcustom org-icalendar-ttl nil
|
||||
"Time to live for the exported calendar.
|
||||
|
||||
Subscribing clients to the exported ics file can derive the time
|
||||
interval to read the file again from the server. One example of such
|
||||
client is Nextcloud calendar, which respects the setting of
|
||||
X-PUBLISHED-TTL in ICS files. Setting `org-icalendar-ttl' to \"PT1H\"
|
||||
would advise a server to reload the file every hour.
|
||||
|
||||
See https://icalendar.org/iCalendar-RFC-5545/3-8-2-5-duration.html
|
||||
for a complete description of possible specifications of this
|
||||
option. For example, \"PT1H\" stands for 1 hour and
|
||||
\"PT0H27M34S\" stands for 0 hours, 27 minutes and 34 seconds.
|
||||
|
||||
The default value is nil, which means no such option is set in
|
||||
the ICS file. This option can also be set on a per-document basis
|
||||
with the ICAL-TTL export keyword."
|
||||
:group 'org-export-icalendar
|
||||
:type '(choice
|
||||
(const :tag "No refresh period" nil)
|
||||
(const :tag "One hour" "PT1H")
|
||||
(const :tag "One day" "PT1D")
|
||||
(const :tag "One week" "PT7D")
|
||||
(string :tag "Other"))
|
||||
:package-version '(Org . "9.7"))
|
||||
|
||||
(defvar org-icalendar-after-save-hook nil
|
||||
"Hook run after an iCalendar file has been saved.
|
||||
This hook is run with the name of the file as argument. A good
|
||||
@@ -305,7 +365,7 @@ re-read the iCalendar file.")
|
||||
|
||||
|
||||
|
||||
;;; Define Back-End
|
||||
;;; Define Backend
|
||||
|
||||
(org-export-define-derived-backend 'icalendar 'ascii
|
||||
:translate-alist '((clock . nil)
|
||||
@@ -334,7 +394,8 @@ re-read the iCalendar file.")
|
||||
(:icalendar-use-deadline nil nil org-icalendar-use-deadline)
|
||||
(:icalendar-use-scheduled nil nil org-icalendar-use-scheduled)
|
||||
(:icalendar-scheduled-summary-prefix nil nil org-icalendar-scheduled-summary-prefix)
|
||||
(:icalendar-deadline-summary-prefix nil nil org-icalendar-deadline-summary-prefix))
|
||||
(:icalendar-deadline-summary-prefix nil nil org-icalendar-deadline-summary-prefix)
|
||||
(:icalendar-ttl "ICAL-TTL" nil org-icalendar-ttl))
|
||||
:filters-alist
|
||||
'((:filter-headline . org-icalendar-clear-blank-lines))
|
||||
:menu-entry
|
||||
@@ -540,20 +601,31 @@ or subject for the event."
|
||||
;; line, real contents must be split at 74 chars.
|
||||
(while (< (setq chunk-end (+ chunk-start 74)) len)
|
||||
(setq folded-line
|
||||
(concat folded-line "\r\n "
|
||||
(concat folded-line "\n "
|
||||
(substring line chunk-start chunk-end))
|
||||
chunk-start chunk-end))
|
||||
(concat folded-line "\r\n " (substring line chunk-start))))))
|
||||
(org-split-string s "\n") "\r\n")))
|
||||
(concat folded-line "\n " (substring line chunk-start))))))
|
||||
(org-split-string s "\n") "\n")))
|
||||
|
||||
(defun org-icalendar--post-process-file (file)
|
||||
"Post-process the exported iCalendar FILE.
|
||||
Converts line endings to dos-style CRLF as per RFC 5545, then
|
||||
runs `org-icalendar-after-save-hook'."
|
||||
(with-temp-buffer
|
||||
(insert-file-contents file)
|
||||
(let ((coding-system-for-write (coding-system-change-eol-conversion
|
||||
last-coding-system-used 'dos)))
|
||||
(write-region nil nil file)))
|
||||
(run-hook-with-args 'org-icalendar-after-save-hook file)
|
||||
nil)
|
||||
|
||||
|
||||
;;; Filters
|
||||
|
||||
(defun org-icalendar-clear-blank-lines (headline _back-end _info)
|
||||
(defun org-icalendar-clear-blank-lines (headline _backend _info)
|
||||
"Remove blank lines in HEADLINE export.
|
||||
HEADLINE is a string representing a transcoded headline.
|
||||
BACK-END and INFO are ignored."
|
||||
BACKEND and INFO are ignored."
|
||||
(replace-regexp-in-string "^\\(?:[ \t]*\n\\)+" "" headline))
|
||||
|
||||
|
||||
@@ -591,20 +663,22 @@ inlinetask within the section."
|
||||
(if (eq type 'inlinetask)
|
||||
(cons 'org-data (cons nil (org-element-contents entry)))
|
||||
(let ((first (car (org-element-contents entry))))
|
||||
(and (eq (org-element-type first) 'section)
|
||||
(and (org-element-type-p first 'section)
|
||||
(cons 'org-data
|
||||
(cons nil (org-element-contents first))))))))
|
||||
(concat
|
||||
(let ((todo-type (org-element-property :todo-type entry))
|
||||
(uid (or (org-element-property :ID entry) (org-id-new)))
|
||||
(summary (org-icalendar-cleanup-string
|
||||
(or (org-element-property :SUMMARY entry)
|
||||
(org-export-data
|
||||
(org-element-property :title entry) info))))
|
||||
(loc (org-icalendar-cleanup-string
|
||||
(org-export-get-node-property
|
||||
:LOCATION entry
|
||||
(org-property-inherit-p "LOCATION"))))
|
||||
(or
|
||||
(let ((org-property-separators '(("SUMMARY" . "\n"))))
|
||||
(org-entry-get entry "SUMMARY" 'selective))
|
||||
(org-export-data
|
||||
(org-element-property :title entry) info))))
|
||||
(loc
|
||||
(let ((org-property-separators '(("LOCATION" . "\n"))))
|
||||
(org-icalendar-cleanup-string
|
||||
(org-entry-get entry "LOCATION" 'selective))))
|
||||
(class (org-icalendar-cleanup-string
|
||||
(org-export-get-node-property
|
||||
:CLASS entry
|
||||
@@ -613,7 +687,8 @@ inlinetask within the section."
|
||||
;; (headline) or contents (inlinetask).
|
||||
(desc
|
||||
(org-icalendar-cleanup-string
|
||||
(or (org-element-property :DESCRIPTION entry)
|
||||
(or (let ((org-property-separators '(("DESCRIPTION" . "\n"))))
|
||||
(org-entry-get entry "DESCRIPTION" 'selective))
|
||||
(let ((contents (org-export-data inside info)))
|
||||
(cond
|
||||
((not (org-string-nw-p contents)) nil)
|
||||
@@ -683,13 +758,16 @@ inlinetask within the section."
|
||||
;; so, call `org-icalendar--vtodo' to transcode it into
|
||||
;; a "VTODO" component.
|
||||
(when (and todo-type
|
||||
(cl-case (plist-get info :icalendar-include-todo)
|
||||
(all t)
|
||||
(unblocked
|
||||
(pcase (plist-get info :icalendar-include-todo)
|
||||
(`all t)
|
||||
(`unblocked
|
||||
(and (eq type 'headline)
|
||||
(not (org-icalendar-blocked-headline-p
|
||||
entry info))))
|
||||
((t) (eq todo-type 'todo))))
|
||||
entry info))))
|
||||
;; unfinished
|
||||
(`t (eq todo-type 'todo))
|
||||
((and (pred listp) kwd-list)
|
||||
(member (org-element-property :todo-keyword entry) kwd-list))))
|
||||
(org-icalendar--vtodo entry uid summary loc desc cat tz class))
|
||||
;; Diary-sexp: Collect every diary-sexp element within ENTRY
|
||||
;; and its title, and transcode them. If ENTRY is
|
||||
@@ -720,6 +798,15 @@ inlinetask within the section."
|
||||
;; Don't forget components from inner entries.
|
||||
contents))))
|
||||
|
||||
(defun org-icalendar--rrule (unit value)
|
||||
"Format RRULE icalendar entry for UNIT frequency and VALUE interval.
|
||||
UNIT is a symbol `hour', `day', `week', `month', or `year'."
|
||||
(format "RRULE:FREQ=%s;INTERVAL=%d"
|
||||
(cl-case unit
|
||||
(hour "HOURLY") (day "DAILY") (week "WEEKLY")
|
||||
(month "MONTHLY") (year "YEARLY"))
|
||||
value))
|
||||
|
||||
(defun org-icalendar--vevent
|
||||
(entry timestamp uid summary location description categories timezone class)
|
||||
"Create a VEVENT component.
|
||||
@@ -732,35 +819,50 @@ venue for the event. DESCRIPTION provides the complete
|
||||
description of the event. CATEGORIES defines the categories the
|
||||
event belongs to. TIMEZONE specifies a time zone for this event
|
||||
only. CLASS contains the visibility attribute. Three of them
|
||||
(\"PUBLIC\", \"CONFIDENTIAL\", and \"PRIVATE\") are predefined, others
|
||||
\\(\"PUBLIC\", \"CONFIDENTIAL\", and \"PRIVATE\") are predefined, others
|
||||
should be treated as \"PRIVATE\" if they are unknown to the iCalendar server.
|
||||
|
||||
Return VEVENT component as a string."
|
||||
(org-icalendar-fold-string
|
||||
(if (eq (org-element-property :type timestamp) 'diary)
|
||||
(org-icalendar-transcode-diary-sexp
|
||||
(org-element-property :raw-value timestamp) uid summary)
|
||||
(concat "BEGIN:VEVENT\n"
|
||||
(org-icalendar-dtstamp) "\n"
|
||||
"UID:" uid "\n"
|
||||
(org-icalendar-convert-timestamp timestamp "DTSTART" nil timezone) "\n"
|
||||
(org-icalendar-convert-timestamp timestamp "DTEND" t timezone) "\n"
|
||||
;; RRULE.
|
||||
(when (org-element-property :repeater-type timestamp)
|
||||
(format "RRULE:FREQ=%s;INTERVAL=%d\n"
|
||||
(cl-case (org-element-property :repeater-unit timestamp)
|
||||
(hour "HOURLY") (day "DAILY") (week "WEEKLY")
|
||||
(month "MONTHLY") (year "YEARLY"))
|
||||
(org-element-property :repeater-value timestamp)))
|
||||
"SUMMARY:" summary "\n"
|
||||
(and (org-string-nw-p location) (format "LOCATION:%s\n" location))
|
||||
(and (org-string-nw-p class) (format "CLASS:%s\n" class))
|
||||
(and (org-string-nw-p description)
|
||||
(format "DESCRIPTION:%s\n" description))
|
||||
"CATEGORIES:" categories "\n"
|
||||
;; VALARM.
|
||||
(org-icalendar--valarm entry timestamp summary)
|
||||
"END:VEVENT"))))
|
||||
(if (eq (org-element-property :type timestamp) 'diary)
|
||||
(org-icalendar-transcode-diary-sexp
|
||||
(org-element-property :raw-value timestamp) uid summary)
|
||||
(concat "BEGIN:VEVENT\n"
|
||||
(org-icalendar-dtstamp) "\n"
|
||||
"UID:" uid "\n"
|
||||
(org-icalendar-convert-timestamp timestamp "DTSTART" nil timezone) "\n"
|
||||
(org-icalendar-convert-timestamp timestamp "DTEND" t timezone) "\n"
|
||||
;; RRULE.
|
||||
(when (org-element-property :repeater-type timestamp)
|
||||
(concat (org-icalendar--rrule
|
||||
(org-element-property :repeater-unit timestamp)
|
||||
(org-element-property :repeater-value timestamp))
|
||||
"\n"))
|
||||
"SUMMARY:" summary "\n"
|
||||
(and (org-string-nw-p location) (format "LOCATION:%s\n" location))
|
||||
(and (org-string-nw-p class) (format "CLASS:%s\n" class))
|
||||
(and (org-string-nw-p description)
|
||||
(format "DESCRIPTION:%s\n" description))
|
||||
"CATEGORIES:" categories "\n"
|
||||
;; VALARM.
|
||||
(org-icalendar--valarm entry timestamp summary)
|
||||
"END:VEVENT\n")))
|
||||
|
||||
(defun org-icalendar--repeater-type (elem)
|
||||
"Return ELEM's repeater-type if supported, else warn and return nil."
|
||||
(let ((repeater-value (org-element-property :repeater-value elem))
|
||||
(repeater-type (org-element-property :repeater-type elem)))
|
||||
(cond
|
||||
((not (and repeater-type
|
||||
repeater-value
|
||||
(> repeater-value 0)))
|
||||
nil)
|
||||
;; TODO Add catch-up to supported repeaters (use EXDATE to implement)
|
||||
((not (memq repeater-type '(cumulate)))
|
||||
(org-display-warning
|
||||
(format "Repeater-type %s not currently supported by iCalendar export"
|
||||
(symbol-name repeater-type)))
|
||||
nil)
|
||||
(repeater-type))))
|
||||
|
||||
(defun org-icalendar--vtodo
|
||||
(entry uid summary location description categories timezone class)
|
||||
@@ -769,51 +871,125 @@ Return VEVENT component as a string."
|
||||
ENTRY is either a headline or an inlinetask element. UID is the
|
||||
unique identifier for the task. SUMMARY defines a short summary
|
||||
or subject for the task. LOCATION defines the intended venue for
|
||||
the task. DESCRIPTION provides the complete description of the
|
||||
task. CATEGORIES defines the categories the task belongs to.
|
||||
TIMEZONE specifies a time zone for this TODO only.
|
||||
the task. CLASS sets the task class (e.g. confidential). DESCRIPTION
|
||||
provides the complete description of the task. CATEGORIES defines the
|
||||
categories the task belongs to. TIMEZONE specifies a time zone for
|
||||
this TODO only.
|
||||
|
||||
Return VTODO component as a string."
|
||||
(let ((start (or (and (memq 'todo-start org-icalendar-use-scheduled)
|
||||
(org-element-property :scheduled entry))
|
||||
;; If we can't use a scheduled time for some
|
||||
;; reason, start task now.
|
||||
(let ((now (decode-time)))
|
||||
(list 'timestamp
|
||||
(list :type 'active
|
||||
:minute-start (nth 1 now)
|
||||
:hour-start (nth 2 now)
|
||||
:day-start (nth 3 now)
|
||||
:month-start (nth 4 now)
|
||||
:year-start (nth 5 now)))))))
|
||||
(org-icalendar-fold-string
|
||||
(concat "BEGIN:VTODO\n"
|
||||
"UID:TODO-" uid "\n"
|
||||
(org-icalendar-dtstamp) "\n"
|
||||
(org-icalendar-convert-timestamp start "DTSTART" nil timezone) "\n"
|
||||
(and (memq 'todo-due org-icalendar-use-deadline)
|
||||
(org-element-property :deadline entry)
|
||||
(concat (org-icalendar-convert-timestamp
|
||||
(org-element-property :deadline entry) "DUE" nil timezone)
|
||||
"\n"))
|
||||
"SUMMARY:" summary "\n"
|
||||
(and (org-string-nw-p location) (format "LOCATION:%s\n" location))
|
||||
(and (org-string-nw-p class) (format "CLASS:%s\n" class))
|
||||
(and (org-string-nw-p description)
|
||||
(format "DESCRIPTION:%s\n" description))
|
||||
"CATEGORIES:" categories "\n"
|
||||
"SEQUENCE:1\n"
|
||||
(format "PRIORITY:%d\n"
|
||||
(let ((pri (or (org-element-property :priority entry)
|
||||
org-priority-default)))
|
||||
(floor (- 9 (* 8. (/ (float (- org-priority-lowest pri))
|
||||
(- org-priority-lowest
|
||||
org-priority-highest)))))))
|
||||
(format "STATUS:%s\n"
|
||||
(if (eq (org-element-property :todo-type entry) 'todo)
|
||||
"NEEDS-ACTION"
|
||||
"COMPLETED"))
|
||||
"END:VTODO"))))
|
||||
(let* ((sc (and (memq 'todo-start org-icalendar-use-scheduled)
|
||||
(org-element-property :scheduled entry)))
|
||||
(dl (and (memq 'todo-due org-icalendar-use-deadline)
|
||||
(org-element-property :deadline entry)))
|
||||
(sc-repeat-p (org-icalendar--repeater-type sc))
|
||||
(dl-repeat-p (org-icalendar--repeater-type dl))
|
||||
(repeat-value (or (org-element-property :repeater-value sc)
|
||||
(org-element-property :repeater-value dl)))
|
||||
(repeat-unit (or (org-element-property :repeater-unit sc)
|
||||
(org-element-property :repeater-unit dl)))
|
||||
(repeat-until (and sc-repeat-p (not dl-repeat-p) dl))
|
||||
(start
|
||||
(cond
|
||||
(sc)
|
||||
((eq org-icalendar-todo-unscheduled-start 'current-datetime)
|
||||
(let ((now (decode-time)))
|
||||
(list 'timestamp
|
||||
(list :type 'active
|
||||
:minute-start (nth 1 now)
|
||||
:hour-start (nth 2 now)
|
||||
:day-start (nth 3 now)
|
||||
:month-start (nth 4 now)
|
||||
:year-start (nth 5 now)))))
|
||||
((or (and (eq org-icalendar-todo-unscheduled-start
|
||||
'deadline-warning)
|
||||
dl)
|
||||
(and (eq org-icalendar-todo-unscheduled-start
|
||||
'recurring-deadline-warning)
|
||||
dl-repeat-p))
|
||||
(let ((dl-raw (org-element-property :raw-value dl)))
|
||||
(with-temp-buffer
|
||||
(insert dl-raw)
|
||||
(goto-char (point-min))
|
||||
(org-timestamp-down-day (org-get-wdays dl-raw))
|
||||
(org-element-timestamp-parser)))))))
|
||||
(concat "BEGIN:VTODO\n"
|
||||
"UID:TODO-" uid "\n"
|
||||
(org-icalendar-dtstamp) "\n"
|
||||
(when start (concat (org-icalendar-convert-timestamp
|
||||
start "DTSTART" nil timezone)
|
||||
"\n"))
|
||||
(when (and dl (not repeat-until))
|
||||
(concat (org-icalendar-convert-timestamp
|
||||
dl "DUE" nil timezone)
|
||||
"\n"))
|
||||
;; RRULE
|
||||
(cond
|
||||
;; SCHEDULED, DEADLINE have different repeaters
|
||||
((and dl-repeat-p
|
||||
(not (and (eq repeat-value (org-element-property
|
||||
:repeater-value dl))
|
||||
(eq repeat-unit (org-element-property
|
||||
:repeater-unit dl)))))
|
||||
;; TODO Implement via RDATE with changing DURATION
|
||||
(org-display-warning "Not yet implemented: \
|
||||
different repeaters on SCHEDULED and DEADLINE. Skipping.")
|
||||
nil)
|
||||
;; DEADLINE has repeater but SCHEDULED doesn't
|
||||
((and dl-repeat-p (and sc (not sc-repeat-p)))
|
||||
;; TODO SCHEDULED should only apply to first instance;
|
||||
;; use RDATE with custom DURATION to implement that
|
||||
(org-display-warning "Not yet implemented: \
|
||||
repeater on DEADLINE but not SCHEDULED. Skipping.")
|
||||
nil)
|
||||
((or sc-repeat-p dl-repeat-p)
|
||||
(concat
|
||||
(org-icalendar--rrule repeat-unit repeat-value)
|
||||
;; add UNTIL part to RRULE
|
||||
(when repeat-until
|
||||
(let* ((start-time
|
||||
(org-element-property :minute-start start))
|
||||
;; RFC5545 requires UTC iff DTSTART is not local time
|
||||
(local-time-p
|
||||
(and (not timezone)
|
||||
(equal org-icalendar-date-time-format
|
||||
":%Y%m%dT%H%M%S")))
|
||||
(encoded
|
||||
(org-encode-time
|
||||
0
|
||||
(or (org-element-property :minute-start repeat-until)
|
||||
0)
|
||||
(or (org-element-property :hour-start repeat-until)
|
||||
0)
|
||||
(org-element-property :day-start repeat-until)
|
||||
(org-element-property :month-start repeat-until)
|
||||
(org-element-property :year-start repeat-until))))
|
||||
(concat ";UNTIL="
|
||||
(cond
|
||||
((not start-time)
|
||||
(format-time-string "%Y%m%d" encoded))
|
||||
(local-time-p
|
||||
(format-time-string "%Y%m%dT%H%M%S" encoded))
|
||||
((format-time-string "%Y%m%dT%H%M%SZ"
|
||||
encoded t))))))
|
||||
"\n")))
|
||||
"SUMMARY:" summary "\n"
|
||||
(and (org-string-nw-p location) (format "LOCATION:%s\n" location))
|
||||
(and (org-string-nw-p class) (format "CLASS:%s\n" class))
|
||||
(and (org-string-nw-p description)
|
||||
(format "DESCRIPTION:%s\n" description))
|
||||
"CATEGORIES:" categories "\n"
|
||||
"SEQUENCE:1\n"
|
||||
(format "PRIORITY:%d\n"
|
||||
(let ((pri (or (org-element-property :priority entry)
|
||||
org-priority-default)))
|
||||
(floor (- 9 (* 8. (/ (float (- org-priority-lowest pri))
|
||||
(- org-priority-lowest
|
||||
org-priority-highest)))))))
|
||||
(format "STATUS:%s\n"
|
||||
(if (eq (org-element-property :todo-type entry) 'todo)
|
||||
"NEEDS-ACTION"
|
||||
"COMPLETED"))
|
||||
"END:VTODO\n")))
|
||||
|
||||
(defun org-icalendar--valarm (entry timestamp summary)
|
||||
"Create a VALARM component.
|
||||
@@ -872,26 +1048,32 @@ as a communication channel."
|
||||
(or (org-string-nw-p org-icalendar-timezone) (format-time-string "%Z"))
|
||||
;; Description.
|
||||
(org-export-data (plist-get info :title) info)
|
||||
;; TTL
|
||||
(plist-get info :icalendar-ttl)
|
||||
contents))
|
||||
|
||||
(defun org-icalendar--vcalendar (name owner tz description contents)
|
||||
(defun org-icalendar--vcalendar (name owner tz description ttl contents)
|
||||
"Create a VCALENDAR component.
|
||||
NAME, OWNER, TZ, DESCRIPTION and CONTENTS are all strings giving,
|
||||
NAME, OWNER, TZ, DESCRIPTION, TTL and CONTENTS are all strings giving,
|
||||
respectively, the name of the calendar, its owner, the timezone
|
||||
used, a short description and the other components included."
|
||||
(concat (format "BEGIN:VCALENDAR
|
||||
used, a short description, time to live (refresh period) and
|
||||
the other components included."
|
||||
(org-icalendar-fold-string
|
||||
(concat (format "BEGIN:VCALENDAR
|
||||
VERSION:2.0
|
||||
X-WR-CALNAME:%s
|
||||
PRODID:-//%s//Emacs with Org mode//EN
|
||||
X-WR-TIMEZONE:%s
|
||||
X-WR-CALDESC:%s
|
||||
CALSCALE:GREGORIAN\n"
|
||||
(org-icalendar-cleanup-string name)
|
||||
(org-icalendar-cleanup-string owner)
|
||||
(org-icalendar-cleanup-string tz)
|
||||
(org-icalendar-cleanup-string description))
|
||||
contents
|
||||
"END:VCALENDAR\n"))
|
||||
X-WR-CALDESC:%s\n"
|
||||
(org-icalendar-cleanup-string name)
|
||||
(org-icalendar-cleanup-string owner)
|
||||
(org-icalendar-cleanup-string tz)
|
||||
(org-icalendar-cleanup-string description))
|
||||
(when ttl (format "X-PUBLISHED-TTL:%s\n"
|
||||
(org-icalendar-cleanup-string ttl)))
|
||||
"CALSCALE:GREGORIAN\n"
|
||||
contents
|
||||
"END:VCALENDAR\n")))
|
||||
|
||||
|
||||
|
||||
@@ -926,14 +1108,13 @@ Return ICS file name."
|
||||
(let ((file (buffer-file-name (buffer-base-buffer))))
|
||||
(when (and file org-icalendar-store-UID)
|
||||
(org-icalendar-create-uid file 'warn-user)))
|
||||
;; Export part. Since this back-end is backed up by `ascii', ensure
|
||||
;; Export part. Since this backend is backed up by `ascii', ensure
|
||||
;; links will not be collected at the end of sections.
|
||||
(let ((outfile (org-export-output-file-name ".ics" subtreep)))
|
||||
(org-export-to-file 'icalendar outfile
|
||||
async subtreep visible-only body-only
|
||||
'(:ascii-charset utf-8 :ascii-links-to-notes nil)
|
||||
'(lambda (file)
|
||||
(run-hook-with-args 'org-icalendar-after-save-hook file) nil))))
|
||||
#'org-icalendar--post-process-file)))
|
||||
|
||||
;;;###autoload
|
||||
(defun org-icalendar-export-agenda-files (&optional async)
|
||||
@@ -961,7 +1142,13 @@ external process."
|
||||
(catch 'nextfile
|
||||
(org-check-agenda-file file)
|
||||
(with-current-buffer (org-get-agenda-file-buffer file)
|
||||
(org-icalendar-export-to-ics))))
|
||||
(condition-case err
|
||||
(org-icalendar-export-to-ics)
|
||||
(error
|
||||
(warn "Exporting %s to icalendar failed: %s"
|
||||
file
|
||||
(error-message-string err))
|
||||
(signal (car err) (cdr err)))))))
|
||||
(org-release-buffers org-agenda-new-buffers)))))
|
||||
|
||||
;;;###autoload
|
||||
@@ -1018,8 +1205,9 @@ This function assumes major mode for current buffer is
|
||||
user-full-name
|
||||
(or (org-string-nw-p org-icalendar-timezone) (format-time-string "%Z"))
|
||||
org-icalendar-combined-description
|
||||
org-icalendar-ttl
|
||||
contents)))
|
||||
(run-hook-with-args 'org-icalendar-after-save-hook file)))
|
||||
(org-icalendar--post-process-file file)))
|
||||
|
||||
(defun org-icalendar--combine-files (&rest files)
|
||||
"Combine entries from multiple files into an iCalendar file.
|
||||
@@ -1042,6 +1230,8 @@ FILES is a list of files to build the calendar from."
|
||||
(format-time-string "%Z"))
|
||||
;; Description.
|
||||
org-icalendar-combined-description
|
||||
;; TTL (Refresh period)
|
||||
org-icalendar-ttl
|
||||
;; Contents.
|
||||
(concat
|
||||
;; Agenda contents.
|
||||
@@ -1061,8 +1251,7 @@ FILES is a list of files to build the calendar from."
|
||||
(when (and org-icalendar-include-bbdb-anniversaries
|
||||
(require 'ol-bbdb nil t))
|
||||
(with-output-to-string (org-bbdb-anniv-export-ical)))))))
|
||||
(run-hook-with-args 'org-icalendar-after-save-hook
|
||||
org-icalendar-combined-agenda-file))
|
||||
(org-icalendar--post-process-file org-icalendar-combined-agenda-file))
|
||||
(org-release-buffers org-agenda-new-buffers))))
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user