tdiary-contrib 5.4.0 → 5.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0ea6ac9aa57f48f37e87e0487488694fedde1f3b36f9a352532975e205a9a0b8
4
- data.tar.gz: 9e33bee8f1c7fa448357428e5ba06f5574553d3b37fb0a7b031cfaa00c557632
3
+ metadata.gz: 12adb1758c3d032d996c055bd197971f0ccd527b3f3c8084b09d9565331b1584
4
+ data.tar.gz: 3e9c54111c414ebd1f2a9867a7c2d9c594557ea54c8aa2e345f9745b1f283743
5
5
  SHA512:
6
- metadata.gz: 5e3ac23fa672b189da7a9e19cc3b6b24d46c66e30b2d32fd58dd74dff13009863d54340501fafe6c231ceab537c3570ccd11f893d3b6a4b79b69df52ab9a425d
7
- data.tar.gz: 9e8db2d64a4c054da3ba8dfcd947b435fcfe3e4a86b66dd5be9e5c33a902e66486fe01201adde278b4b8dbff3348bcdc2430b3108b75e04ed2822a8101ee357b
6
+ metadata.gz: 590cea4c3c1440cc140812de2d133e991573e7b216eaaf240be3262152fcacaa38cdfc36c3568339200434f0e37126639f21c582d7b89ec97174b04f1bf58b35
7
+ data.tar.gz: 159563f601640899e155a6ea33e742dc0d293ec7b220f0f749f1d14195c899791ffbd80f0a2a75522cde3d914ce3088d95479f9fe5b1bec1df4cdaf61add3480
@@ -1,5 +1,5 @@
1
1
  module TDiary
2
2
  class Contrib
3
- VERSION = "5.4.0"
3
+ VERSION = "5.5.0"
4
4
  end
5
5
  end
data/plugin/flickr.rb CHANGED
@@ -59,34 +59,17 @@ def flickr_photo_info(photo_id, size)
59
59
 
60
60
  begin
61
61
  flickr_open('flickr.photos.getInfo', photo_id) {|f|
62
- if defined?(Oga)
63
- res = Oga.parse_xml(f)
64
- photo[:page] = res.xpath('/rsp/photo/urls/url').text
65
- photo[:title] = res.xpath('/rsp/photo/title').text
66
- else
67
- res = REXML::Document.new(f)
68
- photo[:page] = res.elements['//rsp/photo/urls/url'].text
69
- photo[:title] = res.elements['//rsp/photo/title'].text
70
- end
62
+ res = REXML::Document.new(f)
63
+ photo[:page] = res.elements['//rsp/photo/urls/url'].text
64
+ photo[:title] = res.elements['//rsp/photo/title'].text
71
65
  }
72
66
  flickr_open('flickr.photos.getSizes', photo_id) {|f|
73
- if defined?(Oga)
74
- res = Oga.parse_xml(f)
75
- res.xpath('//rsp/sizes/size').each do |s|
76
- if s.get('label').downcase == size.downcase
77
- photo[:src] = s.get('source')
78
- photo[:width] = s.get('width')
79
- photo[:height] = s.get('height')
80
- end
81
- end
82
- else
83
- res = REXML::Document.new(f)
84
- res.elements.each('//rsp/sizes/size') do |s|
85
- if s.attributes['label'].downcase == size.downcase
86
- photo[:src] = s.attributes['source']
87
- photo[:width] = s.attributes['width']
88
- photo[:height] = s.attributes['height']
89
- end
67
+ res = REXML::Document.new(f)
68
+ res.elements.each('//rsp/sizes/size') do |s|
69
+ if s.attributes['label'].downcase == size.downcase
70
+ photo[:src] = s.attributes['source']
71
+ photo[:width] = s.attributes['width']
72
+ photo[:height] = s.attributes['height']
90
73
  end
91
74
  end
92
75
  }
@@ -0,0 +1,68 @@
1
+ #
2
+ # Google Analytics GA4 plugin for tDiary
3
+ #
4
+ # Copyright (C) 2025 KITADAI Yukinori <algebraicallyClosedField@gmail.com>
5
+ # You can redistribute it and/or modify it under GPL2.
6
+ #
7
+ if /^(?:latest|day|month|nyear|search)$/ =~ @mode then
8
+ add_footer_proc do
9
+ google_analytics_ga_insert_code
10
+ end
11
+ end
12
+
13
+ def google_analytics_ga_insert_code
14
+ return '' unless @conf['google_analytics_ga.profile']
15
+ <<-HTML
16
+ <!-- Global site tag (gtag.js) - Google Analytics -->
17
+ <script async src="https://www.googletagmanager.com/gtag/js?id=#{@conf['google_analytics_ga.profile']}"></script>
18
+ <script>
19
+ window.dataLayer = window.dataLayer || [];
20
+ function gtag(){dataLayer.push(arguments);}
21
+ gtag('js', new Date());
22
+
23
+ gtag('config', '#{@conf['google_analytics_ga.profile']}');
24
+ </script>
25
+ HTML
26
+ end
27
+
28
+ # G-XXXXXXXXXX
29
+ add_conf_proc( 'google_analytics_ga', 'Google Analytics GA4' ) do
30
+ if @mode == 'saveconf' then
31
+ @conf['google_analytics_ga.profile'] = @cgi.params['google_analytics_ga.profile'][0]
32
+ end
33
+ r = <<-HTML
34
+ <h3>Google Analytics GA4 Profile</h3>
35
+ <p>set your Profile ID (G-XXXXXXXXXX)</p>
36
+ <p><input name="google_analytics_ga.profile" value="#{h @conf['google_analytics_ga.profile']}"></p>
37
+ HTML
38
+ r
39
+ end
40
+
41
+ if defined? AMP
42
+ add_amp_header_proc do
43
+ %Q|<script async custom-element="amp-analytics"
44
+ src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>|
45
+ end
46
+
47
+ add_amp_body_enter_proc do
48
+ profile_id = %w(google_analytics_ga.profile).map {|key|
49
+ @conf[key]
50
+ }.find {|profile|
51
+ profile && !profile.empty?
52
+ }
53
+ <<-HTML
54
+ <amp-analytics type="gtag" data-credentials="include">
55
+ <script type="application/json">
56
+ {
57
+ "vars": {
58
+ "gtag_id": "#{h profile_id}"
59
+ "config" : {
60
+ "#{h profile_id}": { "groups": "default" }
61
+ }
62
+ }
63
+ }
64
+ </script>
65
+ </amp-analytics>
66
+ HTML
67
+ end
68
+ end
data/plugin/gyazo.rb CHANGED
@@ -5,19 +5,58 @@
5
5
  #
6
6
  require 'net/http'
7
7
  require 'json'
8
+ require 'fileutils'
8
9
 
9
10
  if /^(form|edit|formplugin|showcomment)$/ =~ @mode then
10
11
  enable_js('gyazo.js')
11
12
  end
12
13
 
13
- def gyazo(permalink_url, alt = '[description]', style = 'photo')
14
- size = @conf['gyazo_max_size'] || 512
14
+ def gyazo_extract_id(permalink_url)
15
+ # Extract Gyazo ID from permalink URL
16
+ # e.g., "https://gyazo.com/71a4efdd066f5c2ebd11ee5a37b8f9ef" -> "71a4efdd066f5c2ebd11ee5a37b8f9ef"
17
+ permalink_url.to_s.strip.match(%r{gyazo\.com/([a-f0-9]+)}i)&.captures&.first
18
+ end
19
+
20
+ def gyazo_oembed_fetch(permalink_url)
21
+ gyazo_id = gyazo_extract_id(permalink_url)
22
+ return nil unless gyazo_id
23
+
24
+ cache_dir = File.join(@cache_path, 'gyazo')
25
+ cache_file = File.join(cache_dir, "#{gyazo_id}.json")
26
+
27
+ # Load from cache if available (skip cache in preview mode)
28
+ if @mode != 'preview' && File.exist?(cache_file)
29
+ begin
30
+ json_data = File.read(cache_file)
31
+ return JSON.parse(json_data, symbolize_names: true)
32
+ rescue => e
33
+ @logger.warn("gyazo.rb: cache read error for #{gyazo_id}: #{e.message}")
34
+ # Continue to fetch from API if cache read fails
35
+ end
36
+ end
37
+
38
+ # Fetch from Gyazo OEmbed API
15
39
  begin
16
- oembed = JSON.parse(Net::HTTP.get(URI("https://api.gyazo.com/api/oembed?url=#{permalink_url}")), symbolize_names: true)
40
+ json_data = Net::HTTP.get(URI("https://api.gyazo.com/api/oembed?url=#{permalink_url}"))
41
+ oembed = JSON.parse(json_data, symbolize_names: true)
42
+
43
+ # Save to cache
44
+ FileUtils.mkdir_p(cache_dir) unless File.directory?(cache_dir)
45
+ File.write(cache_file, json_data)
46
+
47
+ oembed
17
48
  rescue => e
18
- @logger.error(e)
19
- return ''
49
+ @logger.error("gyazo.rb: API error for #{permalink_url}: #{e.message}")
50
+ nil
20
51
  end
52
+ end
53
+
54
+ def gyazo(permalink_url, alt = '[description]', style = 'photo')
55
+ size = @conf['gyazo_max_size'] || 512
56
+
57
+ oembed = gyazo_oembed_fetch(permalink_url)
58
+ return '' unless oembed
59
+
21
60
  url = oembed[:url].gsub(%r|/thumb/\d+/|, "/thumb/#{size}/")
22
61
  width = oembed[:width].to_i
23
62
  height = oembed[:height].to_i
@@ -69,11 +108,29 @@ add_form_proc() do |date|
69
108
  '</div></div>'
70
109
  end
71
110
 
111
+ def gyazo_clear_cache
112
+ cache_dir = File.join(@cache_path, 'gyazo')
113
+ return unless File.directory?(cache_dir)
114
+
115
+ count = 0
116
+ Dir.glob(File.join(cache_dir, '*.json')).each do |cache_file|
117
+ File.delete(cache_file)
118
+ count += 1
119
+ end
120
+ @logger.info("gyazo.rb: cleared #{count} cache files")
121
+ count
122
+ end
123
+
72
124
  add_conf_proc('gyazo', 'Gyazo') do
73
125
  if @mode == 'saveconf'
74
126
  @conf['gyazo_token'] = @cgi.params['gyazo_token'][0]
75
127
  @conf['gyazo_max_images'] = @cgi.params['gyazo_max_images'][0].to_i
76
128
  @conf['gyazo_max_size'] = @cgi.params['gyazo_max_size'][0].to_i
129
+
130
+ if @cgi.params['gyazo_clear_cache'][0] == 'true'
131
+ count = gyazo_clear_cache
132
+ @conf.save
133
+ end
77
134
  end
78
135
  @conf['gyazo_max_images'] = 5 if @conf['gyazo_max_images'].to_i < 1
79
136
  @conf['gyazo_max_size'] = 512 if @conf['gyazo_max_size'].to_i < 1
@@ -83,8 +140,12 @@ add_conf_proc('gyazo', 'Gyazo') do
83
140
  r << %Q|<p>Get your token from <a href="https://gyazo.com/oauth/applications">Gyazo Applications</a></p>|
84
141
  r << %Q|<p><input name="gyazo_token" value="#{h @conf['gyazo_token']}" size=64></p>|
85
142
  r << %Q|<h3 class="subtitle">Max images in list</h3>|
86
- r << %Q|<p><input name="gyazo_token" value="#{h @conf['gyazo_max_images']}" size=3></p>|
143
+ r << %Q|<p><input name="gyazo_max_images" value="#{h @conf['gyazo_max_images']}" size=3></p>|
87
144
  r << %Q|<h3 class="subtitle">Max image size</h3>|
88
- r << %Q|<p><input name="gyazo_token" value="#{h @conf['gyazo_max_size']}" size=4> px</p>|
145
+ r << %Q|<p><input name="gyazo_max_size" value="#{h @conf['gyazo_max_size']}" size=4> px</p>|
146
+ r << %Q|<h3 class="subtitle">Clear cache</h3>|
147
+ r << %Q|<p><label for="gyazo_clear_cache">|
148
+ r << %Q|<input type="checkbox" id="gyazo_clear_cache" name="gyazo_clear_cache" value="true">|
149
+ r << %Q| Clear all cached OEmbed data</label></p>|
89
150
  r
90
151
  end
data/plugin/oembed.rb ADDED
@@ -0,0 +1,191 @@
1
+ # SPDX-License-Identifier: MIT
2
+ #
3
+ # oembed.rb - YouTubeやSpotifyなどが提供する埋め込みタグをoEmbedを利用して取得する
4
+ #
5
+ # 使い方:
6
+ # 日記の中で <%=oembed 'https://www.youtube.com/watch?v=jNQXAC9IVRw' %> などと記述します。
7
+ # <%=oembed 'https://www.youtube.com/watch?v=jNQXAC9IVRw', 500, 200 %> などと書くことで幅や高さを指定することもできます。
8
+ #
9
+ # オプション:
10
+ # url (required): URL
11
+ # width (optional, number): 埋め込みタグの幅(単位: px)
12
+ # height (optional, number): 埋め込みタグの高さ(単位: px)
13
+ #
14
+ # 設定:
15
+ # なし
16
+ #
17
+ # 構築方法:
18
+ # 1. nokolexborをインストールします。以下の方法などからインストールできます。
19
+ # - Gemfile.localに`gem 'nokolexbor'`と記述し、`bundle install`を実行する
20
+ # - `gem install nokolexbor` を実行する
21
+ #
22
+ # その他:
23
+ # - xml形式でoembedのデータを処理する実装はしていません。
24
+ #
25
+ # Copyright (c) 2025 eniehack <http://www.eniehack.net/~eniehack>
26
+
27
+ require "pstore"
28
+ require "nokolexbor"
29
+ require "open-uri"
30
+ require 'json'
31
+
32
+ def fetch_oembed(endpoint_url, target_url, maxwidth = nil, maxheight = nil)
33
+ params = URI.decode_www_form(endpoint_url.query).to_h
34
+ params["url"] = target_url
35
+ params["format"] = "json" if params["format"].nil?
36
+ params["maxheight"] = maxheight unless maxheight.nil?
37
+ params["maxwidth"] = maxwidth unless maxwidth.nil?
38
+ oembed_url = endpoint_url
39
+ oembed_url.query = URI.encode_www_form(params)
40
+
41
+ begin
42
+ URI.open(oembed_url,
43
+ 'User-Agent' => 'tDiary oEmbed Plugin/1.0',
44
+ read_timeout: 10,
45
+ redirect: false
46
+ ) do |response|
47
+ JSON.parse(response.read, symbolize_names: true)
48
+ end
49
+ rescue OpenURI::HTTPError, JSON::ParserError, Timeout::Error => e
50
+ nil
51
+ end
52
+ end
53
+
54
+ def parse_link_header(link_header, base_url)
55
+ link_pattern = /<([^>]+)>(?:\s*;\s*([^,]+))?/
56
+
57
+ link_header.split(',').each do |link|
58
+ match = link.strip.match(link_pattern)
59
+ next unless match
60
+
61
+ url = match[1]
62
+ params_str = match[2] || ''
63
+
64
+ params = {}
65
+ params_str.scan(/(\w+)=(?:"([^"]+)"|([^;,\s]+))/) do |key, quoted_val, unquoted_val|
66
+ params[key] = quoted_val || unquoted_val
67
+ end
68
+
69
+ if params['rel'] == 'alternate' &&
70
+ params['type'] == 'application/json+oembed' then
71
+ return URI.join(base_url, url).to_s
72
+ end
73
+ end
74
+
75
+ nil
76
+ end
77
+
78
+ # HTMLからoEmbedエンドポイントを探す
79
+ def discover_oembed_endpoint(url)
80
+ begin
81
+ URI.open(url,
82
+ 'User-Agent' => 'tDiary oEmbed Plugin/1.0',
83
+ read_timeout: 5
84
+ ) do |response|
85
+ if response.meta["link"] then
86
+ link = parse_link_header(response.meta["link"], url)
87
+ return link if link
88
+ end
89
+ doc = Nokolexbor::HTML(response.read)
90
+ link = doc.at_xpath('//link[@rel="alternate" and @type="application/json+oembed"]/@href')
91
+ return link.to_s if link
92
+ nil
93
+ end
94
+ rescue => e
95
+ nil
96
+ end
97
+ end
98
+
99
+ def fallback_html(url)
100
+ %Q|<a href="#{url}">#{url}</a>|
101
+ end
102
+
103
+ def generate_general_oembed_endpoint(url)
104
+ parsed_url = URI.parse(url)
105
+ general_params = URI.decode_www_form(parsed_url.query).filter { |arr| arr[0] != 'url' }.to_h
106
+ general_params['format'] = 'json' unless general_params['format']
107
+ parsed_url.query = URI.encode_www_form(general_params)
108
+ parsed_url
109
+ end
110
+
111
+ def get_oembed_endpoint(url, db)
112
+ uri = URI.parse(url)
113
+ domain = uri.host.downcase
114
+
115
+ db.transaction do |store|
116
+ store[:endpoints] ||= {}
117
+ cached_endpoint = store[:endpoints][domain]
118
+
119
+ if cached_endpoint && Time.now < cached_endpoint[:expires_at]
120
+ return URI.parse(cached_endpoint[:url])
121
+ end
122
+ end
123
+
124
+ # cacheやHTMLなどからoEmbedエンドポイントを探す
125
+ discovered_endpoint = discover_oembed_endpoint(url)
126
+ if discovered_endpoint
127
+ # 相対URLを絶対URLに変換
128
+ discovered_endpoint = URI.join(url, discovered_endpoint).to_s
129
+
130
+ # ディスカバリー結果をキャッシュに保存(短期間)
131
+ endpoint = generate_general_oembed_endpoint(discovered_endpoint)
132
+ db.transaction do |store|
133
+ store[:endpoints][domain] = {
134
+ url: endpoint,
135
+ discovered_at: Time.now,
136
+ expires_at: Time.now + (3600 * 24 * 7)
137
+ }
138
+ end
139
+ return endpoint
140
+ end
141
+
142
+ nil
143
+ end
144
+
145
+ def build_html(json, url)
146
+ case json[:type]
147
+ when "rich"
148
+ %Q|<div style="width: #{json[:width]}px;">#{json[:html]}</div>|
149
+ when "video"
150
+ %Q|<div style="width: #{json[:width]}px;">#{json[:html]}</div>|
151
+ when "image"
152
+ %Q|<div style="width: #{json[:width]}px;"><img src="#{json[:url]}" height="#{json[:height]}" width="#{json[:width]}" /></div>|
153
+ when "link"
154
+ %Q|<div><a href="#{url}">#{json[:title]}</a></div>|
155
+ else
156
+ fallback_html(url)
157
+ end
158
+ end
159
+
160
+ def oembed(url, width = nil, height = nil)
161
+ cache_file = "#{@cache_path}/oembed/cache.pstore"
162
+
163
+ begin
164
+ db = PStore.new(cache_file)
165
+ db.transaction do |store|
166
+ cached = store[:responses][url] unless store[:responses].nil?
167
+ if cached && cached[:expires_at] && Time.now < cached[:expires_at]
168
+ return build_html(cached[:json], url)
169
+ end
170
+ end
171
+
172
+ # サービスのoEmbedエンドポイントをcacheまたはHTMLから探す
173
+ endpoint = get_oembed_endpoint(url, db)
174
+ return fallback_html(url) unless endpoint
175
+
176
+ # oEmbedによる埋め込みコードの取得
177
+ oembed_data = fetch_oembed(endpoint, url, width, height)
178
+ return fallback_html(url) unless oembed_data
179
+
180
+ db.transaction do |store|
181
+ store[:responses] ||= {}
182
+ store[:responses][url] = {
183
+ json: oembed_data,
184
+ expires_at: Time.now + (3600 * 24 * 3)
185
+ }
186
+ end
187
+ build_html(oembed_data, url)
188
+ rescue => e
189
+ fallback_html(url)
190
+ end
191
+ end
data/plugin/ogp.rb CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  # @conf['ogp.facebook.app_id'] - your facebook application ID.
8
8
  # @conf['ogp.facebook.admins'] - your facebook ID.
9
+ # @conf['ogp.fediverse.creator'] - your fediverse ID.
9
10
 
10
11
  def ogp_description(html)
11
12
  @conf.shorten(remove_tag(html), 200)
@@ -28,7 +29,8 @@ def ogp_tag
28
29
  ogp = ogp_tag_org || ''
29
30
  headers = {
30
31
  'fb:app_id' => @conf['ogp.facebook.app_id'],
31
- 'fb:admins' => @conf['ogp.facebook.admins']
32
+ 'fb:admins' => @conf['ogp.facebook.admins'],
33
+ 'fediverse:creator' => @conf['ogp.fediverse.creator']
32
34
  }
33
35
 
34
36
  if @mode == 'day'
@@ -60,8 +62,13 @@ end
60
62
 
61
63
  add_conf_proc('Open Graph Protocol', 'Open Graph Protocol') do
62
64
  if @mode == 'saveconf'
63
- @conf['ogp.facebook.app_id'] = @cgi.params['ogp.facebook.app_id'][0]
64
- @conf['ogp.facebook.admins'] = @cgi.params['ogp.facebook.admins'][0]
65
+ %w(
66
+ ogp.facebook.app_id
67
+ ogp.facebook.admins
68
+ ogp.fediverse.creator
69
+ ).each do |name|
70
+ @conf[name] = @cgi.params[name][0]
71
+ end
65
72
  end
66
73
 
67
74
  <<-HTML
@@ -70,5 +77,8 @@ add_conf_proc('Open Graph Protocol', 'Open Graph Protocol') do
70
77
 
71
78
  <h3>Facebook User ID</h3>
72
79
  <p><input name="ogp.facebook.admins" value="#{h(@conf['ogp.facebook.admins'])}"></p>
80
+
81
+ <h3>Fediverse ID</h3>
82
+ <p><input name="ogp.fediverse.creator" value="#{h(@conf['ogp.fediverse.creator'])}"></p>
73
83
  HTML
74
84
  end
@@ -0,0 +1,59 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ require 'spec_helper'
3
+
4
+ describe "google_analytics_ga plugin" do
5
+ def setup_google_analytics_ga_plugin(profile_id, mode)
6
+ fake_plugin(:google_analytics_ga) { |plugin|
7
+ plugin.mode = mode
8
+ plugin.conf['google_analytics_ga.profile'] = profile_id
9
+ }
10
+ end
11
+
12
+ describe "should render javascript" do
13
+ before do
14
+ @plugin = setup_google_analytics_ga_plugin('G-XXXXXXXXXX', 'latest')
15
+ end
16
+
17
+ it "for footer" do
18
+ snippet = @plugin.footer_proc
19
+ expect(snippet).to eq(expected_html_footer_snippet)
20
+ end
21
+ end
22
+
23
+ describe "should render javascript" do
24
+ before do
25
+ @plugin = setup_google_analytics_ga_plugin('G-XXXXXXXXXX', 'conf')
26
+ end
27
+
28
+ it "for footer" do
29
+ snippet = @plugin.footer_proc
30
+ expect(snippet).to be_empty
31
+ end
32
+ end
33
+
34
+ describe "should not render when profile_id is empty" do
35
+ before do
36
+ @plugin = setup_google_analytics_ga_plugin(nil, 'latest')
37
+ end
38
+
39
+ it "for footer" do
40
+ snippet = @plugin.footer_proc
41
+ expect(snippet).to be_empty
42
+ end
43
+ end
44
+
45
+ def expected_html_footer_snippet
46
+ expected = <<-SCRIPT
47
+ <!-- Global site tag (gtag.js) - Google Analytics -->
48
+ <script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
49
+ <script>
50
+ window.dataLayer = window.dataLayer || [];
51
+ function gtag(){dataLayer.push(arguments);}
52
+ gtag('js', new Date());
53
+
54
+ gtag('config', 'G-XXXXXXXXXX');
55
+ </script>
56
+ SCRIPT
57
+ expected.gsub( /^\t/, '' ).chomp
58
+ end
59
+ end
@@ -0,0 +1,69 @@
1
+ * What's this?
2
+ Emacs から tDiary を更新するための Major mode です.
3
+ * How to Use
4
+ load-path の通った所に [[file:tdiary-mode.el][tdiary-mode.el]] を置いて,
5
+ 以下を =.emacs= に記載して下さい.
6
+ #+BEGIN_SRC emacs-lisp
7
+ ;; 更新する日記のリスト
8
+ (setq tdiary-diary-list '(("my 1st diary" "http://example.com/tdiary/")
9
+ ("my 2nd diary" "http://example.com/tdiary2/")))
10
+ ;; 更新後に日記原稿を保存するディレクトリ
11
+ (setq tdiary-text-directory (expand-file-name "~/path-to-saved-diary"))
12
+ ;; preview に使う関数
13
+ (setq tdiary-browser-function 'browse-url)
14
+ ;; CSRF key ;; tdiary.conf から csrf.protection.key をコピーする
15
+ (setq tdiary-csrf-key "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX")
16
+ ;; スタイルに応じて: org スタイル
17
+ (setq tdiary-style-mode 'org-mode
18
+ tdiary-text-suffix ".org")
19
+ ;; スタイルに応じて: tDiary as html スタイル
20
+ ;; (setq tdiary-style-mode 'html-mode
21
+ ;; tdiary-text-suffix ".td")
22
+ ;; スタイルに応じて: gfm スタイル
23
+ ;; (setq tdiary-style-mode 'markdown-mode
24
+ ;; tdiary-text-suffix ".md")
25
+ ;; スタイルに応じて: RD スタイル
26
+ ;; (setq tdiary-style-mode 'rd-mode
27
+ ;; tdiary-text-suffix ".rd")
28
+ ;; 以下, お好みで: テンプレートとして登録する plugin の定義
29
+ ;; (setq tdiary-plugin-definition
30
+ ;; '(
31
+ ;; ("STRONG" ("<%=STRONG %Q|" (p "str: ") "| %>"))
32
+ ;; ("PRE" ("<%=PRE %Q|" (p "str: ") "| %>"))
33
+ ;; ("CITE" ("<%=CITE %Q|" (p "str: ") "| %>"))
34
+ ;; ))
35
+ ;; (add-hook 'tdiary-mode-hook
36
+ ;; '(lambda ()
37
+ ;; (local-set-key "\C-i" 'tdiary-complete-plugin)))
38
+ #+END_SRC
39
+
40
+ パスワードを保存したい場合には
41
+ #+BEGIN_SRC emacs-lisp
42
+ (setq tdiary-passwd-file (locate-user-emacs-file "tdiary-passwd" ".tdiary-passwd"))
43
+ #+END_SRC
44
+ とでもしておいて
45
+ #+BEGIN_EXAMPLE
46
+ M-x tdiary-password-save
47
+ #+END_EXAMPLE
48
+ として下さい(そのうち =auth-source= に対応する予定です).
49
+ * Notice
50
+ 上手く書き込みできない場合にはご自身のtDiaryで
51
+ CSRFの設定がどうなっているかを確認してください.
52
+ tDiaryが動いているマシンにsshできるのであればCSRF KEYは
53
+ =tdiary.conf= に書いてあります.
54
+
55
+ sshができない場合は何かしらの方法でCSRF KEYを知る必要がありますが,
56
+ 良いアイデアがありません.
57
+ * ToDO [1/4]
58
+
59
+ - [ ] 認証情報を =auth-source= で扱う
60
+ - [X] Customize 対応
61
+ - [ ] テストの作成
62
+ - [ ] melpa への登録申請?
63
+ * LICENSE
64
+ #+BEGIN_EXAMPLE
65
+ Copyright (C) 2002 Junichiro Kita
66
+ 2017-2026 Youhei SASAKI
67
+ License: GPL-2.0+
68
+ #+END_EXAMPLE
69
+ @see [[file:LICENSE][LICENSE]] for detail.