jekyll-theme-satellite 1.0.0 → 1.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.
data/assets/js/post.js ADDED
@@ -0,0 +1,197 @@
1
+ document.addEventListener('DOMContentLoaded', function(){
2
+ var content = document.querySelector('main');
3
+ let currentTheme = localStorage.getItem('theme');
4
+
5
+ // Lazy image loading
6
+ var images = content.querySelectorAll('img');
7
+
8
+ images.forEach((img) => {
9
+ img.setAttribute('loading', 'lazy');
10
+ });
11
+
12
+ // tocbot
13
+ var headings = content.querySelectorAll('h1, h2');
14
+ var headingMap = {};
15
+
16
+ Array.prototype.forEach.call(headings, function (heading) {
17
+ var id = heading.id ? heading.id : heading.textContent.trim().toLowerCase()
18
+ .split(' ').join('-').replace(/[\!\@\#\$\%\^\&\*\(\):]/ig, '');
19
+
20
+ headingMap[id] = !isNaN(headingMap[id]) ? ++headingMap[id] : 0;
21
+
22
+ if (headingMap[id]) {
23
+ heading.id = id + '-' + headingMap[id];
24
+ } else {
25
+ heading.id = id;
26
+ }
27
+ })
28
+
29
+ tocbot.init({
30
+ tocSelector: '.toc-board',
31
+ contentSelector: '.inner-content',
32
+ headingSelector:'h1, h2',
33
+ hasInnerContainers: false
34
+ });
35
+
36
+ // link (for hover effect)
37
+ var links = content.querySelectorAll('a:not(.related-item a)');
38
+
39
+ links.forEach((link) => {
40
+ link.setAttribute('data-content', link.innerText);
41
+ });
42
+
43
+ // code clipboard copy button
44
+ async function copyCode(block) {
45
+ let code = block.querySelector("code");
46
+ let text = code.innerText;
47
+
48
+ await navigator.clipboard.writeText(text);
49
+ }
50
+
51
+ let blocks = document.querySelectorAll("pre");
52
+
53
+ blocks.forEach((block) => {
54
+ // only add button if browser supports Clipboard API
55
+ if (navigator.clipboard) {
56
+ let clip_btn = document.createElement("button");
57
+ let clip_img = document.createElement("svg");
58
+
59
+ clip_btn.setAttribute('title', "Copy Code");
60
+ clip_img.ariaHidden = true;
61
+
62
+ block.appendChild(clip_btn);
63
+ clip_btn.appendChild(clip_img);
64
+
65
+ clip_btn.addEventListener("click", async () => {
66
+ await copyCode(block, clip_btn);
67
+ });
68
+ }
69
+ });
70
+
71
+ // Initialize/Change Giscus theme
72
+ var giscusTheme = "light";
73
+
74
+ const giscus_repo = $('meta[name="giscus_repo"]').attr("content");
75
+ const giscus_repoId = $('meta[name="giscus_repoId"]').attr("content");
76
+ const giscus_category = $('meta[name="giscus_category"]').attr("content");
77
+ const giscus_categoryId = $('meta[name="giscus_categoryId"]').attr("content");
78
+
79
+ console.log(giscus_repo);
80
+
81
+ if (giscus_repo !== undefined) {
82
+ if (currentTheme === 'dark'){
83
+ giscusTheme = "noborder_gray";
84
+ }
85
+
86
+ console.log("what?");
87
+
88
+ let giscusAttributes = {
89
+ "src": "https://giscus.app/client.js",
90
+ "data-repo": giscus_repo,
91
+ "data-repo-id": giscus_repoId,
92
+ "data-category": giscus_category,
93
+ "data-category-id": giscus_categoryId,
94
+ "data-mapping": "pathname",
95
+ "data-reactions-enabled": "1",
96
+ "data-emit-metadata": "1",
97
+ "data-theme": giscusTheme,
98
+ "data-lang": "en",
99
+ "crossorigin": "anonymous",
100
+ "async": "",
101
+ };
102
+
103
+ let giscusScript = document.createElement("script");
104
+ Object.entries(giscusAttributes).forEach(([key, value]) => giscusScript.setAttribute(key, value));
105
+ document.body.appendChild(giscusScript);
106
+
107
+ console.log("what??");
108
+ }
109
+
110
+ // Giscus IMetadataMessage event handler
111
+ function handleMessage(event) {
112
+ if (event.origin !== 'https://giscus.app') return;
113
+ if (!(typeof event.data === 'object' && event.data.giscus)) return;
114
+
115
+ const giscusData = event.data.giscus;
116
+
117
+ if (giscusData && giscusData.hasOwnProperty('discussion')) {
118
+ $('#num-comments').text(giscusData.discussion.totalCommentCount);
119
+ }
120
+ else {
121
+ $('#num-comments').text('0');
122
+ }
123
+ }
124
+
125
+ window.addEventListener('message', handleMessage);
126
+
127
+ // Tag EventListener
128
+ const searchPage = document.querySelector("#search");
129
+
130
+ document.querySelectorAll('.tag-box').forEach(function(tagButton){
131
+ tagButton.addEventListener('click', function() {
132
+ const contentID = tagButton.getAttribute('contentID');
133
+ searchPage.classList.add('active');
134
+
135
+ $('#search-input').val(contentID);
136
+ $('#search-input').trigger('keyup');
137
+ });
138
+ });
139
+
140
+ // Page Hits
141
+ const pageHits = document.getElementById('page-hits');
142
+
143
+ if (pageHits) {
144
+ const goatcounterCode = pageHits.getAttribute('usercode');
145
+ const requestURL = 'https://'
146
+ + goatcounterCode
147
+ + '.goatcounter.com/counter/'
148
+ + encodeURIComponent(location.pathname)
149
+ + '.json';
150
+
151
+ var resp = new XMLHttpRequest();
152
+ resp.open('GET', requestURL);
153
+ resp.onerror = function() { pageHits.innerText = "0"; };
154
+ resp.onload = function() { pageHits.innerText = JSON.parse(this.responseText).count; };
155
+ resp.send();
156
+ }
157
+
158
+ // Sweat Scroll
159
+ const scroller = new SweetScroll({
160
+ /* some options */
161
+ });
162
+
163
+ // Move to Top
164
+ if (document.querySelector('.thumbnail')){
165
+ const arrowButton = document.querySelector('.top-arrow');
166
+
167
+ setInterval(function(){
168
+ var scrollPos = document.documentElement.scrollTop;
169
+
170
+ if (scrollPos < 512){
171
+ arrowButton.classList.remove('arrow-open');
172
+ }
173
+ else {
174
+ arrowButton.classList.add('arrow-open');
175
+ }
176
+ }, 1000);
177
+ }
178
+
179
+ // Code highlighter
180
+ if (currentTheme === 'dark'){
181
+ // Disable highlighter default color theme
182
+ document.getElementById("highlight-default").disabled=true;
183
+ }
184
+ else {
185
+ // Disable highlighter dark color theme
186
+ document.getElementById("highlight-dark").disabled=true;
187
+ }
188
+
189
+ hljs.highlightAll();
190
+
191
+ // Disable code highlights to the plaintext codeblocks
192
+ document.querySelectorAll('.language-text, .language-plaintext').forEach(function(codeblock){
193
+ codeblock.querySelectorAll('.hljs-keyword, .hljs-meta, .hljs-selector-tag').forEach(function($){
194
+ $.outerHTML = $.innerHTML;
195
+ });
196
+ });
197
+ });
@@ -0,0 +1,168 @@
1
+ function searchPost(pages){
2
+ $('#search-input').on('keyup', function () {
3
+ var keyword = this.value.toLowerCase();
4
+ var searchResult = [];
5
+
6
+ if (keyword.length > 0) {
7
+ $('#search-result').show();
8
+ $('#btn-clear').show();
9
+ } else {
10
+ $('#search-result').hide();
11
+ $('#btn-clear').hide();
12
+ }
13
+
14
+ $('.result-item').remove();
15
+
16
+ for (var i = 0; i < pages.length; i++) {
17
+ var post = pages[i];
18
+
19
+ if (post.title === 'Home' && post.type == 'category') continue;
20
+
21
+ if (post.title.toLowerCase().indexOf(keyword) >= 0
22
+ || post.path.toLowerCase().indexOf(keyword) >= 0
23
+ || post.tags.toLowerCase().indexOf(keyword) >= 0){
24
+ searchResult.push(post);
25
+ }
26
+ }
27
+
28
+ if (searchResult.length === 0) {
29
+ $('#search-result').append(
30
+ '<li class="result-item"><span class="description">There is no search result.</span></li>'
31
+ );
32
+
33
+ return;
34
+ }
35
+
36
+ searchResult.sort(function (a, b) {
37
+ if (a.type == 'category') return 1;
38
+
39
+ return -1;
40
+ });
41
+
42
+ for (var i = 0; i < searchResult.length; i++) {
43
+ var highlighted_path = highlightKeyword(searchResult[i].path, keyword);
44
+
45
+ if (highlighted_path === '')
46
+ highlighted_path = "Home";
47
+
48
+ if (searchResult[i].type === 'post'){
49
+ var highlighted_title = highlightKeyword(searchResult[i].title, keyword);
50
+ var highlighted_tags = highlightKeyword(searchResult[i].tags, keyword);
51
+
52
+ if (highlighted_tags === '')
53
+ highlighted_tags = "none";
54
+
55
+ $('#search-result').append(
56
+ '<li class="result-item"><a href="' +
57
+ searchResult[i].url +
58
+ '"><table><thead><tr><th><svg class="ico-book"></svg></th><th>' + highlighted_title +
59
+ '</th></tr></thead><tbody><tr><td><svg class="ico-folder"></svg></td><td>' + highlighted_path +
60
+ '</td></tr><tr><td><svg class="ico-tags"></svg></td><td>' + highlighted_tags +
61
+ '</td></tr><tr><td><svg class="ico-calendar"></svg></td><td>' + searchResult[i].date +
62
+ '</td></tr></tbody></table></a></li>'
63
+ );
64
+ }
65
+ else {
66
+ $('#search-result').append(
67
+ '<li class="result-item"><a href="' +
68
+ searchResult[i].url +
69
+ '"><table><thead><tr><th><svg class="ico-folder"></svg></th><th>' + highlighted_path +
70
+ '</th></tr></thead></table></a></li>'
71
+ );
72
+ }
73
+ }
74
+ });
75
+
76
+ function highlightKeyword(txt, keyword) {
77
+ var index = txt.toLowerCase().lastIndexOf(keyword);
78
+
79
+ if (index >= 0) {
80
+ out = txt.substring(0, index) +
81
+ "<span class='highlight'>" +
82
+ txt.substring(index, index+keyword.length) +
83
+ "</span>" +
84
+ txt.substring(index + keyword.length);
85
+ return out;
86
+ }
87
+
88
+ return txt;
89
+ }
90
+ }
91
+
92
+ function searchRelated(pages){
93
+ const refBox = document.getElementById('related-box');
94
+
95
+ if (!refBox) return;
96
+
97
+ var relatedPosts = [];
98
+ var currPost = pages.find(obj => {return obj.url === location.pathname});
99
+
100
+ let currTags = currPost.tags.split(', ');
101
+ let currCategory = currPost.path.split(' > ').pop();
102
+
103
+ for (var i = 0; i < pages.length; i++) {
104
+ let page = pages[i];
105
+
106
+ if (page.type === 'category') continue;
107
+
108
+ if (page.title === currPost.title) continue;
109
+
110
+ let tags = page.tags.split(', ');
111
+ let category = page.path.split(' > ').pop();
112
+ let correlationScore = 0;
113
+
114
+ for (var j = 0; j < currTags.length; j++){
115
+ if (tags.indexOf(currTags[j]) != -1) correlationScore += 1;
116
+ }
117
+
118
+ if (category === currCategory) correlationScore += 1;
119
+
120
+ if (correlationScore == 0) continue;
121
+
122
+ relatedPosts.push({
123
+ 'title': page.title,
124
+ 'date': page.date,
125
+ 'category': category,
126
+ 'url': page.url,
127
+ 'thumbnail': page.image,
128
+ 'score': correlationScore
129
+ });
130
+ }
131
+
132
+ relatedPosts.sort(function (a, b) {
133
+ if(a.hasOwnProperty('score')){
134
+ return b.score - a.score;
135
+ }
136
+ });
137
+
138
+ if (relatedPosts.length == 0){
139
+ $('#related-box').hide();
140
+ return;
141
+ }
142
+
143
+ for (var i = 0; i < Math.min(relatedPosts.length, 6); i++){
144
+ let post = relatedPosts[i];
145
+ let date = '-';
146
+ let category = 'No category';
147
+
148
+ if (post.date !== '1900-01-01'){
149
+ date = new Date(post.date);
150
+ date = date.toLocaleString('en-US', {day: 'numeric', month:'long', year:'numeric'});
151
+ }
152
+
153
+ if (post.category !== '') category = post.category;
154
+
155
+ if (post.thumbnail === ''){
156
+ post.thumbnail = "/assets/img/thumbnail/empty.jpg";
157
+ }
158
+
159
+ $('#related-posts').append(
160
+ '<li class="related-item"><a href="' + post.url +
161
+ '"><img src="' + post.thumbnail +
162
+ '"/><p class="category">' + category +
163
+ '</p><p class="title">' + post.title +
164
+ '</p><p class="date">' + date +
165
+ '</p></a></li>'
166
+ );
167
+ }
168
+ }
@@ -0,0 +1,132 @@
1
+ document.addEventListener('DOMContentLoaded', function(){
2
+ // Loading page
3
+ window.addEventListener("load", () => {
4
+ var load_div = document.querySelector('#loading');
5
+
6
+ if(load_div != null){
7
+ setTimeout(function(){
8
+ load_div.style.transition = '.75s';
9
+ load_div.style.opacity = '0';
10
+ load_div.style.visibility = 'hidden';
11
+ }, 800);
12
+ }
13
+ });
14
+
15
+ // pagination
16
+ const paginationNumbers = document.querySelector("#pagination-numbers");
17
+ const paginatedList = document.querySelector(".paginated-list");
18
+
19
+ if (paginatedList) {
20
+ const listItems = paginatedList.querySelectorAll("li");
21
+ const nextButton = document.querySelector("#next-button");
22
+ const prevButton = document.querySelector("#prev-button");
23
+ const pageKey = "pageKey=" + document.URL;
24
+
25
+ const paginationLimit = 5;
26
+ const pageCount = Math.ceil(listItems.length / paginationLimit);
27
+ let currentPage = 1;
28
+
29
+ const disableButton = (button) => {
30
+ button.classList.add("disabled");
31
+ button.setAttribute("disabled", true);
32
+ };
33
+
34
+ const enableButton = (button) => {
35
+ button.classList.remove("disabled");
36
+ button.removeAttribute("disabled");
37
+ };
38
+
39
+ const handlePageButtonsStatus = () => {
40
+ if (currentPage === 1) {
41
+ disableButton(prevButton);
42
+ } else {
43
+ enableButton(prevButton);
44
+ }
45
+
46
+ if (pageCount === currentPage) {
47
+ disableButton(nextButton);
48
+ } else {
49
+ enableButton(nextButton);
50
+ }
51
+ };
52
+
53
+ const handleActivePageNumber = () => {
54
+ document.querySelectorAll(".pagination-number").forEach((button) => {
55
+ button.classList.remove("active");
56
+
57
+ const pageIndex = Number(button.getAttribute("page-index"));
58
+
59
+ if (pageIndex == currentPage) {
60
+ button.classList.add("active");
61
+ }
62
+ });
63
+ };
64
+
65
+ const appendPageNumber = (index) => {
66
+ const pageNumber = document.createElement("button");
67
+
68
+ pageNumber.className = "pagination-number";
69
+ pageNumber.innerHTML = index;
70
+ pageNumber.setAttribute("page-index", index);
71
+ pageNumber.setAttribute("aria-label", "Page " + index);
72
+
73
+ paginationNumbers.appendChild(pageNumber);
74
+ };
75
+
76
+ const getPaginationNumbers = () => {
77
+ for (let i = 1; i <= pageCount; i++) {
78
+ appendPageNumber(i);
79
+ }
80
+ };
81
+
82
+ const setCurrentPage = (pageNum) => {
83
+ currentPage = pageNum;
84
+
85
+ handleActivePageNumber();
86
+ handlePageButtonsStatus();
87
+
88
+ const prevRange = (pageNum - 1) * paginationLimit;
89
+ const currRange = pageNum * paginationLimit;
90
+
91
+ listItems.forEach((item, index) => {
92
+ item.classList.add("hidden");
93
+
94
+ if (index >= prevRange && index < currRange) {
95
+ item.classList.remove("hidden");
96
+ }
97
+ });
98
+
99
+ $('html, body').scrollTop(0);
100
+ localStorage.setItem(pageKey, currentPage);
101
+ };
102
+
103
+ window.addEventListener("load", (event) => {
104
+ // Load last visited page number
105
+ if (event.persisted
106
+ || (window.performance && window.performance.navigation.type == 2)) {
107
+ currentPage = localStorage.getItem(pageKey);
108
+ }
109
+
110
+ getPaginationNumbers();
111
+ setCurrentPage(currentPage);
112
+
113
+ prevButton.addEventListener("click", () => {
114
+ setCurrentPage(currentPage - 1);
115
+ });
116
+
117
+ nextButton.addEventListener("click", () => {
118
+ setCurrentPage(currentPage + 1);
119
+ });
120
+
121
+ document.querySelectorAll(".pagination-number").forEach((button) => {
122
+ const pageIndex = Number(button.getAttribute("page-index"));
123
+
124
+ if (pageIndex) {
125
+ button.addEventListener("click", () => {
126
+ setCurrentPage(pageIndex);
127
+ });
128
+ }
129
+ });
130
+ });
131
+ }
132
+ });
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-theme-satellite
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Yankos
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-29 00:00:00.000000000 Z
11
+ date: 2024-02-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: jekyll
@@ -24,6 +24,48 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: jekyll-feed
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.12'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: jekyll-sitemap
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.3'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.3'
55
+ - !ruby/object:Gem::Dependency
56
+ name: jekyll-seo-tag
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.6'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.6'
27
69
  description:
28
70
  email:
29
71
  - byanko55@gmail.com
@@ -32,14 +74,19 @@ extensions: []
32
74
  extra_rdoc_files: []
33
75
  files:
34
76
  - LICENSE
35
- - _config.yml
77
+ - README.md
78
+ - _includes/category.html
79
+ - _includes/footer.html
80
+ - _includes/head.html
36
81
  - _includes/loading.html
37
82
  - _includes/navigation.html
38
83
  - _includes/pagination.html
39
84
  - _includes/post.html
40
85
  - _includes/search.html
86
+ - _includes/search_event.html
41
87
  - _includes/sidebar.html
42
88
  - _layouts/default.html
89
+ - _layouts/page.html
43
90
  - _sass/darkmode.scss
44
91
  - _sass/layout.scss
45
92
  - _sass/navigation.scss
@@ -49,6 +96,8 @@ files:
49
96
  - _sass/sidebar.scss
50
97
  - _sass/toc.scss
51
98
  - _sass/vars.scss
99
+ - assets/css/404.scss
100
+ - assets/css/fonts.scss
52
101
  - assets/css/highlight-dark.min.css
53
102
  - assets/css/highlight-default.min.css
54
103
  - assets/css/style.scss
@@ -73,9 +122,13 @@ files:
73
122
  - assets/img/thumbnail/sample.png
74
123
  - assets/img/tile.png
75
124
  - assets/js/404.js
125
+ - assets/js/background.js
126
+ - assets/js/common.js
127
+ - assets/js/fontfaceobserver.js
76
128
  - assets/js/highlight.min.js
77
- - assets/js/main.js
78
- - assets/js/stars.js
129
+ - assets/js/post.js
130
+ - assets/js/search.js
131
+ - assets/js/subject.js
79
132
  - assets/js/sweet-scroll.min.js
80
133
  - assets/js/tocbot.min.js
81
134
  homepage: https://github.com/byanko55/jekyll-theme-satellite
data/_config.yml DELETED
@@ -1,82 +0,0 @@
1
- # Welcome to Jekyll!
2
- #
3
- # This config file is meant for settings that affect your whole blog, values
4
- # which you are expected to set up once and rarely edit after that. If you find
5
- # yourself editing this file very often, consider using Jekyll's data files
6
- # feature for the data you need to update frequently.
7
- #
8
- # For technical reasons, this file is *NOT* reloaded automatically when you use
9
- # 'bundle exec jekyll serve'. If you change this file, please restart the server process.
10
- #
11
- # If you need help with YAML syntax, here are some quick references for you:
12
- # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml
13
- # https://learnxinyminutes.com/docs/yaml/
14
- #
15
- # Site settings
16
- # These are used to personalize your new site. If you look in the HTML files,
17
- # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
18
- # You can create any custom variable you would like, and they will be accessible
19
- # in the templates via {{ site.myvariable }}.
20
-
21
- title: Example.com
22
- username: username
23
- email: example@gmail.com
24
- github_username: github
25
- twitter_username: twitter
26
- instagram_username: instagram
27
- linkedin_username: linkedin
28
- facebook_username: facebook
29
- description: "Satellite - jekyll blog theme"
30
- baseurl: "" # the subpath of your site, e.g. /blog
31
- url: "localhost:4000" # the base hostname & protocol for your site, e.g. http://example.com
32
- repo_url: "https://github.com/byanko55/jekyll-theme-satellite"
33
- goatcounter_code:
34
- google_analytics:
35
-
36
- # Build settings
37
- plugins:
38
- - jekyll-feed
39
- - jekyll-sitemap
40
- - jekyll-seo-tag
41
-
42
- exclude:
43
- - guide/
44
-
45
- collections:
46
- pages:
47
- output: true
48
- permalink: /:path.html
49
-
50
- defaults:
51
- -
52
- scope:
53
- path: ""
54
- type: "pages"
55
- values:
56
- layout: "default"
57
- date: "1900-01-01"
58
-
59
- # Markdown newline activator
60
- kramdown:
61
- input: GFM
62
- hard_wrap: true
63
-
64
- # Exclude from processing.
65
- # The following items will not be processed, by default.
66
- # Any item listed under the `exclude:` key here will be automatically added to
67
- # the internal "default list".
68
- #
69
- # Excluded items can be processed by explicitly listing the directories or
70
- # their entries' file path in the `include:` list.
71
- #
72
- # exclude:
73
- # - .sass-cache/
74
- # - .jekyll-cache/
75
- # - gemfiles/
76
- # - Gemfile
77
- # - Gemfile.lock
78
- # - node_modules/
79
- # - vendor/bundle/
80
- # - vendor/cache/
81
- # - vendor/gems/
82
- # - vendor/ruby/