linkedin2cv 0.0.1 → 0.0.2
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 +4 -4
- data/.buildpacks +1 -1
- data/.gitignore +3 -2
- data/Procfile +1 -3
- data/README.md +2 -2
- data/app/routes/api.rb +27 -3
- data/app/routes/web.rb +6 -5
- data/app/views/home.erb +2 -2
- data/app/views/index.haml +1 -3
- data/bin/linkedin2cv +9 -0
- data/lib/linkedin2cv/converter.rb +25 -21
- data/lib/linkedin2cv/renderer/latex_renderer.rb +41 -33
- data/lib/linkedin2cv/version.rb +1 -1
- data/linkedin2cv.gemspec +1 -1
- data/public/app/index.html +3 -3
- data/public/app/scripts/app.js +2 -2
- data/public/app/scripts/controllers/main.js +1 -1
- data/public/app/scripts/services/linkedin2cv.js +2 -2
- data/public/app/styles/main.scss +1 -1
- data/public/app/views/main.html +1 -1
- data/public/bower.json +1 -1
- data/public/package.json +1 -1
- data/public/test/spec/controllers/main.js +1 -1
- data/public/test/spec/services/happyapi.js +6 -6
- data/public/test/spec/services/happyservice.js +1 -1
- data/spec/converter_spec.rb +0 -32
- data/spec/latex_renderer_spec.rb +32 -0
- data/spec/mocks/config.yml +5 -1
- data/spec/mocks/matt.latex +1424 -0
- data/spec/mocks/profile.json +1 -1
- data/templates/cv.erb +15 -4
- data/texlive.packages +17 -0
- metadata +10 -6
- data/app/views/hello.erb +0 -36
- data/test.rb +0 -109
- data/test.sh +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c48a59e31c94af34237c92f71a6872892289589c
|
4
|
+
data.tar.gz: 1dccf11adb9d8481312779c47dd0ecba6a919d46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a286e40bdea56ddde934ead557fc7eff9e36ec3d6986a7642edb9a1e7d4a6a8408246a0388a731977ddf8d4c778171078567b3b528b0915b7e6edc5c33b0a6f1
|
7
|
+
data.tar.gz: 335e4ba1a22918848c0327021cb39babe78b6819c9d796243884b5cba8420207ecfbbfc67e070098994cf64bd193ab1c8b516300cb2cdf93f180fd6ded3c6623
|
data/.buildpacks
CHANGED
@@ -1,2 +1,2 @@
|
|
1
1
|
https://github.com/heroku/heroku-buildpack-ruby.git
|
2
|
-
https://github.com/mefellows/heroku-buildpack-
|
2
|
+
https://github.com/mefellows/heroku-buildpack-tex.git
|
data/.gitignore
CHANGED
data/Procfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
LinkedIn 2 CV
|
2
2
|
==============
|
3
3
|
|
4
|
-
Turn your LinkedIn Profile into a professional resume in many formats (PDF / HTML5 /
|
4
|
+
Turn your LinkedIn Profile into a professional resume in many formats (PDF / HTML5 / LaTeX / Asciidoc)
|
5
5
|
|
6
6
|
## Motivations
|
7
7
|
|
@@ -11,7 +11,7 @@ Oh, and don't bother using the LinkedIn 'Export to PDF' feature - it sucks.
|
|
11
11
|
|
12
12
|
## Getting Started
|
13
13
|
|
14
|
-
First, install [MacTex](http://www.tug.org/mactex/) so that you can produce LaTeX documents. Then, simply run the converter:
|
14
|
+
First, install a Tex distribution ([MacTex](http://www.tug.org/mactex/) or [TexLive](http://www.tug.org/texlive/)) so that you can produce LaTeX documents. Then, simply run the converter:
|
15
15
|
|
16
16
|
gem install linkedin2resume
|
17
17
|
linkedin2cv convert --format latex --options config.yml matt.fellows
|
data/app/routes/api.rb
CHANGED
@@ -6,6 +6,7 @@ require 'sinatra/param'
|
|
6
6
|
require "sinatra/json"
|
7
7
|
require "json"
|
8
8
|
require "sinatra-websocket"
|
9
|
+
require "linkedin2cv/converter"
|
9
10
|
|
10
11
|
#
|
11
12
|
# Public: API for the Application
|
@@ -14,8 +15,16 @@ module Linkedin2CV
|
|
14
15
|
module Routes
|
15
16
|
class API < Sinatra::Application
|
16
17
|
helpers Sinatra::Param
|
18
|
+
use Rack::Logger
|
17
19
|
|
18
20
|
configure do
|
21
|
+
set :views, 'app/views'
|
22
|
+
set :public_folder, 'public/dist'
|
23
|
+
set :api, ENV['LINKEDIN_API_KEY']
|
24
|
+
set :secret, ENV["LINKEDIN_API_SECRET"]
|
25
|
+
set :logging, true
|
26
|
+
set :server, 'thin'
|
27
|
+
set :port, 5000
|
19
28
|
set :json_encoder, :to_json
|
20
29
|
set :sockets, []
|
21
30
|
end
|
@@ -36,8 +45,23 @@ module Linkedin2CV
|
|
36
45
|
# Public: Main API entry point to run the Linkedin2CV service
|
37
46
|
#
|
38
47
|
#
|
39
|
-
get '/api/
|
48
|
+
get '/api/cv/:name' do
|
40
49
|
|
50
|
+
param :format, String, default: 'latex'
|
51
|
+
param :name, String, required: true
|
52
|
+
param :access_token, String, required: true
|
53
|
+
|
54
|
+
logger.debug("Request received to create a CV. Format: #{params[:format]}, Name: #{params[:name]}, Token: #{params[:access_token]}")
|
55
|
+
|
56
|
+
options = {}
|
57
|
+
options['output_file'] = "/tmp/#{params[:name]}"
|
58
|
+
options['format'] = params[:format]
|
59
|
+
|
60
|
+
converter = Linkedin2CV::Converter.new(params[:access_token])
|
61
|
+
converter.create_resume(options)
|
62
|
+
|
63
|
+
content_type "application/pdf"
|
64
|
+
File.read("#{options['output_file']}.pdf")
|
41
65
|
end
|
42
66
|
|
43
67
|
|
@@ -90,9 +114,9 @@ module Linkedin2CV
|
|
90
114
|
# Override this for custom behaviour.
|
91
115
|
#
|
92
116
|
def checkPong(msg)
|
93
|
-
|
117
|
+
logger.debug("Checking ping pong for a ping: " + msg['message'])
|
94
118
|
if (msg['message'] == 'ping')
|
95
|
-
|
119
|
+
logger.debug("Ping hit!")
|
96
120
|
response_obj = {'message' => 'pong'}
|
97
121
|
end
|
98
122
|
end
|
data/app/routes/web.rb
CHANGED
@@ -99,12 +99,13 @@ module Linkedin2CV
|
|
99
99
|
token = client.request_access_token(code, redirect_uri: "http://#{request.host}:#{request.port}/auth/callback")
|
100
100
|
session[:atoken] = token.token
|
101
101
|
|
102
|
-
#
|
103
|
-
file = File.new('.token', 'w')
|
104
|
-
file.write(token.token)
|
105
|
-
file.close
|
106
|
-
|
102
|
+
# CLI Only behaviour
|
107
103
|
if !ENV['CLI_ONLY'].nil?
|
104
|
+
# Store in env for command line!
|
105
|
+
file = File.new('.token', 'w')
|
106
|
+
file.write(token.token)
|
107
|
+
file.close
|
108
|
+
|
108
109
|
logger.info "Got access token, shutting down!: #{session[:atoken]}"
|
109
110
|
Thread.current.thread_variable_set('access_token', token.token)
|
110
111
|
Thread.kill(Thread.current)
|
data/app/views/home.erb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
<!doctype html><!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--><!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--><!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--><!--[if gt IE 8]><!--><html class="no-js"><!--<![endif]--><head><meta charset="utf-8"><title>Linkedin2CV Generator | Generate a Linkedin2CV for your website</title><meta name="description" content=""><meta name="viewport" content="width=device-width"><!-- Place favicon.ico and apple-touch-icon.png in the root directory --><link rel="stylesheet" href="styles/vendor.css"><link rel="stylesheet" href="styles/f24dfdad.main.css"><body ng-app="
|
2
|
-
<p class="browselinkedin2cv
|
1
|
+
<!doctype html><!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]--><!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]--><!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]--><!--[if gt IE 8]><!--><html class="no-js"><!--<![endif]--><head><meta charset="utf-8"><title>Linkedin2CV Generator | Generate a Linkedin2CV for your website</title><meta name="description" content=""><meta name="viewport" content="width=device-width"><!-- Place favicon.ico and apple-touch-icon.png in the root directory --><link rel="stylesheet" href="styles/vendor.css"><link rel="stylesheet" href="styles/f24dfdad.main.css"><body ng-app="linkedin2cvGeneratorApp"><!--[if lt IE 7]>
|
2
|
+
<p class="browselinkedin2cv">You are using an <strong>outdated</strong> browser. Please <a href="http://browselinkedin2cv.com/">upgrade your browser</a> to improve your experience.</p>
|
3
3
|
<![endif]--><!-- Add your site or application content here --><div class="container" ng-view=""></div><!-- Google Analytics: change UA-XXXXX-X to be your site's ID --><script>(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
|
4
4
|
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
|
5
5
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
|
data/app/views/index.haml
CHANGED
@@ -2,10 +2,8 @@
|
|
2
2
|
%a{:href => "/auth/logout"} Logout
|
3
3
|
%p= profile.headline
|
4
4
|
%br
|
5
|
+
%a{:href => "/api/cv/#{session[:atoken]}?access_token=#{session[:atoken]}"} Download CV
|
5
6
|
%div= "Your token is #{session[:atoken]}"
|
6
7
|
|
7
8
|
-else
|
8
9
|
%a{:href => "/auth"} Login using LinkedIn
|
9
|
-
|
10
|
-
-if session[:authorize_url]
|
11
|
-
%p="yo, auth url #{session[:authorize_url]}"
|
data/bin/linkedin2cv
ADDED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'linkedin-oauth2'
|
2
|
+
require 'linkedin2cv/logging'
|
2
3
|
require 'asciidoctor'
|
3
4
|
require 'launchy'
|
4
5
|
require 'tempfile'
|
@@ -13,17 +14,20 @@ module Linkedin2CV
|
|
13
14
|
class Converter
|
14
15
|
include Logging
|
15
16
|
|
16
|
-
TOKEN_WAIT_TIME = 15
|
17
17
|
API_KEY = ENV['LINKEDIN_API_KEY']
|
18
18
|
API_SECRET = ENV["LINKEDIN_API_SECRET"]
|
19
19
|
|
20
|
-
def initialize
|
20
|
+
def initialize(token = nil)
|
21
21
|
@display_fields = ["company", "publication", "patent", "language", "skills", "certification", "education", "course", "volunteer", "recommendations"]
|
22
22
|
@profile_fields = ["projects:(start-date,end-date,description,id,name,url),main-address,phone-numbers,email-address,first-name,last-name,maiden-name,formatted-name,phonetic-first-name,phonetic-last-name,formatted-phonetic-name,headline,location:(name,country:(code)),industry,current-status,current-share,num-connections,num-connections-capped,summary,specialties,positions,picture-url,api-standard-profile-request:(url,headers),public-profile-url,last-modified-timestamp,proposal-comments,associations,interests,publications:(id,title,publisher:(name),authors:(id,name,person),date,url,summary),patents,languages:(id,language:(name),proficiency:(level,name)),skills:(id,skill:(name)),certifications:(id,name,authority:(name),number,start-date,end-date),educations:(id,school-name,field-of-study,start-date,end-date,degree,activities,notes),courses:(id,name,number),three-current-positions:(id,title,summary,start-date,end-date,is-current,company),three-past-positions:(id,title,summary,start-date,end-date,is-current,company),num-recommenders,recommendations-received:(id,recommendation-type,recommendation-text,recommender),mfeed-rss-url,following,job-bookmarks,suggestions,date-of-birth,member-url-resources:(url,name),related-profile-views,honors-awards"]
|
23
|
+
@token = token
|
23
24
|
|
24
25
|
# Confirm API keys setup
|
26
|
+
raise StandardError, "API_KEY not set" unless !API_KEY.nil?
|
27
|
+
raise StandardError, "API_SECRET not set" unless !API_SECRET.nil?
|
25
28
|
|
26
29
|
# Setup default options to be merged with supplied ones
|
30
|
+
|
27
31
|
end
|
28
32
|
|
29
33
|
# Public: Create a resume given a LinkedIn Profile
|
@@ -42,25 +46,33 @@ module Linkedin2CV
|
|
42
46
|
|
43
47
|
# Currently only supports one LaTeX template
|
44
48
|
renderer = Linkedin2CV::LatexRenderer.new
|
45
|
-
renderer.
|
49
|
+
renderer.render(profile, options)
|
46
50
|
else
|
47
51
|
puts "Sorry mate, I don't yet support the '#{options['format']}' format'"
|
48
52
|
end
|
49
53
|
end
|
50
54
|
|
55
|
+
# Public: Authenticate & Authorize the end user (via OAuth2) and fetch access token
|
56
|
+
#
|
57
|
+
#
|
51
58
|
def auth
|
52
|
-
|
53
|
-
|
54
|
-
|
59
|
+
if @token.nil?
|
60
|
+
# If in CLI Mode, spawn a thread, run the API and open a browser window
|
61
|
+
|
62
|
+
thread = nil
|
63
|
+
if !File.exists?('.token')
|
64
|
+
auth_thread = prompt_user_authorisation
|
55
65
|
|
56
|
-
|
57
|
-
|
66
|
+
puts "Waiting to retrieve LinkedIn OAuth token"
|
67
|
+
sleep 1 until auth_thread.thread_variable?('access_token')
|
68
|
+
end
|
69
|
+
|
70
|
+
# Get Token from environment
|
71
|
+
@token = fetch_token_from_tempfile
|
72
|
+
puts "I have a token for you: #{@token}"
|
58
73
|
end
|
59
74
|
|
60
|
-
|
61
|
-
token = fetch_token_from_tempfile
|
62
|
-
puts "I have a token for you: #{token}"
|
63
|
-
token
|
75
|
+
@token
|
64
76
|
end
|
65
77
|
|
66
78
|
# Public: Fetch Profile Data
|
@@ -73,15 +85,7 @@ module Linkedin2CV
|
|
73
85
|
|
74
86
|
# Get Client info based on fields provided
|
75
87
|
client = LinkedIn::Client.new(API_KEY, API_SECRET, token)
|
76
|
-
|
77
|
-
|
78
|
-
end
|
79
|
-
|
80
|
-
# Public: Fetches a template for rendering via Asciidoctor
|
81
|
-
#
|
82
|
-
#
|
83
|
-
def lookup_template
|
84
|
-
|
88
|
+
client.profile(:fields => @profile_fields)
|
85
89
|
end
|
86
90
|
|
87
91
|
# Public: Invoke the API and browser session for end user authentication.
|
@@ -1,52 +1,35 @@
|
|
1
1
|
module Linkedin2CV
|
2
2
|
class LatexRenderer
|
3
3
|
|
4
|
-
# Public:
|
4
|
+
# Public: Render a LaTeX document
|
5
5
|
#
|
6
6
|
#
|
7
|
-
def
|
8
|
-
|
9
|
-
def clean_latex(s)
|
10
|
-
# Clean &
|
11
|
-
s = s.gsub(/(?<!\\)\&(?!\\)/, '\\\&')
|
12
|
-
|
13
|
-
# Clean $
|
14
|
-
s = s.gsub(/(?<!\\)\$(?!\\)/, '\\\$')
|
15
|
-
|
16
|
-
# Clean %
|
17
|
-
s = s.gsub(/(?<!\\)%(?!\\)/, '\\\%')
|
18
|
-
|
19
|
-
# # Clean ~
|
20
|
-
s = s.gsub(/\~/, '\\\textasciitilde')
|
21
|
-
|
22
|
-
# # Clean >
|
23
|
-
s = s.gsub(/\>/, '\\\textgreater')
|
24
|
-
|
25
|
-
# # Clean <
|
26
|
-
s = s.gsub(/\</, '\\\textless')
|
27
|
-
|
28
|
-
s
|
29
|
-
end
|
30
|
-
|
31
|
-
require 'tilt/erb'
|
7
|
+
def render(profile, options)
|
8
|
+
output = render_latex(profile, options)
|
32
9
|
output_filename = "#{options['output_file']}.latex"
|
33
|
-
|
34
|
-
|
35
|
-
output = template.render(self, :profile => profile, :options => options)
|
10
|
+
render_pdf(output, output_filename)
|
11
|
+
end
|
36
12
|
|
13
|
+
# Public: Produce a Latex PDF
|
14
|
+
#
|
15
|
+
#
|
16
|
+
def render_pdf(output, output_filename)
|
37
17
|
output_file = File.new(output_filename, 'w')
|
38
18
|
output_file.write(output)
|
39
19
|
output_file.close
|
40
20
|
|
41
21
|
# Make sure this variable is escaped, clearly.....
|
42
|
-
|
22
|
+
system("pdflatex -output-directory=/tmp/ #{output_filename}")
|
43
23
|
end
|
44
24
|
|
45
|
-
#
|
25
|
+
# Convert a LinkedIn Profile into a LaTeX source document.
|
46
26
|
#
|
47
27
|
#
|
48
|
-
def
|
49
|
-
|
28
|
+
def render_latex(profile, options)
|
29
|
+
require 'tilt/erb'
|
30
|
+
template = Tilt.new('templates/cv.erb')
|
31
|
+
output = template.render(self, :profile => profile, :options => options)
|
32
|
+
output
|
50
33
|
end
|
51
34
|
end
|
52
35
|
end
|
@@ -62,3 +45,28 @@ class ERB::Compiler
|
|
62
45
|
end
|
63
46
|
|
64
47
|
end
|
48
|
+
|
49
|
+
# Public: Prepare a string for LaTeX rendering.
|
50
|
+
#
|
51
|
+
# Escapes special chars, replaces etc.
|
52
|
+
def clean_latex(s)
|
53
|
+
# Clean &
|
54
|
+
s = s.gsub(/(?<!\\)\&(?!\\)/, '\\\&')
|
55
|
+
|
56
|
+
# Clean $
|
57
|
+
s = s.gsub(/(?<!\\)\$(?!\\)/, '\\\$')
|
58
|
+
|
59
|
+
# Clean %
|
60
|
+
s = s.gsub(/(?<!\\)%(?!\\)/, '\\\%')
|
61
|
+
|
62
|
+
# # Clean ~
|
63
|
+
s = s.gsub(/\~/, '\\\textasciitilde')
|
64
|
+
|
65
|
+
# # Clean >
|
66
|
+
s = s.gsub(/\>/, '\\\textgreater')
|
67
|
+
|
68
|
+
# # Clean <
|
69
|
+
s = s.gsub(/\</, '\\\textless')
|
70
|
+
|
71
|
+
s
|
72
|
+
end
|
data/lib/linkedin2cv/version.rb
CHANGED
data/linkedin2cv.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.email = ["matt.fellows@onegeek.com.au"]
|
11
11
|
spec.summary = "Linkedin2CV"
|
12
12
|
spec.description = "Turn your LinkedIn Profile into a professional resume in many formats (PDF / HTML5 / LaTeX / Asciidoc)"
|
13
|
-
spec.homepage = "
|
13
|
+
spec.homepage = "http://linkedin2cv.herokuapp.com"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
16
|
spec.files = `git ls-files`.split($/)
|
data/public/app/index.html
CHANGED
@@ -17,9 +17,9 @@
|
|
17
17
|
<link rel="stylesheet" href="styles/main.css">
|
18
18
|
<!-- endbuild -->
|
19
19
|
</head>
|
20
|
-
<body ng-app="
|
20
|
+
<body ng-app="linkedin2cvApp">
|
21
21
|
<!--[if lt IE 7]>
|
22
|
-
<p class="browselinkedin2cv
|
22
|
+
<p class="browselinkedin2cv">You are using an <strong>outdated</strong> browser. Please <a href="http://browselinkedin2cv.com/">upgrade your browser</a> to improve your experience.</p>
|
23
23
|
<![endif]-->
|
24
24
|
|
25
25
|
<!-- Add your site or application content here -->
|
@@ -67,7 +67,7 @@
|
|
67
67
|
<!-- build:js({.tmp,app}) scripts/scripts.js -->
|
68
68
|
<script src="scripts/app.js"></script>
|
69
69
|
<script src="scripts/controllers/main.js"></script>
|
70
|
-
<script src="scripts/services/
|
70
|
+
<script src="scripts/services/linkedin2cvapi.js"></script>
|
71
71
|
<!-- endbuild -->
|
72
72
|
</body>
|
73
73
|
</html>
|
data/public/app/scripts/app.js
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
'use strict';
|
2
2
|
|
3
3
|
angular
|
4
|
-
.module('
|
4
|
+
.module('linkedin2cvApp', [
|
5
5
|
'ngCookies',
|
6
6
|
'ngResource',
|
7
7
|
'ngSanitize',
|
8
8
|
'ngRoute',
|
9
|
-
'
|
9
|
+
'linkedin2cvServices'
|
10
10
|
])
|
11
11
|
.config(function ($routeProvider) {
|
12
12
|
$routeProvider
|
@@ -1,8 +1,8 @@
|
|
1
1
|
'use strict';
|
2
2
|
|
3
|
-
var
|
3
|
+
var linkedin2cvServices = angular.module('linkedin2cvServices', ['ngResource']);
|
4
4
|
|
5
|
-
|
5
|
+
linkedin2cvServices.factory('Linkedin2CV', ['$q', '$rootScope', '$interval', function($q, $rootScope, $interval) {
|
6
6
|
// We return this object to anything injecting our service
|
7
7
|
var Service = {};
|
8
8
|
|
data/public/app/styles/main.scss
CHANGED
@@ -6,7 +6,7 @@ $icon-font-path: "/bower_components/bootstrap-sass-official/vendor/assets/fonts/
|
|
6
6
|
@import "../bower_components/bootstrap-sass-official/vendor/assets/stylesheets/bootstrap.scss";
|
7
7
|
// endbower
|
8
8
|
|
9
|
-
.browselinkedin2cv
|
9
|
+
.browselinkedin2cv {
|
10
10
|
margin: 0.2em 0;
|
11
11
|
background: #ccc;
|
12
12
|
color: #000;
|
data/public/app/views/main.html
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
<div class="header">
|
2
2
|
<ul class="nav nav-pills pull-right">
|
3
3
|
<li class="active"><a ng-href="#">Home</a></li>
|
4
|
-
<li><a ng-href="http://github.com/mefellows/linkedin2cv
|
4
|
+
<li><a ng-href="http://github.com/mefellows/linkedin2cv-generator">About</a></li>
|
5
5
|
</ul>
|
6
6
|
<h1 class="text-muted">Linkedin2CV App</h1>
|
7
7
|
</div>
|
data/public/bower.json
CHANGED