;; There's nothing hard or overly complicated here. Take a look at
;; describe-mode (`C-h m') which will show you the keybindings
;; available. All of the "lj-mode specific" interactive commands
-;; have a binding. There are 3 "global" commands that don't...
+;; have a binding. There are 5 "global" commands that don't...
;;
;; #'lj
;; #'lj-blog-buffer
;; #'lj-blog-region
+;; #'lj-edit-old-post
+;; #'lj-delete-old-post
;;
;; The only reason they don't have keybindings is that I think it'd
;; be bad form on my part to set global keys for you. Assign them
;;
;;; Version:
-(defconst lj-version 1.29
+(defconst lj-version 1.30
"Version number of SXEmacs/LJ.")
;;; Code:
:type 'function
:group 'lj)
-(defcustom lj-archive-posts nil
+(defcustom lj-archive-posts t
"*Keep an archive copy of LJ posts when non-nil."
:type 'boolean
:group 'lj)
(copy-face 'bold 'lj-separator)
(set-face-foreground 'lj-separator "red")
+(make-face 'lj-header-itemid "Face used for LJ ItemID header content.")
+(set-face-parent 'lj-header-itemid 'lj-separator)
+
+(make-face 'lj-header-url "Face used for LJ URL header content.")
+(set-face-parent 'lj-header-url 'lj-separator)
+
;; compatibility hoohar
(unless (featurep 'sxemacs)
(fset #'defregexp #'defvar))
(defregexp lj-header-regexp
(let ((headers '("Subject" "FCC" "BCC" "Security" "Community"
- "Location" "Mood" "Music" "Userpic" "Tags")))
+ "Location" "Mood" "Music" "Userpic" "Tags"
+ "X-LJ-URL" "X-LJ-ItemID")))
(concat (regexp-opt headers t) ":"))
"Regular expression matching LJ headers.")
("^Location: \\(.*$\\)" 1 lj-header-location)
("^Userpic: \\(.*$\\)" 1 lj-header-userpic)
("^Tags: \\(.*$\\)" 1 lj-header-tags)
+ ("^X-LJ-URL: \\(.*$\\)" 1 lj-header-url)
+ ("^X-LJ-ItemID: \\(.*$\\)" 1 lj-header-itemid)
(,(regexp-quote lj-header-separator) 0 lj-separator))
hm--html-font-lock-keywords
html-font-lock-keywords)
(defun lj-construct-url (subject body user
&optional security tags community
auto-format no-comments mood location
- music pickw date backdated)
+ music pickw date backdated itemid)
"Construct a URL to use for posting to LiveJournal.
Argument SUBJECT, a string, which is the title of the post.
(ltime (or lj-last-entry-btime (and (lj-get-last-entry-btime)
lj-last-entry-btime)))
url)
- ;; save custom date in case something goes wrong
- (if date
+ ;; save custom date in case something goes wrong (not for edits)
+ (if (and date
+ (not (zerop (length itemid))))
(setq lj-last-user-set-time date)
(setq lj-last-user-set-time nil))
(setq subject (lj-hexify-string subject t))
(if (> (length location) 0)
(setq location (lj-hexify-string location t))
(setq location nil))
- ;; maybe force opt_backdated
- (when (> ltime ctime)
+ ;; maybe force opt_backdated (not touching for edits)
+ (when (and (> ltime ctime)
+ (not (zerop (length itemid))))
(setq backdated t))
;; the final url
(setq url (concat
lj-base-url
- "?mode=postevent"
+ (format "?mode=%sevent" (if (zerop (length itemid))
+ "post"
+ "edit"))
"&user=" user
"&auth_method=cookie"
(format "&ver=%d" (if (lj-utf-emacs-p) 1 0))
(format "&prop_taglist=%s" tags))
(when community
(format "&usejournal=%s" community))
- "&year=" year
- "&mon=" month
- "&day=" day
- "&hour=" hour
- "&min=" minute
+ (when (zerop (length itemid)) ; leave date alone when editing
+ (format
+ "&year=%s&mon=%s&day=%s&hour=%s&min=%s"
+ year month day hour minute))
(when mood
(if (integerp mood)
(format "&prop_current_moodid=%d" mood)
"&prop_opt_preformatted=" (if auto-format "0" "1")
"&prop_opt_nocomments=" (if no-comments "1" "0")
"&prop_useragent=" (lj-hexify-string lj-useragent)
+ (unless (zerop (length itemid))
+ (format "&itemid=%s" itemid))
"&event=" body))
url))
(rename-buffer "*LJ-Post*" 'unique)
(when (lj-utf-emacs-p)
(set-buffer-file-coding-system 'utf-8))
- (insert " \n")
+ (insert "\n")
(make-extent (point-min) (point-at-eol))
(insert "Subject: \n")
(when lj-archive-posts
(insert "Tags: \n")
(backward-char 1))))
+(defun lj-goto-x-lj-itemid (&optional nocreate)
+ "Move to the X-LJ-ItemID header."
+ (goto-char (point-min))
+ (re-search-forward "^X-LJ-ItemID: " nil 'missing))
+
+(defun lj-goto-x-lj-url (&optional nocreate)
+ "Move to the X-LJ-URL header."
+ (goto-char (point-min))
+ (re-search-forward "^X-LJ-URL: " nil 'missing))
+
(defun lj-goto-body ()
"Move to the body of an LJ post buffer."
(interactive)
(defvar lj-last-url "No URL yet, got nothing to blog about?"
"The URL to your last posted blog entry on LiveJournal.")
+(defvar lj-item-id ""
+ "The itemid of the last post.")
+
(defun lj-post-proc-parser (buf)
"Process parser for `lj-post'.
Argument BUF is the process buffer used."
- (let ((url "^url\n\\(.*$\\)"))
+ (let ((url "^url\n\\(.*$\\)")
+ (itemid "^itemid\n\\(.*$\\)"))
(with-current-buffer buf
(when (lj-proc-success)
(setq lj-last-user-set-time nil)
(goto-char (point-min))
- (if (re-search-forward url nil t)
- (setq lj-last-url (match-string 1))
- (setq lj-last-url "NO URL RETURNED FROM LiveJournal"))
+ (save-excursion
+ (if (re-search-forward url nil t)
+ (setq lj-last-url (match-string 1))
+ (setq lj-last-url "NO URL RETURNED FROM LiveJournal")))
+ (save-excursion
+ (and (re-search-forward itemid nil t)
+ (setq lj-item-id (match-string 1))))
(kill-buffer nil)))))
(defun lj-archive-post (archive)
"Archive the current post to ARCHIVE."
(let ((buf (current-buffer)))
(with-current-buffer (find-file-noselect archive)
- (insert-buffer buf)
- (goto-char (point-max))
- (insert (format "\n\n<a href\"%s\">View Online</a>\n" lj-last-url))
+ (when (zerop (length (lj-header-content "x-lj-itemid")))
+ (insert (format "\nX-LJ-URL: %s\n" lj-last-url)
+ (format "X-LJ-ItemID: %s" lj-item-id))
+ (insert-buffer buf))
(save-buffer)
(kill-buffer nil))))
"&howmany=1")))
(lj-http-post url cookies #'lj-last-entry-proc-parser)))
+(defun lj-delete-post-proc-parser (buf)
+ "Process the output from `lj-delete-post'.
+
+Argument BUF is the process buffer that was used."
+ (with-current-buffer buf
+ (when (lj-proc-success)
+ (message "Your post has been successfully removed from LiveJournal.")
+ (kill-buffer nil))))
+
+(defun lj-stringify-id (id)
+ "Returns a string version of the number, ID."
+ (if (stringp id)
+ id
+ (and (numberp id)
+ (number-to-string id))))
+
+(defun lj-delete-post-internal (itemid)
+ "Delete the post with ITEMID."
+ (let ((cookies (or lj-cookies
+ (error "No LJ cookies found")))
+ (url (concat lj-base-url
+ "?mode=editevent"
+ "&user=" lj-user-id
+ "&auth_method=cookie"
+ (format "&ver=%d" (if (lj-utf-emacs-p) 1 0))
+ "&itemid=" (lj-stringify-id itemid)
+ "&prop_useragent=" (lj-hexify-string lj-useragent)
+ "&event=")))
+ (lj-http-post url cookies #'lj-delete-post-proc-parser)))
+
(defun lj-set-date/time ()
"Return an internal time value to use as post date/time.
(location (lj-header-content "location"))
(music (lj-header-content "music"))
(pickw (lj-header-content "userpic"))
+ (itemid (lj-header-content "x-lj-itemid"))
(cookies (or lj-cookies
(error "No LJ cookies found")))
(backdated nil)
(length music)
(length location))
(setq url (lj-construct-url subject body user security tags comm nil nil
- mood location music pickw date backdated))
+ mood location music pickw date backdated
+ itemid))
;; lets save the draft out to disc just in case something goes wrong
(save-buffer)
(lj-http-post url cookies #'lj-post-proc-parser)
;; (and lj-twitter-flag
;; (lj-twitter-update-status lj-twitter-username lj-twitter-password
;; subject lj-last-url))
- (delete-file draftid)
+ ;; If there is a itemid don't delete the draft because it is our
+ ;; archive copy
+ (when (zerop (length itemid))
+ (delete-file draftid))
(run-hooks 'lj-after-post-hook)))
;; keep track of the date of the last entry for backdating purposes
(lj-goto-body)
(lj-mode)))
+(defun lj-edit-old-post (post)
+ "Edit an already posted LJ entry."
+ (interactive (list
+ (read-file-name "Edit Post: "
+ lj-archive-directory "" t)))
+ (if (or (zerop (length post))
+ (not (file-readable-p (expand-file-name post))))
+ (error 'invalid-argument post)
+ (switch-to-buffer (find-file-noselect (expand-file-name post)))
+ (rename-buffer "*LJ-EDIT*" 'unique)
+ (and (zerop (length (lj-header-content "x-lj-itemid")))
+ (error "ItemID missing, CANNOT edit this post from SXEmacs/LJ"))
+ (goto-char (point-min))
+ (make-extent (point) (point-at-eol))
+ (lj-update-userpic-glyph
+ (expand-file-name (lj-header-content "userpic")
+ lj-userpic-directory))
+ (re-search-forward lj-header-separator nil t)
+ (forward-line -1)
+ (set-extent-property
+ (make-extent (point-at-bol) (1+ (point-at-eol))) 'invisible t)
+ (lj-goto-body)
+ (lj-mode)))
+
+(defun lj-delete-old-post (post)
+ "Delete a post from Livejournal."
+ (interactive (list
+ (read-file-name "Delete Old Post: "
+ lj-archive-directory "" t)))
+ (if (or (zerop (length post))
+ (not (file-readable-p (expand-file-name post))))
+ (error 'invalid-argument post)
+ (switch-to-buffer (find-file-noselect (expand-file-name post)))
+ (rename-buffer "*LJ-EDIT*" 'unique)
+ (let ((itemid (lj-header-content "x-lj-itemid")))
+ (and (zerop (length itemid))
+ (error "ItemID missing, CANNOT delete this post from SXEmacs/LJ"))
+ (goto-char (point-min))
+ (make-extent (point) (point-at-eol))
+ (lj-update-userpic-glyph
+ (expand-file-name (lj-header-content "userpic")
+ lj-userpic-directory))
+ (re-search-forward lj-header-separator nil t)
+ (forward-line -1)
+ (set-extent-property
+ (make-extent (point-at-bol) (1+ (point-at-eol))) 'invisible t)
+ (lj-goto-body)
+ (lj-mode)
+ (and (y-or-n-p "Are you sure you want to delete this post? ")
+ (progn
+ (delete-file post)
+ (lj-delete-post-internal itemid))))))
+
(provide 'lj)
;; On-load actions