redmine_apijs 6.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +2 -0
  3. data/LICENSE +339 -0
  4. data/README +74 -0
  5. data/app/controllers/apijs_controller.rb +326 -0
  6. data/app/views/application/_browser.html.erb +45 -0
  7. data/app/views/attachments/_links.html.erb +146 -0
  8. data/app/views/settings/_apijs.html.erb +256 -0
  9. data/assets/fonts/apijs/config.json +130 -0
  10. data/assets/fonts/apijs/fontello.woff +0 -0
  11. data/assets/fonts/apijs/fontello.woff2 +0 -0
  12. data/assets/images/apijs/player-black-200.png +0 -0
  13. data/assets/images/apijs/player-black-400.png +0 -0
  14. data/assets/images/apijs/player-white-200.png +0 -0
  15. data/assets/images/apijs/player-white-400.png +0 -0
  16. data/assets/images/apijs/tv.gif +0 -0
  17. data/assets/javascripts/apijs-redmine.min.js +7 -0
  18. data/assets/javascripts/apijs.min.js +7 -0
  19. data/assets/javascripts/app.js +224 -0
  20. data/assets/stylesheets/apijs-print.min.css +8 -0
  21. data/assets/stylesheets/apijs-redmine-rtl.min.css +8 -0
  22. data/assets/stylesheets/apijs-redmine.min.css +8 -0
  23. data/assets/stylesheets/apijs-screen-rtl.min.css +8 -0
  24. data/assets/stylesheets/apijs-screen.min.css +8 -0
  25. data/assets/stylesheets/styles.css +73 -0
  26. data/config/locales/cs.yml +30 -0
  27. data/config/locales/de.yml +30 -0
  28. data/config/locales/en.yml +30 -0
  29. data/config/locales/es.yml +30 -0
  30. data/config/locales/fr.yml +30 -0
  31. data/config/locales/it.yml +30 -0
  32. data/config/locales/ja.yml +30 -0
  33. data/config/locales/nl.yml +30 -0
  34. data/config/locales/pl.yml +30 -0
  35. data/config/locales/pt-BR.yml +30 -0
  36. data/config/locales/pt.yml +30 -0
  37. data/config/locales/ru.yml +30 -0
  38. data/config/locales/sk.yml +30 -0
  39. data/config/locales/tr.yml +30 -0
  40. data/config/locales/zh.yml +30 -0
  41. data/config/routes.rb +86 -0
  42. data/init.rb +69 -0
  43. data/lib/apijs_attachment.rb +252 -0
  44. data/lib/apijs_const.rb +35 -0
  45. data/lib/apijs_files.rb +55 -0
  46. data/lib/image.py +201 -0
  47. data/lib/redmine_apijs.rb +46 -0
  48. data/lib/useragentparser.rb +175 -0
  49. data/lib/video.py +78 -0
  50. data/redmine_apijs.gemspec +69 -0
  51. metadata +94 -0
@@ -0,0 +1,46 @@
1
+ # encoding: utf-8
2
+ # Created L/13/07/2020
3
+ # Updated D/09/08/2020
4
+ #
5
+ # Copyright 2008-2020 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
6
+ # https://www.luigifab.fr/redmine/apijs
7
+ #
8
+ # This program is free software, you can redistribute it or modify
9
+ # it under the terms of the GNU General Public License (GPL) as published
10
+ # by the free software foundation, either version 2 of the license, or
11
+ # (at your option) any later version.
12
+ #
13
+ # This program is distributed in the hope that it will be useful,
14
+ # but without any warranty, without even the implied warranty of
15
+ # merchantability or fitness for a particular purpose. See the
16
+ # GNU General Public License (GPL) for more details.
17
+
18
+ require 'rails/engine'
19
+
20
+ module RedmineApijs
21
+
22
+ # Gemify redmine plugin (https://github.com/koppen/redmine_github_hook/commit/a82bcccfd0731503509771d3f715d8fb1e6f1bfe)
23
+ # Run the classic redmine plugin initializer after rails boot
24
+ class Plugin < ::Rails::Engine
25
+ config.after_initialize do
26
+ require File.expand_path('../../init', __FILE__)
27
+ ::ActionController::Base.prepend_view_path(File.expand_path('../../app/views/', __FILE__))
28
+
29
+ # And copy plugin assets
30
+ unless ::Redmine::Configuration['mirror_plugins_assets_on_startup'] == false
31
+ module ::Redmine
32
+ class Plugin
33
+ def assets_directory
34
+ if directory =~ /redmine_apijs/
35
+ File.join(File.expand_path('../../', __FILE__), 'assets')
36
+ else
37
+ File.join(directory, 'assets')
38
+ end
39
+ end
40
+ end
41
+ end
42
+ Redmine::Plugin.mirror_assets('redmine_apijs')
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,175 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Copyright 2013-2020 | Jesse G. Donat <donatj~gmail~com>
4
+ # https://github.com/donatj/PhpUserAgent
5
+ #
6
+ # Copyright 2019-2020 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
7
+ # https://gist.github.com/luigifab/19a68d9aa98fa80f2961809d7cec59c0 (1.0.0-fork2)
8
+ #
9
+ # Parses a user agent string into its important parts
10
+ # Licensed under the MIT License
11
+ #
12
+
13
+ class Useragentparser
14
+
15
+ def parse(userAgent=nil)
16
+
17
+ userAgent = Thread.current[:request].env['HTTP_USER_AGENT'] unless userAgent
18
+
19
+ platform = nil
20
+ browser = nil
21
+ version = nil
22
+ empty = {'platform' => platform, 'browser' => browser, 'version' => version}
23
+ priority = ['Xbox One', 'Xbox', 'Windows Phone', 'Tizen', 'Android', 'FreeBSD', 'NetBSD', 'OpenBSD', 'CrOS', 'X11']
24
+
25
+ return empty unless userAgent
26
+
27
+ if (parentMatches = userAgent.match(/\((.*?)\)/m))
28
+ result = parentMatches[1].scan(
29
+ /(?<platform>BB\d+;|Android|CrOS|Tizen|iPhone|iPad|iPod|Linux|(Open|Net|Free)BSD|Macintosh|Windows(\ Phone)?|Silk|linux-gnu|BlackBerry|PlayBook|X11|(New\ )?Nintendo\ (WiiU?|3?DS|Switch)|Xbox(\ One)?) (?:\ [^;]*)? (?:;|$)/imx
30
+ ).map(&:join)
31
+ result.uniq!
32
+ if result.length > 1
33
+ if (keys = priority & result).length > 0
34
+ platform = keys.first
35
+ else
36
+ platform = result[0]
37
+ end
38
+ elsif result
39
+ platform = result[0]
40
+ end
41
+ end
42
+
43
+ if platform == 'linux-gnu' || platform == 'X11'
44
+ platform = 'Linux'
45
+ elsif platform == 'CrOS'
46
+ platform = 'Chrome OS'
47
+ end
48
+
49
+ result = userAgent.to_enum(:scan, # ["browser" => ["Firefox"...], "version" => ["45.0"...]]
50
+ /(?<browser>Camino|Kindle(\ Fire)?|Firefox|Iceweasel|IceCat|Safari|MSIE|Trident|AppleWebKit|TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|Edge|Edg|CriOS|UCBrowser|Puffin|OculusBrowser|SamsungBrowser|Baiduspider|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl|Valve\ Steam\ Tenfoot|NintendoBrowser|PLAYSTATION\ (\d|Vita)+) (?:\)?;?) (?:(?:[:\/ ])(?<version>[0-9A-Z.]+)|\/(?:[A-Z]*))/ix
51
+ ).map { Regexp.last_match.names.collect{ |x| {x => $~[x]} }.reduce({}, :merge) }
52
+ .reduce({}) { |h,pairs| pairs.each {|k,v| (h[k] ||= []) << v}; h }
53
+
54
+ # If nothing matched, return nil (to avoid undefined index errors)
55
+ if !result['browser'] || !result['browser'][0] || !result['version'] || !result['version'][0]
56
+ result = userAgent.match(/^(?!Mozilla)(?<browser>[A-Z0-9\-]+)(\/(?<version>[0-9A-Z.]+))?/ix)
57
+ if result
58
+ return {
59
+ 'platform' => platform ? platform : nil,
60
+ 'browser' => result['browser'],
61
+ 'version' => result['version'] ? result['version'] : nil
62
+ }
63
+ end
64
+ return empty
65
+ end
66
+
67
+ rv_result = userAgent.match(/rv:(?<version>[0-9A-Z.]+)/i)
68
+ if rv_result && rv_result['version']
69
+ rv_result = rv_result['version']
70
+ end
71
+
72
+ browser = result['browser'][0]
73
+ version = result['version'][0]
74
+
75
+ lowerBrowser = result['browser'].map(&:downcase)
76
+ refkey = [0]
77
+ refbro = [browser]
78
+ refpla = [platform]
79
+ refval = ['']
80
+
81
+ if findt(lowerBrowser, {'OPR' => 'Opera', 'UCBrowser' => 'UC Browser', 'YaBrowser' => 'Yandex', 'Iceweasel' => 'Firefox', 'Icecat' => 'Firefox', 'CriOS' => 'Chrome', 'Edg' => 'Edge'}, refkey, refbro)
82
+ browser = refbro[0]
83
+ version = result['version'][refkey[0]]
84
+ elsif find(lowerBrowser, 'Playstation Vita', refkey, platform)
85
+ platform = 'PlayStation Vita'
86
+ browser = 'Browser'
87
+ elsif find(lowerBrowser, ['Kindle Fire', 'Silk'], refkey, refval)
88
+ browser = refval[0] == 'Silk' ? 'Silk' : 'Kindle'
89
+ platform = 'Kindle Fire'
90
+ if !(version = result['version'][refkey[0]]) || !(version[0] =~ /[0-9]/ ? true : false)
91
+ version = result['version'][result['browser'].index('Version')]
92
+ end
93
+ elsif platform == 'Nintendo 3DS' || find(lowerBrowser, 'NintendoBrowser', refkey)
94
+ browser = 'NintendoBrowser'
95
+ version = result['version'][refkey[0]]
96
+ elsif find(lowerBrowser, 'Kindle', refkey, refpla)
97
+ browser = result['browser'][refkey[0]]
98
+ version = result['version'][refkey[0]]
99
+ platform = refpla[0]
100
+ elsif find(lowerBrowser, 'Opera', refkey, refbro)
101
+ find(lowerBrowser, 'Version', refkey)
102
+ version = result['version'][refkey[0]]
103
+ browser = refbro[0]
104
+ elsif find(lowerBrowser, 'Puffin', refkey, refbro)
105
+ version = result['version'][refkey[0]]
106
+ browser = refbro[0]
107
+ if version.length > 3
108
+ part = version[-2..-1]
109
+ if part == part.upcase
110
+ version = version[0..-3]
111
+ flags = {'IP' => 'iPhone', 'IT' => 'iPad', 'AP' => 'Android', 'AT' => 'Android', 'WP' => 'Windows Phone', 'WT' => 'Windows'}
112
+ if flags[part] != nil
113
+ platform = flags[part]
114
+ end
115
+ end
116
+ end
117
+ elsif find(lowerBrowser, ['IEMobile', 'Edge', 'Midori', 'Vivaldi', 'OculusBrowser', 'SamsungBrowser', 'Valve Steam Tenfoot', 'Chrome', 'HeadlessChrome'], refkey, refbro)
118
+ version = result['version'][refkey[0]]
119
+ browser = refbro[0]
120
+ elsif rv_result && find(lowerBrowser, 'Trident')
121
+ browser = 'MSIE'
122
+ version = rv_result
123
+ elsif browser == 'AppleWebKit'
124
+ if platform == 'Android'
125
+ browser = 'Android Browser'
126
+ elsif platform && platform.index('BB') === 0
127
+ browser = 'BlackBerry Browser'
128
+ platform = 'BlackBerry'
129
+ elsif platform == 'BlackBerry' || platform == 'PlayBook'
130
+ browser = 'BlackBerry Browser'
131
+ elsif find(lowerBrowser, 'Safari', refkey, refbro) || find(lowerBrowser, 'TizenBrowser', refkey, refbro)
132
+ browser = refbro[0]
133
+ end
134
+ find(lowerBrowser, 'Version', refkey)
135
+ version = result['version'][refkey[0]]
136
+ else
137
+ pKey = result['browser'].grep(/playstation \d/i)
138
+ if pKey.length > 0
139
+ pKey = pKey.first
140
+ platform = 'PlayStation ' + pKey.gsub(/\D/, '')
141
+ browser = 'NetFront'
142
+ end
143
+ end
144
+
145
+ return {'platform' => platform ? platform : nil, 'browser' => browser ? browser : nil, 'version' => version ? version : nil}
146
+ end
147
+
148
+ def find(lowerBrowser, search, key=[], value=[])
149
+
150
+ search = Array(search)
151
+
152
+ search.each { |val|
153
+ xkey = lowerBrowser.index(val.downcase)
154
+ if xkey != nil
155
+ value[0] = val if value
156
+ key[0] = xkey
157
+ return true
158
+ end
159
+ }
160
+
161
+ return false
162
+ end
163
+
164
+ def findt(lowerBrowser, search, key=nil, value=nil)
165
+
166
+ refval = ['']
167
+
168
+ if find(lowerBrowser, search.keys, key, refval)
169
+ value[0] = search[refval[0]]
170
+ return true
171
+ end
172
+
173
+ return false
174
+ end
175
+ end
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf8 -*-
3
+ # Created J/26/12/2013
4
+ # Updated J/23/07/2020
5
+ #
6
+ # Copyright 2008-2020 | Fabrice Creuzot (luigifab) <code~luigifab~fr>
7
+ # https://www.luigifab.fr/redmine/apijs
8
+ #
9
+ # This program is free software, you can redistribute it or modify
10
+ # it under the terms of the GNU General Public License (GPL) as published
11
+ # by the free software foundation, either version 2 of the license, or
12
+ # (at your option) any later version.
13
+ #
14
+ # This program is distributed in the hope that it will be useful,
15
+ # but without any warranty, without even the implied warranty of
16
+ # merchantability or fitness for a particular purpose. See the
17
+ # GNU General Public License (GPL) for more details.
18
+
19
+ import os
20
+ import re
21
+ import sys
22
+ from PIL import Image
23
+
24
+ try:
25
+ filein = str(sys.argv[1])
26
+ fileout = str(sys.argv[2])
27
+ size = (int(sys.argv[3]), int(sys.argv[4]))
28
+ quality = int(sys.argv[5])
29
+ except:
30
+ print("Usage: video.py source destination width height [quality=0=auto]")
31
+ print("source: ogv,webm,mp4")
32
+ print("destination: jpg")
33
+ exit(-1)
34
+
35
+ if not os.path.exists(os.path.dirname(fileout)):
36
+ os.makedirs(os.path.dirname(fileout))
37
+
38
+ os.system('ffmpegthumbnailer -i ' + filein + ' -o ' + fileout + ' -q 10 -s ' + str(size[0]))
39
+ if os.path.isfile(fileout):
40
+ filein = fileout
41
+ source = Image.open(filein)
42
+ else:
43
+ source = Image.new('RGBA', size, (0,0,0,0))
44
+
45
+ # Standard resizing
46
+ source.thumbnail(size, Image.ANTIALIAS)
47
+ offset_x = int(max((size[0] - source.size[0]) / 2, 0))
48
+ offset_y = int(max((size[1] - source.size[1]) / 2, 0))
49
+
50
+ # Color detection
51
+ pixels = source.getcolors(size[0] * size[1])
52
+ pixels = sorted(pixels, key=lambda t: t[0])
53
+ if (pixels[-1][1] > (127,127,127)): # white background, black player
54
+ dest = Image.new('RGBA', size, (255,255,255,0))
55
+ dest.paste(source, (offset_x, offset_y))
56
+ # https://stackoverflow.com/a/59082116 (replace only last lib)
57
+ # /var/lib/gems/2.7.0/gems/redmine_apijs-6.3.0/lib » /var/lib/gems/2.7.0/gems/redmine_apijs-6.3.0/assets/images...
58
+ path = os.path.dirname(__file__)
59
+ path = ('/assets/images/apijs/player-black-' + str(size[0]) + '.png').join(path.rsplit('/lib', 1))
60
+ play = Image.open(path)
61
+ dest.paste(play, (0, 0), play)
62
+ else:
63
+ dest = Image.new('RGBA', size, (0,0,0,0))
64
+ dest.paste(source, (offset_x, offset_y))
65
+ # https://stackoverflow.com/a/59082116 (replace only last lib)
66
+ # /var/lib/gems/2.7.0/gems/redmine_apijs-6.3.0/lib » /var/lib/gems/2.7.0/gems/redmine_apijs-6.3.0/assets/images...
67
+ path = os.path.dirname(__file__)
68
+ path = ('/assets/images/apijs/player-white-' + str(size[0]) + '.png').join(path.rsplit('/lib', 1))
69
+ play = Image.open(path)
70
+ dest.paste(play, (0, 0), play)
71
+
72
+ # https://pillow.readthedocs.io/en/latest/handbook/image-file-formats.html
73
+ # The image quality, on a scale from 0 (worst) to 95 (best), the default is 75
74
+ if quality < 1:
75
+ quality = 75
76
+ elif quality > 95:
77
+ quality = 95
78
+ dest.convert('RGB').save(fileout, 'JPEG', optimize=True, quality=quality)
@@ -0,0 +1,69 @@
1
+ # coding: utf-8
2
+ Gem::Specification.new do |s|
3
+ s.name = 'redmine_apijs'
4
+ s.version = '6.3.0'
5
+ s.date = '2020-08-08'
6
+ s.summary = 'Redmine Apijs plugin'
7
+ s.description = 'Integrate the apijs javascript library into Redmine.'
8
+ s.homepage = 'https://github.com/luigifab/redmine-apijs'
9
+ s.license = 'GPL-2.0-or-later'
10
+ s.authors = ['Fabrice Creuzot']
11
+ s.email = 'code@luigifab.fr'
12
+ s.metadata = {
13
+ 'bug_tracker_uri' => 'https://github.com/luigifab/redmine-apijs/issues',
14
+ 'documentation_uri' => 'https://www.luigifab.fr/redmine/apijs',
15
+ 'homepage_uri' => 'https://www.redmine.org/plugins/apijs'
16
+ }
17
+ s.required_ruby_version = '>= 1.9'
18
+ s.files = %w[
19
+ app/controllers/apijs_controller.rb
20
+ app/views/application/_browser.html.erb
21
+ app/views/attachments/_links.html.erb
22
+ app/views/settings/_apijs.html.erb
23
+ assets/fonts/apijs/config.json
24
+ assets/fonts/apijs/fontello.woff
25
+ assets/fonts/apijs/fontello.woff2
26
+ assets/images/apijs/player-black-200.png
27
+ assets/images/apijs/player-black-400.png
28
+ assets/images/apijs/player-white-200.png
29
+ assets/images/apijs/player-white-400.png
30
+ assets/images/apijs/tv.gif
31
+ assets/javascripts/apijs-redmine.min.js
32
+ assets/javascripts/apijs.min.js
33
+ assets/javascripts/app.js
34
+ assets/stylesheets/apijs-print.min.css
35
+ assets/stylesheets/apijs-redmine-rtl.min.css
36
+ assets/stylesheets/apijs-redmine.min.css
37
+ assets/stylesheets/apijs-screen-rtl.min.css
38
+ assets/stylesheets/apijs-screen.min.css
39
+ assets/stylesheets/styles.css
40
+ config/locales/cs.yml
41
+ config/locales/de.yml
42
+ config/locales/en.yml
43
+ config/locales/es.yml
44
+ config/locales/fr.yml
45
+ config/locales/it.yml
46
+ config/locales/ja.yml
47
+ config/locales/nl.yml
48
+ config/locales/pl.yml
49
+ config/locales/pt-BR.yml
50
+ config/locales/pt.yml
51
+ config/locales/ru.yml
52
+ config/locales/sk.yml
53
+ config/locales/tr.yml
54
+ config/locales/zh.yml
55
+ config/routes.rb
56
+ Gemfile
57
+ init.rb
58
+ lib/apijs_attachment.rb
59
+ lib/apijs_const.rb
60
+ lib/apijs_files.rb
61
+ lib/image.py
62
+ lib/redmine_apijs.rb
63
+ lib/useragentparser.rb
64
+ lib/video.py
65
+ LICENSE
66
+ README
67
+ redmine_apijs.gemspec
68
+ ]
69
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redmine_apijs
3
+ version: !ruby/object:Gem::Version
4
+ version: 6.3.0
5
+ platform: ruby
6
+ authors:
7
+ - Fabrice Creuzot
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-08 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Integrate the apijs javascript library into Redmine.
14
+ email: code@luigifab.fr
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - Gemfile
20
+ - LICENSE
21
+ - README
22
+ - app/controllers/apijs_controller.rb
23
+ - app/views/application/_browser.html.erb
24
+ - app/views/attachments/_links.html.erb
25
+ - app/views/settings/_apijs.html.erb
26
+ - assets/fonts/apijs/config.json
27
+ - assets/fonts/apijs/fontello.woff
28
+ - assets/fonts/apijs/fontello.woff2
29
+ - assets/images/apijs/player-black-200.png
30
+ - assets/images/apijs/player-black-400.png
31
+ - assets/images/apijs/player-white-200.png
32
+ - assets/images/apijs/player-white-400.png
33
+ - assets/images/apijs/tv.gif
34
+ - assets/javascripts/apijs-redmine.min.js
35
+ - assets/javascripts/apijs.min.js
36
+ - assets/javascripts/app.js
37
+ - assets/stylesheets/apijs-print.min.css
38
+ - assets/stylesheets/apijs-redmine-rtl.min.css
39
+ - assets/stylesheets/apijs-redmine.min.css
40
+ - assets/stylesheets/apijs-screen-rtl.min.css
41
+ - assets/stylesheets/apijs-screen.min.css
42
+ - assets/stylesheets/styles.css
43
+ - config/locales/cs.yml
44
+ - config/locales/de.yml
45
+ - config/locales/en.yml
46
+ - config/locales/es.yml
47
+ - config/locales/fr.yml
48
+ - config/locales/it.yml
49
+ - config/locales/ja.yml
50
+ - config/locales/nl.yml
51
+ - config/locales/pl.yml
52
+ - config/locales/pt-BR.yml
53
+ - config/locales/pt.yml
54
+ - config/locales/ru.yml
55
+ - config/locales/sk.yml
56
+ - config/locales/tr.yml
57
+ - config/locales/zh.yml
58
+ - config/routes.rb
59
+ - init.rb
60
+ - lib/apijs_attachment.rb
61
+ - lib/apijs_const.rb
62
+ - lib/apijs_files.rb
63
+ - lib/image.py
64
+ - lib/redmine_apijs.rb
65
+ - lib/useragentparser.rb
66
+ - lib/video.py
67
+ - redmine_apijs.gemspec
68
+ homepage: https://github.com/luigifab/redmine-apijs
69
+ licenses:
70
+ - GPL-2.0-or-later
71
+ metadata:
72
+ bug_tracker_uri: https://github.com/luigifab/redmine-apijs/issues
73
+ documentation_uri: https://www.luigifab.fr/redmine/apijs
74
+ homepage_uri: https://www.redmine.org/plugins/apijs
75
+ post_install_message:
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '1.9'
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubygems_version: 3.1.2
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Redmine Apijs plugin
94
+ test_files: []