gitnoted 0.1.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 +7 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +40 -0
- data/README.md +162 -0
- data/Rakefile +5 -0
- data/bin/gitnoted +95 -0
- data/gitnoted.gemspec +25 -0
- data/lib/git_noted.rb +2 -0
- data/lib/git_noted/application.rb +163 -0
- data/lib/git_noted/public/css/gitnoted.css +60 -0
- data/lib/git_noted/public/index.html +54 -0
- data/lib/git_noted/public/js/gitnoted.js +46 -0
- data/lib/git_noted/repository.rb +148 -0
- metadata +171 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 514bf87ae44f1a65208940e4a8db87e5a049b27d
|
4
|
+
data.tar.gz: 944db11d0806f73287350138421c35d8b956b38f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0ae0007cff3bb75b96d16831d7e779ab2798a80db0dfade08fe3dcb88c9b7b6f470dcb39cb41832c9dc207f14d5a458a6e4a3e98123552ac0b9ce63ee3f2b320
|
7
|
+
data.tar.gz: 32581b4a57bcf3970965cd85f448bbd2955d3cf2b530332b2be49b7edb89a9bbff6234efc5f18abad3385ae152fbe3d5fa8c4647d1d164354ee49b4f764a64ca
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
gitnoted (0.1.0)
|
5
|
+
concurrent-ruby (>= 1.0.5)
|
6
|
+
puma (~> 3.7)
|
7
|
+
rack-cors (~> 0.4)
|
8
|
+
redcarpet (~> 3.4)
|
9
|
+
rugged (~> 0.25)
|
10
|
+
sigdump (>= 0.2.4)
|
11
|
+
sinatra (~> 1.4)
|
12
|
+
|
13
|
+
GEM
|
14
|
+
remote: https://rubygems.org/
|
15
|
+
specs:
|
16
|
+
concurrent-ruby (1.0.5)
|
17
|
+
puma (3.8.1)
|
18
|
+
rack (1.6.5)
|
19
|
+
rack-cors (0.4.1)
|
20
|
+
rack-protection (1.5.3)
|
21
|
+
rack
|
22
|
+
rake (12.0.0)
|
23
|
+
redcarpet (3.4.0)
|
24
|
+
rugged (0.25.1.1)
|
25
|
+
sigdump (0.2.4)
|
26
|
+
sinatra (1.4.8)
|
27
|
+
rack (~> 1.5)
|
28
|
+
rack-protection (~> 1.4)
|
29
|
+
tilt (>= 1.3, < 3)
|
30
|
+
tilt (2.0.6)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
gitnoted!
|
37
|
+
rake (>= 0.9.2)
|
38
|
+
|
39
|
+
BUNDLED WITH
|
40
|
+
1.14.6
|
data/README.md
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
# GitNoted
|
2
|
+
|
3
|
+
GitNoted is a simple document server that works with [Github Wiki](https://help.github.com/articles/about-github-wikis/) or [Gollum](https://github.com/gollum/gollum) to serve foot notes for external web sites just by adding a small script tag.
|
4
|
+
|
5
|
+
* [JavaScript tag](#javascript-tag)
|
6
|
+
* [Writing notes](#writing-notes)
|
7
|
+
* [Running server with Github Wiki](#running-server-with-github-wiki)
|
8
|
+
* [Running server with other git repositories](#running-server-with-other-git-repositories)
|
9
|
+
* [Deploying to Heroku](#deploying-to-heroku)
|
10
|
+
* [Server usage](#server-usage)
|
11
|
+
|
12
|
+
## JavaScript tag
|
13
|
+
|
14
|
+
You can find the JavaScript code on [lib/git_noted/public/js/gitnoted.js](lib/git_noted/public/js/gitnoted.js). This file will be served as `/js/gitnoted.js` from the GitNoted server. You can import it using `<script>` tag as following.
|
15
|
+
|
16
|
+
```
|
17
|
+
<!-- GitNoted depends on jQuery -->
|
18
|
+
<script src="https://code.jquery.com/jquery-3.1.1.min.js"
|
19
|
+
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
|
20
|
+
crossorigin="anonymous"></script>
|
21
|
+
|
22
|
+
<!-- GitNoted -->
|
23
|
+
<link rel="stylesheet" type="text/css" href="http://localhost:4567/css/gitnoted.css" />
|
24
|
+
<script type="text/javascript" src="http://localhost:4567/js/gitnoted.js"></script>
|
25
|
+
```
|
26
|
+
|
27
|
+
Please replace `http://localhost:4567` to point your GitNoted server (see bellow).
|
28
|
+
|
29
|
+
Once the JavaScript tag is set up, you can add following `<div class="gitnoted" data-labels="label,label,...">` tags your HTML to embed notes. Here is an example:
|
30
|
+
|
31
|
+
```
|
32
|
+
<div class="gitnoted" data-labels="purpose:test,message:hello"></div>
|
33
|
+
```
|
34
|
+
|
35
|
+
`data-labels` is used to search notes by labels. Notes that include all of them match.
|
36
|
+
|
37
|
+
|
38
|
+
## Writing notes
|
39
|
+
|
40
|
+
Edit mode must be Markdown. Other formats are not supported.
|
41
|
+
|
42
|
+
In the body of a page, you need to include `:label: label,label,label,...` line at the beginning or at the end. Example:
|
43
|
+
|
44
|
+
```
|
45
|
+
# My first wiki page
|
46
|
+
|
47
|
+
Hello!
|
48
|
+
|
49
|
+
---
|
50
|
+
|
51
|
+
:label: purpose:test,message:hello
|
52
|
+
```
|
53
|
+
|
54
|
+
## Running server with Github Wiki
|
55
|
+
|
56
|
+
Prepare following information first:
|
57
|
+
|
58
|
+
* Domain names (and ports) of your website that embeds notes. You can have multiple web sites. Here uses `example.com` and `localhost:8080` as an example.
|
59
|
+
* Repository URL of a Github Wiki. Format of the URL is `https://github.com/<user>/<repo>.wiki.git` where "<user> and <repo> are your repository's username and repository name.
|
60
|
+
|
61
|
+
This command starts a server on http://localhost:4567:
|
62
|
+
|
63
|
+
```
|
64
|
+
$ gem install gitnoted
|
65
|
+
$ gitnoted "https://github.com/frsyuki/gitnoted.wiki.git" \
|
66
|
+
./repo \
|
67
|
+
-a example.com -a localhost:8080 \
|
68
|
+
-h localhost -p 4567
|
69
|
+
```
|
70
|
+
|
71
|
+
If your repository is private, you also need to set `GITHUB_ACCESS_TOKEN` environment variable. You can create a token on [your account configuration page](https://github.com/settings/tokens). Example:
|
72
|
+
|
73
|
+
```
|
74
|
+
$ gem install gitnoted
|
75
|
+
$ export GITHUB_ACCESS_TOKEN=abcdef0123456789abcdef0123456789abcdef01
|
76
|
+
$ gitnoted \
|
77
|
+
"https://github.com/frsyuki/my_secret_repository.wiki.git" \
|
78
|
+
./repo \
|
79
|
+
-a example.com -a localhost:8080 \
|
80
|
+
-h localhost -p 4567
|
81
|
+
```
|
82
|
+
|
83
|
+
## Running server with other git repositories
|
84
|
+
|
85
|
+
Instead of using Github Wiki, you can use any git repositories that contain files named `<name>.md`. You can use your text editor or tools such as [Gollum](https://github.com/gollum/gollum) to push `.md` files.
|
86
|
+
|
87
|
+
To start GitNoted for those git repositories, prepare following information:
|
88
|
+
|
89
|
+
* Domain names (and ports) of your website that embeds notes. You can have multiple web sites. Here uses `example.com` and `localhost:8080` as an example.
|
90
|
+
* Repository URL of the git repository. It should provide http access (ssh is not supported at this moment).
|
91
|
+
* Username and password of the git repository. You need to set them to GIT_USERNAME and GIT_PASSWORD environment variables.
|
92
|
+
|
93
|
+
This command starts a server on http://localhost:4567:
|
94
|
+
|
95
|
+
```
|
96
|
+
$ gem install gitnoted
|
97
|
+
$ export GIT_USERNAME=myname
|
98
|
+
$ export GIT_PASSWORD=topsecret
|
99
|
+
$ gitnoted \
|
100
|
+
"https://github.com/frsyuki/my_secret_repository.wiki.git" \
|
101
|
+
./repo \
|
102
|
+
-a example.com -a localhost:8080 \
|
103
|
+
-h localhost -p 4567
|
104
|
+
```
|
105
|
+
|
106
|
+
## Deploying to Heroku
|
107
|
+
|
108
|
+
You need to create 4 files in a new git repository.
|
109
|
+
|
110
|
+
### Procfile
|
111
|
+
|
112
|
+
Put a gitnoted command in your `Procfile` with `$PORT` variable as the port number. Here is an example:
|
113
|
+
|
114
|
+
```
|
115
|
+
web: bundle exec gitnoted "https://github.com/frsyuki/gitnoted.wiki.git ./repo -a example.com -h 0.0.0.0 -p $PORT
|
116
|
+
```
|
117
|
+
|
118
|
+
### Gemfile
|
119
|
+
|
120
|
+
```
|
121
|
+
source "https://rubygems.org"
|
122
|
+
ruby "2.4.0"
|
123
|
+
gem "gitnoted"
|
124
|
+
```
|
125
|
+
|
126
|
+
### .buildpacks
|
127
|
+
|
128
|
+
```
|
129
|
+
https://github.com/ddollar/heroku-buildpack-apts
|
130
|
+
https://github.com/heroku/heroku-buildpack-ruby
|
131
|
+
```
|
132
|
+
|
133
|
+
### Aptfile
|
134
|
+
|
135
|
+
```
|
136
|
+
cmake
|
137
|
+
pkg-config
|
138
|
+
```
|
139
|
+
|
140
|
+
## Server usage
|
141
|
+
|
142
|
+
```
|
143
|
+
$ gitnoted [options] <git url> <local path to store>
|
144
|
+
options:
|
145
|
+
-a, --allow-origin DOMAIN[:PORT] Allow cross-origin resource sharing (CORS) from this domain (can be set multiple times)
|
146
|
+
-h, --host ADDRESS Bind address (default: 'localhost')
|
147
|
+
-p, --port PORT Port (default: 4567)
|
148
|
+
-e, --extra-app PATH.rb Add an extra Sinatra application
|
149
|
+
-i, --interval SECONDS Interval to update the git repository
|
150
|
+
--threads MIN:MAX Number of HTTP worker threads
|
151
|
+
environment variables:
|
152
|
+
GIT_USERNAME Git username
|
153
|
+
GIT_PASSWORD Git password
|
154
|
+
GITHUB_ACCESS_TOKEN Github personal API token
|
155
|
+
```
|
156
|
+
|
157
|
+
----
|
158
|
+
|
159
|
+
GitNoted
|
160
|
+
Author: Sadayuki Furuhashi
|
161
|
+
License: MIT
|
162
|
+
|
data/Rakefile
ADDED
data/bin/gitnoted
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
extra_app_paths = []
|
7
|
+
|
8
|
+
scheduler_options = {
|
9
|
+
interval: 60
|
10
|
+
}
|
11
|
+
|
12
|
+
app_options = {
|
13
|
+
allow_origins: [],
|
14
|
+
}
|
15
|
+
|
16
|
+
repo_options = {
|
17
|
+
logger: Logger.new(STDOUT),
|
18
|
+
}
|
19
|
+
|
20
|
+
server_options = {
|
21
|
+
server: 'puma',
|
22
|
+
Host: 'localhost',
|
23
|
+
Port: 4567,
|
24
|
+
Threads: nil,
|
25
|
+
}
|
26
|
+
|
27
|
+
op = OptionParser.new
|
28
|
+
op.banner = "#{$0} [options] <git url> <local path to store>"
|
29
|
+
|
30
|
+
op.separator " options:"
|
31
|
+
|
32
|
+
op.on('-a', '--allow-origin DOMAIN[:PORT]', "Allow cross-origin resource sharing (CORS) from this domain (can be set multiple times)") do |v|
|
33
|
+
app_options[:allow_origins] << v
|
34
|
+
end
|
35
|
+
|
36
|
+
op.on('-h', '--host ADDRESS', "Bind address (default: 'localhost')") do |v|
|
37
|
+
server_options[:Host] = v
|
38
|
+
end
|
39
|
+
|
40
|
+
op.on('-p', '--port PORT', Integer, "Port (default: 4567)") do |v|
|
41
|
+
server_options[:Port] = v
|
42
|
+
end
|
43
|
+
|
44
|
+
op.on('-e', '--extra-app PATH.rb', "Add an extra Sinatra application") do |v|
|
45
|
+
extra_app_paths << v
|
46
|
+
end
|
47
|
+
|
48
|
+
op.on('-i', '--interval SECONDS', Integer, "Interval to update the git repository") do |v|
|
49
|
+
scheduler_options[:interval] = v
|
50
|
+
end
|
51
|
+
|
52
|
+
op.on('--threads MIN:MAX', "Number of HTTP worker threads") do |v|
|
53
|
+
server_options[:Threads] = v
|
54
|
+
end
|
55
|
+
|
56
|
+
op.separator <<EOF
|
57
|
+
environment variables:
|
58
|
+
GIT_USERNAME Git username
|
59
|
+
GIT_PASSWORD Git password
|
60
|
+
GITHUB_ACCESS_TOKEN Github personal API token
|
61
|
+
EOF
|
62
|
+
|
63
|
+
|
64
|
+
op.parse!(ARGV)
|
65
|
+
|
66
|
+
if ARGV.length != 2
|
67
|
+
puts op.to_s
|
68
|
+
exit 1
|
69
|
+
end
|
70
|
+
|
71
|
+
remote_url, local_path = *ARGV
|
72
|
+
|
73
|
+
require 'git_noted'
|
74
|
+
require 'sigdump/setup'
|
75
|
+
|
76
|
+
ENV['RACK_ENV'] ||= 'production'
|
77
|
+
|
78
|
+
if ENV['GIT_USERNAME']
|
79
|
+
repo_options[:username] = ENV['GIT_USERNAME']
|
80
|
+
repo_options[:password] = ENV['GIT_PASSWORD']
|
81
|
+
elsif ENV['GITHUB_ACCESS_TOKEN']
|
82
|
+
repo_options[:username] = ENV['GITHUB_ACCESS_TOKEN']
|
83
|
+
repo_options[:password] = 'x-oauth-basic'
|
84
|
+
end
|
85
|
+
|
86
|
+
repository = GitNoted::Repository.new(remote_url, local_path, **repo_options)
|
87
|
+
app = GitNoted::Application.with(repository: repository, **app_options)
|
88
|
+
repository.schedule_update! scheduler_options[:interval]
|
89
|
+
|
90
|
+
extra_app_paths.each do |path|
|
91
|
+
app.instance_eval(File.read(path), path)
|
92
|
+
end
|
93
|
+
|
94
|
+
app.run!(server_options)
|
95
|
+
|
data/gitnoted.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
|
2
|
+
Gem::Specification.new do |gem|
|
3
|
+
gem.name = "gitnoted"
|
4
|
+
gem.description = "Simple document server that works with Github Wiki or Gollum to serve foot notes for external websites just by adding a small script tag"
|
5
|
+
gem.homepage = "https://github.com/frsyuki/gitnoted"
|
6
|
+
gem.summary = gem.description
|
7
|
+
gem.version = "0.1.0"
|
8
|
+
gem.authors = ["Sadayuki Furuhashi"]
|
9
|
+
gem.email = ["frsyuki@gmail.com"]
|
10
|
+
gem.license = "MIT"
|
11
|
+
gem.has_rdoc = false
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
15
|
+
gem.require_paths = ['lib']
|
16
|
+
|
17
|
+
gem.add_dependency 'sinatra', '~> 1.4'
|
18
|
+
gem.add_dependency 'puma', '~> 3.7'
|
19
|
+
gem.add_dependency 'rack-cors', '~> 0.4'
|
20
|
+
gem.add_dependency 'redcarpet', '~> 3.4'
|
21
|
+
gem.add_dependency 'rugged', '~> 0.25'
|
22
|
+
gem.add_dependency 'concurrent-ruby', '>= 1.0.5'
|
23
|
+
gem.add_dependency 'sigdump', '>= 0.2.4'
|
24
|
+
gem.add_development_dependency "rake", ">= 0.9.2"
|
25
|
+
end
|
data/lib/git_noted.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'rack/cors'
|
3
|
+
require 'rack/static'
|
4
|
+
require 'redcarpet'
|
5
|
+
require 'erb'
|
6
|
+
require 'json'
|
7
|
+
require 'git_noted/repository'
|
8
|
+
|
9
|
+
module GitNoted
|
10
|
+
class Application < Sinatra::Base
|
11
|
+
def self.default_renderer
|
12
|
+
renderer = Redcarpet::Render::HTML.new({
|
13
|
+
escape_html: true,
|
14
|
+
safe_links_only: true,
|
15
|
+
})
|
16
|
+
redcarpet = Redcarpet::Markdown.new(renderer, {
|
17
|
+
tables: true,
|
18
|
+
no_intra_emphasis: true
|
19
|
+
})
|
20
|
+
redcarpet.method(:render)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.with(allow_origins: [], **options)
|
24
|
+
Class.new(self) do
|
25
|
+
alias_method :initialize_saved, :initialize
|
26
|
+
define_method(:initialize) do
|
27
|
+
initialize_saved(**options)
|
28
|
+
end
|
29
|
+
|
30
|
+
use Rack::Cors do
|
31
|
+
allow do
|
32
|
+
origins *allow_origins unless allow_origins.empty?
|
33
|
+
|
34
|
+
resource '/api/*', {
|
35
|
+
methods: [:get, :options, :head],
|
36
|
+
headers: :any,
|
37
|
+
expose: [],
|
38
|
+
credentials: true,
|
39
|
+
max_age: 600,
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
allow do
|
44
|
+
origins '*'
|
45
|
+
resource '/public/*', :headers => :any, :methods => :get
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(repository:, renderer: Application.default_renderer)
|
52
|
+
super()
|
53
|
+
@repository = repository
|
54
|
+
@renderer = renderer
|
55
|
+
end
|
56
|
+
|
57
|
+
attr_accessor :repository
|
58
|
+
attr_accessor :renderer
|
59
|
+
|
60
|
+
use Rack::Static, urls: ["/js", "/css"], root: File.expand_path("../public", __FILE__)
|
61
|
+
|
62
|
+
include ERB::Util
|
63
|
+
|
64
|
+
TRANSPARENT_1PX_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVQYV2NgYAAAAAMAAWgmWQ0AAAAASUVORK5CYII=".unpack('m').first
|
65
|
+
|
66
|
+
get '/' do
|
67
|
+
redirect "/index.html"
|
68
|
+
end
|
69
|
+
|
70
|
+
# GET /api/notes
|
71
|
+
# ?labels=k1:v1,k2:v2,...
|
72
|
+
# ?exclude_labels=k1:v1,k2:v2,...
|
73
|
+
get '/api/notes' do
|
74
|
+
notes = load_notes(params).map do |note|
|
75
|
+
{
|
76
|
+
labels: note.labels,
|
77
|
+
body: read_note(note)
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
response = {
|
82
|
+
notes: notes
|
83
|
+
}
|
84
|
+
|
85
|
+
content_type "application/json"
|
86
|
+
return response.to_json
|
87
|
+
end
|
88
|
+
|
89
|
+
# GET /api/notes.html
|
90
|
+
# ?labels=k1:v1,k2:v2,...
|
91
|
+
# ?exclude_labels=k1:v1,k2:v2,...
|
92
|
+
get '/api/notes.html' do
|
93
|
+
notes = load_notes(params)
|
94
|
+
|
95
|
+
html = %[<ul class="gitnoted">]
|
96
|
+
notes.each do |note|
|
97
|
+
body = render_note(note)
|
98
|
+
html << %[<li class="gitnoted-note">]
|
99
|
+
html << %[<div class="gitnoted-body">#{body}</div>]
|
100
|
+
html << %[<ul class="gitnoted-labels">]
|
101
|
+
note.labels.each do |label|
|
102
|
+
html << %[<li class="gitnoted-label">#{html_escape(label)}</li>]
|
103
|
+
end
|
104
|
+
html << %[</ul>]
|
105
|
+
html << %[</li>]
|
106
|
+
end
|
107
|
+
html << %[</ul>]
|
108
|
+
|
109
|
+
content_type "text/html"
|
110
|
+
return html
|
111
|
+
end
|
112
|
+
|
113
|
+
# GET /api/labels
|
114
|
+
# ?prefix=k1:
|
115
|
+
# ?used_with=k1:v1
|
116
|
+
get '/api/labels' do
|
117
|
+
labels = load_labels(params)
|
118
|
+
|
119
|
+
response = {
|
120
|
+
labels: labels
|
121
|
+
}
|
122
|
+
|
123
|
+
content_type "application/json"
|
124
|
+
return response.to_json
|
125
|
+
end
|
126
|
+
|
127
|
+
# force update
|
128
|
+
get '/api/github_hook' do
|
129
|
+
@repository.update!
|
130
|
+
|
131
|
+
response = { }
|
132
|
+
|
133
|
+
content_type "application/json"
|
134
|
+
return response.to_json
|
135
|
+
end
|
136
|
+
|
137
|
+
get '/favicon.ico' do
|
138
|
+
content_type "image/png"
|
139
|
+
return TRANSPARENT_1PX_PNG
|
140
|
+
end
|
141
|
+
|
142
|
+
def read_note(note)
|
143
|
+
@repository.read(note)
|
144
|
+
end
|
145
|
+
|
146
|
+
def render_note(note)
|
147
|
+
@renderer.call(read_note(note))
|
148
|
+
end
|
149
|
+
|
150
|
+
def load_notes(params)
|
151
|
+
label_names = (params[:labels] || '').split(",")
|
152
|
+
exclude_label_names = (params[:exclude_labels] || '').split(",")
|
153
|
+
@repository.search_notes(labels: label_names, exclude_labels: exclude_label_names)
|
154
|
+
end
|
155
|
+
|
156
|
+
def load_labels(params)
|
157
|
+
used_with_label_names = (params[:used_with] || '').split(',')
|
158
|
+
prefix = params[:prefix]
|
159
|
+
prefix = nil if prefix == ''
|
160
|
+
@repository.search_labels(prefix: prefix, used_with: used_with_label_names)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
ul.gitnoted {
|
2
|
+
list-style-type: none;
|
3
|
+
list-style-position: outside;
|
4
|
+
margin: 0;
|
5
|
+
padding: 0;
|
6
|
+
}
|
7
|
+
|
8
|
+
ul.gitnoted > li.gitnoted-note {
|
9
|
+
display: block;
|
10
|
+
margin: 2em 0;
|
11
|
+
padding: 0.8em 1em 0.5em 1em;
|
12
|
+
border: 1px solid #ddd;
|
13
|
+
background-color: #fff;
|
14
|
+
}
|
15
|
+
|
16
|
+
ul.gitnoted > li.gitnoted-note > div.gitnoted-body
|
17
|
+
h1 {
|
18
|
+
font-size: 120%;
|
19
|
+
}
|
20
|
+
|
21
|
+
ul.gitnoted > li.gitnoted-note > div.gitnoted-body
|
22
|
+
h2 {
|
23
|
+
font-size: 120%;
|
24
|
+
}
|
25
|
+
|
26
|
+
ul.gitnoted > li.gitnoted-note > div.gitnoted-body
|
27
|
+
h3 {
|
28
|
+
font-size: 100%;
|
29
|
+
}
|
30
|
+
|
31
|
+
ul.gitnoted > li.gitnoted-note > div.gitnoted-body
|
32
|
+
h4 {
|
33
|
+
font-size: 100%;
|
34
|
+
}
|
35
|
+
|
36
|
+
ul.gitnoted > li.gitnoted-note > ul.gitnoted-labels {
|
37
|
+
list-style-type: none;
|
38
|
+
list-style-position: outside;
|
39
|
+
text-align: right;
|
40
|
+
font-size: 70%;
|
41
|
+
}
|
42
|
+
|
43
|
+
ul.gitnoted > li.gitnoted-note > ul.gitnoted-labels > li.gitnoted-label {
|
44
|
+
display: inline-block;
|
45
|
+
margin-left: 1.3em;
|
46
|
+
margin-right: -1em;
|
47
|
+
padding: 0 0.5em;
|
48
|
+
color: #fff;
|
49
|
+
background-color: #bbb;
|
50
|
+
border-radius: 0.8em;
|
51
|
+
}
|
52
|
+
|
53
|
+
ul.gitnoted > li.gitnoted-note > ul.gitnoted-labels .gitnoted-label-button:hover {
|
54
|
+
background-color: #888;
|
55
|
+
}
|
56
|
+
|
57
|
+
ul.gitnoted > li.gitnoted-note code {
|
58
|
+
white-space: pre;
|
59
|
+
}
|
60
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<title>GitNotes setup completed</title>
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<h1>GitNotes setup completed</h1>
|
8
|
+
<section id="api_notes_html">
|
9
|
+
<form action="/api/notes.html":>
|
10
|
+
<h2>GET /api/notes.html</h2>
|
11
|
+
<p>Search and render notes.</p>
|
12
|
+
<div>
|
13
|
+
<label for="labels">Labels (separated by comma ","):</label><br />
|
14
|
+
<input type="text" name="labels" id="labels" />
|
15
|
+
</div>
|
16
|
+
<div>
|
17
|
+
<label for="exclude_labels">Exclude labels (separated by comma ","):</label><br />
|
18
|
+
<input type="text" name="exclude_labels" id="exclude_labels" />
|
19
|
+
</div>
|
20
|
+
<input type="submit" value="Go">
|
21
|
+
</form>
|
22
|
+
</section>
|
23
|
+
<section id="api_notes">
|
24
|
+
<form action="/api/notes":>
|
25
|
+
<h2>GET /api/notes</h2>
|
26
|
+
<p>Search notes.</p>
|
27
|
+
<div>
|
28
|
+
<label for="labels">Labels (separated by comma ","):</label><br />
|
29
|
+
<input type="text" name="labels" id="labels" />
|
30
|
+
</div>
|
31
|
+
<div>
|
32
|
+
<label for="exclude_labels">Exclude labels (separated by comma ","):</label><br />
|
33
|
+
<input type="text" name="exclude_labels" id="exclude_labels" />
|
34
|
+
</div>
|
35
|
+
<input type="submit" value="Go">
|
36
|
+
</form>
|
37
|
+
</section>
|
38
|
+
<section id="api_labels">
|
39
|
+
<form action="/api/labels":>
|
40
|
+
<h2>GET /api/labels</h2>
|
41
|
+
<p>Search labels.</p>
|
42
|
+
<div>
|
43
|
+
<label for="prefix">Label name prefix:</label><br />
|
44
|
+
<input type="text" name="prefix" id="prefix" />
|
45
|
+
</div>
|
46
|
+
<div>
|
47
|
+
<label for="used_with">Used with labels (separated by comma ","):</label><br />
|
48
|
+
<input type="text" name="used_with" id="used_with" />
|
49
|
+
</div>
|
50
|
+
<input type="submit" value="Go">
|
51
|
+
</form>
|
52
|
+
</section>
|
53
|
+
</body>
|
54
|
+
</html>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
var gitNotesUrl = new URL(document.currentScript.src)
|
2
|
+
gitNotesUrl.pathname = "/api/notes.html"
|
3
|
+
|
4
|
+
$(document).ready(function() {
|
5
|
+
function injectExpandScript(e, parentLabels) {
|
6
|
+
e.querySelectorAll('.gitnoted-label').forEach(function (e) {
|
7
|
+
var url = new URL(gitNotesUrl)
|
8
|
+
url.searchParams.set('labels', e.innerText)
|
9
|
+
url.searchParams.set('exclude_labels', parentLabels)
|
10
|
+
var nextParentLabels = `${parentLabels},${e.innerText}`
|
11
|
+
e.classList.add('gitnoted-label-button')
|
12
|
+
e.onclick = function() {
|
13
|
+
console.log(`exclude_labels: ${nextParentLabels}`)
|
14
|
+
fetch(url, {mode: 'cors'}).then(function (r) {
|
15
|
+
return r.text()
|
16
|
+
}).then(function (t) {
|
17
|
+
var template = document.createElement('template')
|
18
|
+
template.innerHTML = t
|
19
|
+
var note = template.content.firstChild
|
20
|
+
injectExpandScript(note, nextParentLabels)
|
21
|
+
e.parentNode.parentNode.appendChild(note, e)
|
22
|
+
e.classList.remove('gitnoted-label-button')
|
23
|
+
e.onclick = null
|
24
|
+
})
|
25
|
+
}
|
26
|
+
})
|
27
|
+
}
|
28
|
+
|
29
|
+
document.querySelectorAll('div.gitnoted').forEach(function (e) {
|
30
|
+
var url = new URL(gitNotesUrl)
|
31
|
+
if (e.dataset.labels) {
|
32
|
+
url.searchParams.set('labels', e.dataset.labels)
|
33
|
+
}
|
34
|
+
fetch(url, {mode: 'cors'}).then(function (r) {
|
35
|
+
return r.text()
|
36
|
+
}).then(function (t) {
|
37
|
+
var template = document.createElement('template')
|
38
|
+
template.innerHTML = t
|
39
|
+
var note = template.content.firstChild
|
40
|
+
if (e.dataset.labels) {
|
41
|
+
injectExpandScript(note, e.dataset.labels)
|
42
|
+
}
|
43
|
+
e.parentNode.replaceChild(note, e)
|
44
|
+
})
|
45
|
+
})
|
46
|
+
})
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'rugged'
|
2
|
+
require 'concurrent'
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module GitNoted
|
6
|
+
class Repository
|
7
|
+
class Note
|
8
|
+
def initialize(name, path, labels)
|
9
|
+
@name = name
|
10
|
+
@path = path
|
11
|
+
@labels = labels
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :name, :path, :labels
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(remote_url, local_path, username: nil, password: nil, logger: Logger.new(STDERR))
|
18
|
+
@local_path = Pathname.new(local_path).cleanpath
|
19
|
+
@remote_url = remote_url
|
20
|
+
if username
|
21
|
+
@credentials = Rugged::Credentials::UserPassword.new(username: username, password: password)
|
22
|
+
else
|
23
|
+
@credentials = nil
|
24
|
+
end
|
25
|
+
@logger = logger
|
26
|
+
|
27
|
+
# initial update
|
28
|
+
update! || index!
|
29
|
+
end
|
30
|
+
|
31
|
+
def update!
|
32
|
+
begin
|
33
|
+
@repo ||= Rugged::Repository.init_at(@local_path.to_s)
|
34
|
+
updated = fetch(@repo)
|
35
|
+
return false unless updated
|
36
|
+
rescue => e
|
37
|
+
# fall back to clone.
|
38
|
+
@logger.warn "Failed to update repository incrementally. Falling back to clone: #{e}"
|
39
|
+
end
|
40
|
+
@repo = clone
|
41
|
+
index!
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
|
45
|
+
def read(note)
|
46
|
+
File.read(note.path).sub(/^:label:(.*)$\n/, '')
|
47
|
+
end
|
48
|
+
|
49
|
+
def schedule_update!(interval)
|
50
|
+
Concurrent::ScheduledTask.execute(interval) do
|
51
|
+
begin
|
52
|
+
update!
|
53
|
+
rescue => e
|
54
|
+
@logger.error "Failed to update repository: #{e}"
|
55
|
+
e.backtrace.each do |bt|
|
56
|
+
@logger.error " #{bt}"
|
57
|
+
end
|
58
|
+
ensure
|
59
|
+
schedule_update!(interval)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def search_notes(labels: nil, exclude_labels: nil)
|
65
|
+
labels ||= []
|
66
|
+
exclude_labels ||= []
|
67
|
+
|
68
|
+
@notes.select do |note|
|
69
|
+
labels.all? {|t| note.labels.include?(t) } &&
|
70
|
+
!exclude_labels.any? {|t| note.labels.include?(t) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def search_labels(prefix: nil, used_with: nil)
|
75
|
+
match = {}
|
76
|
+
@notes.each do |note|
|
77
|
+
if used_with.nil? || used_with.all? {|t| note.labels.include?(t) }
|
78
|
+
if prefix.nil?
|
79
|
+
matching = note.labels
|
80
|
+
else
|
81
|
+
matching = note.labels.select {|label| label.start_with?(prefix) }
|
82
|
+
end
|
83
|
+
matching.each {|label| match[label] = true }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
match.keys.sort
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def clone
|
92
|
+
logger_timer "Cloning remote repository." do
|
93
|
+
tmp_path = "#{@local_path}.tmp"
|
94
|
+
FileUtils.rm_rf tmp_path
|
95
|
+
Rugged::Repository.clone_at(@remote_url, tmp_path, credentials: @credentials)
|
96
|
+
FileUtils.rm_rf @local_path
|
97
|
+
FileUtils.mv tmp_path, @local_path
|
98
|
+
Rugged::Repository.init_at(@local_path.to_s)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def fetch(repo)
|
103
|
+
logger_timer "Fetching remote repository." do
|
104
|
+
remote = repo.remotes["origin"]
|
105
|
+
unless remote
|
106
|
+
raise "Remote repository is not fetched yet."
|
107
|
+
end
|
108
|
+
fetched = remote.fetch
|
109
|
+
return fetched[:total_objects] > 0
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def index!
|
114
|
+
logger_timer "Updating label index." do
|
115
|
+
files = Dir["#{@local_path}/**/*.md"]
|
116
|
+
@notes = files.map do |path|
|
117
|
+
name = Pathname.new(path).relative_path_from(Pathname.new(@local_path)).sub(/\.md$/, '')
|
118
|
+
parse_note(name, path, File.read(path))
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
LABEL_KEY_CHARSET = /[a-zA-Z0-9_\-\.\/]/
|
124
|
+
LABEL_VALUE_CHARSET = /[a-zA-Z0-9_\-\.\/]/
|
125
|
+
|
126
|
+
def parse_note(name, path, text)
|
127
|
+
m = /^:label:(.*)$/.match(text)
|
128
|
+
if m
|
129
|
+
labels = m[1].scan(/[a-zA-Z0-9_\-\.\/]+:[a-zA-Z0-9_\-\.\/]+/)
|
130
|
+
else
|
131
|
+
labels = []
|
132
|
+
end
|
133
|
+
|
134
|
+
Note.new(name, path, labels)
|
135
|
+
end
|
136
|
+
|
137
|
+
def logger_timer(start_message, end_message = "%.2f seconds.")
|
138
|
+
@logger.info start_message
|
139
|
+
start_time = Time.now
|
140
|
+
begin
|
141
|
+
return yield
|
142
|
+
ensure
|
143
|
+
finish_time = Time.now
|
144
|
+
@logger.info end_message % (finish_time - start_time)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
metadata
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitnoted
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Sadayuki Furuhashi
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sinatra
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: puma
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.7'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.7'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rack-cors
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: redcarpet
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.4'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.4'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rugged
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.25'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.25'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: concurrent-ruby
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.0.5
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.0.5
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: sigdump
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.2.4
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.2.4
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: rake
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 0.9.2
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 0.9.2
|
125
|
+
description: Simple document server that works with Github Wiki or Gollum to serve
|
126
|
+
foot notes for external websites just by adding a small script tag
|
127
|
+
email:
|
128
|
+
- frsyuki@gmail.com
|
129
|
+
executables:
|
130
|
+
- gitnoted
|
131
|
+
extensions: []
|
132
|
+
extra_rdoc_files: []
|
133
|
+
files:
|
134
|
+
- Gemfile
|
135
|
+
- Gemfile.lock
|
136
|
+
- README.md
|
137
|
+
- Rakefile
|
138
|
+
- bin/gitnoted
|
139
|
+
- gitnoted.gemspec
|
140
|
+
- lib/git_noted.rb
|
141
|
+
- lib/git_noted/application.rb
|
142
|
+
- lib/git_noted/public/css/gitnoted.css
|
143
|
+
- lib/git_noted/public/index.html
|
144
|
+
- lib/git_noted/public/js/gitnoted.js
|
145
|
+
- lib/git_noted/repository.rb
|
146
|
+
homepage: https://github.com/frsyuki/gitnoted
|
147
|
+
licenses:
|
148
|
+
- MIT
|
149
|
+
metadata: {}
|
150
|
+
post_install_message:
|
151
|
+
rdoc_options: []
|
152
|
+
require_paths:
|
153
|
+
- lib
|
154
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ">="
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '0'
|
159
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
160
|
+
requirements:
|
161
|
+
- - ">="
|
162
|
+
- !ruby/object:Gem::Version
|
163
|
+
version: '0'
|
164
|
+
requirements: []
|
165
|
+
rubyforge_project:
|
166
|
+
rubygems_version: 2.6.8
|
167
|
+
signing_key:
|
168
|
+
specification_version: 4
|
169
|
+
summary: Simple document server that works with Github Wiki or Gollum to serve foot
|
170
|
+
notes for external websites just by adding a small script tag
|
171
|
+
test_files: []
|