locatine 0.00695 → 0.01084
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/locatine/app/background.js +9 -0
- data/lib/locatine/app/content.css +40 -0
- data/lib/locatine/app/content.js +94 -0
- data/lib/locatine/app/devtools.html +1 -0
- data/lib/locatine/app/devtools.js +3 -0
- data/lib/locatine/app/manifest.json +20 -0
- data/lib/locatine/app/popup.css +41 -0
- data/lib/locatine/app/popup.html +17 -0
- data/lib/locatine/app/popup.js +50 -0
- data/lib/locatine/search.rb +54 -26
- data/lib/locatine/version.rb +1 -1
- data/lib/locatine.rb +2 -1
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38885391980f7e8d2b0cef65643f998edf5074cf
|
4
|
+
data.tar.gz: f321d064c113b65f024d8d22bd52bfd0ab6a56d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 110afe53c901b20d93cdc7c34c2846e82c5b361d96068e368a42ad907278bb124eed0fdc53bc993a77f87afae72b744a03bd41e7e7156679a62604a6a76a115b
|
7
|
+
data.tar.gz: 02105771e43ba72453ebbb238be198408037c11fd90e2169f0e79f44d96efc49a1c4bed2c08dcf8e5068e34c08c2725bb015343475074b9a48707e9a52937764
|
@@ -0,0 +1,40 @@
|
|
1
|
+
@keyframes locatine_found {
|
2
|
+
0% {border: 4px dashed #6495ED;}
|
3
|
+
20% {border: 4px dashed #FF7F50;}
|
4
|
+
40% {border: 4px dashed #8FBC8F;}
|
5
|
+
60% {border: 4px dashed #FFA500;}
|
6
|
+
80% {border: 4px dashed #DDA0DD;}
|
7
|
+
100% {border: 4px dashed #6495ED;}
|
8
|
+
}
|
9
|
+
|
10
|
+
div[locatinestyle=true] {
|
11
|
+
position:fixed;
|
12
|
+
padding:0;
|
13
|
+
margin:0;
|
14
|
+
top:0;
|
15
|
+
left:0;
|
16
|
+
opacity: 0;
|
17
|
+
background: black;
|
18
|
+
height: 100%;
|
19
|
+
width: 100%;
|
20
|
+
pointer-events: visible;
|
21
|
+
display: block;
|
22
|
+
z-index: 2147483646;
|
23
|
+
}
|
24
|
+
|
25
|
+
div[locatinestyle=false] {
|
26
|
+
height: 0%;
|
27
|
+
width: 0%;
|
28
|
+
}
|
29
|
+
|
30
|
+
div[locatinestyle=blocked] {
|
31
|
+
height: 0%;
|
32
|
+
width: 0%;
|
33
|
+
}
|
34
|
+
|
35
|
+
[locatineclass=foundbylocatine]
|
36
|
+
{animation: locatine_found 6s infinite;
|
37
|
+
-webkit-appearance: none;
|
38
|
+
-moz-appearance: none;
|
39
|
+
appearance: none;
|
40
|
+
}
|
@@ -0,0 +1,94 @@
|
|
1
|
+
async function set_value(name, value){
|
2
|
+
let temp = {};
|
3
|
+
temp[name] = value;
|
4
|
+
await chrome.storage.sync.set(temp, function() {});
|
5
|
+
};
|
6
|
+
|
7
|
+
async function get_value(name) {
|
8
|
+
let x = await new Promise((resolve, reject) => chrome.storage.sync.get([name], resolve));
|
9
|
+
return x[name];
|
10
|
+
};
|
11
|
+
|
12
|
+
document.addEventListener("locatine_send", async function(e) {
|
13
|
+
await set_value(e.detail.varname, e.detail.varvalue);
|
14
|
+
});
|
15
|
+
|
16
|
+
async function refreshData(){
|
17
|
+
if (!document.getElementById("locatine_magic_div")){
|
18
|
+
const options = {
|
19
|
+
"locatineclass": "locatine_smthing",
|
20
|
+
"id":"locatine_magic_div",
|
21
|
+
"locatinestyle": await get_value('magic_div') || "false",
|
22
|
+
"locatinetitle": "ok",
|
23
|
+
"locatinehint": "ok"
|
24
|
+
};
|
25
|
+
locatine_create_element(document.body, "div", options, "");
|
26
|
+
} else {
|
27
|
+
const magicDiv = document.getElementById("locatine_magic_div");
|
28
|
+
if (magicDiv.getAttribute("locatinestyle") === "set_true") {
|
29
|
+
await set_value('magic_div', true);
|
30
|
+
magicDiv.setAttribute("locatinestyle", "true");
|
31
|
+
};
|
32
|
+
if (magicDiv.getAttribute("locatinetitle") != "ok") {
|
33
|
+
await set_value('locatine_title', magicDiv.getAttribute("locatinetitle"));
|
34
|
+
await set_value('locatine_hint', magicDiv.getAttribute("locatinehint"));
|
35
|
+
magicDiv.setAttribute("locatinetitle", "ok");
|
36
|
+
}
|
37
|
+
let status = await get_value('magic_div');
|
38
|
+
if (magicDiv.getAttribute("locatinestyle") != "set_true"){
|
39
|
+
magicDiv.setAttribute("locatinestyle", status);
|
40
|
+
}
|
41
|
+
magicDiv.setAttribute("locatinecollection", await get_value("locatine_collection"))
|
42
|
+
if (magicDiv.getAttribute("locatineconfirmed") === "ok") {
|
43
|
+
magicDiv.removeAttribute("tag");
|
44
|
+
magicDiv.removeAttribute("index");
|
45
|
+
await set_value("locatine_confirm", false);
|
46
|
+
await set_value('magic_div', false);
|
47
|
+
}
|
48
|
+
const confirmed = await get_value('locatine_confirm');
|
49
|
+
magicDiv.setAttribute("locatineconfirmed", confirmed);
|
50
|
+
};
|
51
|
+
const magic_cover = document.getElementById('locatine_magic_div');
|
52
|
+
magic_cover.onclick = function(e) {locatine_magic_click(e)};
|
53
|
+
};
|
54
|
+
|
55
|
+
function getSelected(value){
|
56
|
+
const tagName = value.tagName;
|
57
|
+
const array = Array.prototype.slice.call( document.getElementsByTagName(tagName) );
|
58
|
+
const index = array.indexOf(value);
|
59
|
+
document.getElementById("locatine_magic_div").setAttribute("tag", tagName);
|
60
|
+
document.getElementById("locatine_magic_div").setAttribute("index", index);
|
61
|
+
};
|
62
|
+
|
63
|
+
function locatine_magic_click(e) {
|
64
|
+
document.getElementById("locatine_magic_div").setAttribute("locatinestyle", "blocked");
|
65
|
+
const value = document.elementFromPoint(e.clientX, e.clientY);
|
66
|
+
document.getElementById("locatine_magic_div").setAttribute("locatinestyle", "true");
|
67
|
+
const tagName = value.tagName;
|
68
|
+
const array = Array.prototype.slice.call( document.getElementsByTagName(tagName) );
|
69
|
+
const index = array.indexOf(value);
|
70
|
+
document.getElementById("locatine_magic_div").setAttribute("TAG", tagName);
|
71
|
+
document.getElementById("locatine_magic_div").setAttribute("INDEX", index);
|
72
|
+
};
|
73
|
+
|
74
|
+
function locatine_create_element(dom, tag, attrs, inner) {
|
75
|
+
const element = document.createElement(tag);
|
76
|
+
dom.appendChild(element);
|
77
|
+
for (var key in attrs) {
|
78
|
+
element.setAttribute(key, attrs[key])
|
79
|
+
};
|
80
|
+
element.innerHTML = inner;
|
81
|
+
return element;
|
82
|
+
};
|
83
|
+
|
84
|
+
//set_value('magic_div','off');
|
85
|
+
|
86
|
+
setInterval(async function(){
|
87
|
+
if (document.getElementById("locatine_magic_div")) {
|
88
|
+
if (document.getElementById("locatine_magic_div").getAttribute("locatinestyle") != "blocked") {
|
89
|
+
await refreshData()
|
90
|
+
}
|
91
|
+
} else {
|
92
|
+
await refreshData()
|
93
|
+
}
|
94
|
+
}, 100);
|
@@ -0,0 +1 @@
|
|
1
|
+
<script src="devtools.js"></script>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"name": "Locatine app",
|
3
|
+
"version": "0.01084",
|
4
|
+
"description": "Messaging from browser to main app",
|
5
|
+
"devtools_page": "devtools.html",
|
6
|
+
"permissions": ["activeTab", "storage", "contextMenus", "tabs"],
|
7
|
+
"background": {
|
8
|
+
"scripts": ["background.js"],
|
9
|
+
"persistent": true
|
10
|
+
},
|
11
|
+
"content_scripts": [{
|
12
|
+
"matches": ["<all_urls>"],
|
13
|
+
"all_frames": true,
|
14
|
+
"js": ["content.js"],
|
15
|
+
"css": ["content.css"]
|
16
|
+
}],
|
17
|
+
"browser_action": {
|
18
|
+
"default_title": "Locatine"},
|
19
|
+
"manifest_version": 2
|
20
|
+
}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
.button {
|
2
|
+
border: none;
|
3
|
+
height: 30px;
|
4
|
+
width: 100%;
|
5
|
+
color: white;
|
6
|
+
padding: 5px 32px;
|
7
|
+
text-align: center;
|
8
|
+
text-decoration: none;
|
9
|
+
display: inline-block;
|
10
|
+
font-size: 16px;
|
11
|
+
}
|
12
|
+
|
13
|
+
.green {
|
14
|
+
background-color: #4CAF50
|
15
|
+
}
|
16
|
+
|
17
|
+
.red {
|
18
|
+
background-color: #AF4C50
|
19
|
+
}
|
20
|
+
|
21
|
+
.blue {
|
22
|
+
background-color: #4C50AF
|
23
|
+
}
|
24
|
+
|
25
|
+
.header {
|
26
|
+
padding: 10px 5px 10px 5px;
|
27
|
+
text-align: center;
|
28
|
+
color: black;
|
29
|
+
font-size: 25px;
|
30
|
+
}
|
31
|
+
|
32
|
+
.hint {
|
33
|
+
padding: 10px 5px 10px 5px;
|
34
|
+
text-align: center;
|
35
|
+
background-color: #EFFA93;
|
36
|
+
box-shadow: 5px 5px 5px;
|
37
|
+
}
|
38
|
+
|
39
|
+
.block {
|
40
|
+
padding: 30px 5px 30px 5px;
|
41
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<link rel="stylesheet" href="popup.css">
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
<h2 class="header" id="mainTitle">Right now you are defining nothing. So no button will work</h2>
|
8
|
+
<h3 class="hint" id="hint">But you can click it anyway :)</h3>
|
9
|
+
<div class="block">
|
10
|
+
<input class="blue button" id="watchSwitch" type="button" value="Do not watch"/>
|
11
|
+
<input class="blue button" id="mode" type="button" value="Adding mode is enabled"/>
|
12
|
+
<input class="red button" id="clearMark" type="button" value="Clear selection"/>
|
13
|
+
<input class="green button" id="confirm" type="button" value="Confirm selection"/>
|
14
|
+
</div>
|
15
|
+
<script src="popup.js"></script>
|
16
|
+
</body>
|
17
|
+
</html>
|
@@ -0,0 +1,50 @@
|
|
1
|
+
async function get_value(name) {
|
2
|
+
let x = await new Promise((resolve, reject) => chrome.storage.sync.get([name], resolve));
|
3
|
+
return x[name];
|
4
|
+
};
|
5
|
+
|
6
|
+
async function set_value(name, value){
|
7
|
+
let temp = {};
|
8
|
+
temp[name] = value;
|
9
|
+
await chrome.storage.sync.set(temp, function() {});
|
10
|
+
};
|
11
|
+
|
12
|
+
async function correct_buttons() {
|
13
|
+
if (await get_value("magic_div") === true) {
|
14
|
+
document.getElementById("watchSwitch").setAttribute("value", "Locatine is waiting for click");
|
15
|
+
} else {
|
16
|
+
document.getElementById("watchSwitch").setAttribute("value", "Locatine is not waiting now");
|
17
|
+
};
|
18
|
+
if (await get_value("locatine_collection") === true) {
|
19
|
+
document.getElementById("mode").setAttribute("value", "You are in collection mode")
|
20
|
+
} else {
|
21
|
+
document.getElementById("mode").setAttribute("value", "You are in single selection mode")
|
22
|
+
};
|
23
|
+
document.getElementById("mainTitle").innerText = await get_value("locatine_title");
|
24
|
+
document.getElementById("hint").innerText = await get_value("locatine_hint");
|
25
|
+
}
|
26
|
+
|
27
|
+
async function watch() {
|
28
|
+
await set_value("magic_div", !(await get_value("magic_div")));
|
29
|
+
}
|
30
|
+
|
31
|
+
function clear() {
|
32
|
+
set_value("locatine_confirm", "declined");
|
33
|
+
}
|
34
|
+
|
35
|
+
function confirm() {
|
36
|
+
set_value("locatine_confirm", true);
|
37
|
+
}
|
38
|
+
|
39
|
+
async function mode() {
|
40
|
+
await set_value("locatine_collection", !(await get_value("locatine_collection")));
|
41
|
+
}
|
42
|
+
|
43
|
+
document.getElementById("watchSwitch").onclick = function() {watch()};
|
44
|
+
document.getElementById("clearMark").onclick = function() {clear()};
|
45
|
+
document.getElementById("confirm").onclick = function() {confirm()};
|
46
|
+
document.getElementById("mode").onclick = function() {mode()};
|
47
|
+
|
48
|
+
setInterval(function(){
|
49
|
+
correct_buttons();
|
50
|
+
}, 100);
|
data/lib/locatine/search.rb
CHANGED
@@ -67,7 +67,16 @@ module Locatine
|
|
67
67
|
# +look_in+ only elements of that kind will be used. Use Watir::Browser methods returning collections (:text_fields, :links, :divs, etc.)
|
68
68
|
#
|
69
69
|
# +iframe+ if provided locatine will look for elements inside of it
|
70
|
-
def find(simple_name = nil,
|
70
|
+
def find(simple_name = nil,
|
71
|
+
name: nil,
|
72
|
+
scope: nil,
|
73
|
+
exact: false,
|
74
|
+
locator: {},
|
75
|
+
vars: {},
|
76
|
+
look_in: nil,
|
77
|
+
iframe: nil,
|
78
|
+
return_locator: false,
|
79
|
+
collection: false)
|
71
80
|
name ||= simple_name
|
72
81
|
raise ArgumentError, ":name should be provided" if !name
|
73
82
|
@type = look_in
|
@@ -85,22 +94,26 @@ module Locatine
|
|
85
94
|
end
|
86
95
|
end
|
87
96
|
result, attributes = ask(scope, name, result, vars) if @learn
|
88
|
-
raise RuntimeError, "Nothing was found for #{scope} #{name}" if !result
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
97
|
+
raise RuntimeError, "Nothing was found for #{scope} #{name}" if !result && !exact
|
98
|
+
if result
|
99
|
+
attributes = generate_data(result, vars) if !attributes
|
100
|
+
store(attributes, scope, name)
|
101
|
+
return return_locator ? {xpath: generate_xpath(attributes, vars)} : to_subtype(result, collection)
|
102
|
+
else
|
103
|
+
return nil
|
104
|
+
end
|
93
105
|
end
|
94
106
|
|
95
107
|
##
|
96
108
|
# Find alias with return_locator option enforced
|
97
109
|
def lctr(*args)
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
110
|
+
enforce(:return_locator, true, *args)
|
111
|
+
end
|
112
|
+
|
113
|
+
##
|
114
|
+
# Find alias with collection option enforced
|
115
|
+
def collect(*args)
|
116
|
+
enforce(:collection, true, *args)
|
104
117
|
end
|
105
118
|
|
106
119
|
private
|
@@ -124,6 +137,18 @@ module Locatine
|
|
124
137
|
end
|
125
138
|
end
|
126
139
|
|
140
|
+
def enforce(what, value, *args)
|
141
|
+
if args.last.class == Hash
|
142
|
+
args.last[what] = value
|
143
|
+
find(*args)
|
144
|
+
else
|
145
|
+
temp = Hash.new
|
146
|
+
temp[what] = value
|
147
|
+
args.push(temp)
|
148
|
+
find(*args)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
127
152
|
def engine
|
128
153
|
return (@iframe || @browser)
|
129
154
|
end
|
@@ -180,7 +205,7 @@ module Locatine
|
|
180
205
|
end
|
181
206
|
|
182
207
|
def generate_xpath(data, vars)
|
183
|
-
xpath = ''
|
208
|
+
xpath = "[not(@id = 'locatine_magic_div')]"
|
184
209
|
data.each_pair do |depth, array|
|
185
210
|
trusted = get_trusted(array)
|
186
211
|
trusted.each do |hash|
|
@@ -247,21 +272,21 @@ module Locatine
|
|
247
272
|
full_part = full_part + "[contains(name(), '#{part}')]"
|
248
273
|
end
|
249
274
|
xpath = full_part + "[., '#{process_string(hash["value"], vars)}')]]"
|
250
|
-
find_by_locator(xpath: "#{full_part}[contains(., '#{process_string(hash["value"], vars)}')]]#{correction}")
|
275
|
+
find_by_locator(xpath: "#{full_part}[contains(., '#{process_string(hash["value"], vars)}')]]#{correction}[not(@id = 'locatine_magic_div')]")
|
251
276
|
end
|
252
277
|
|
253
278
|
##
|
254
279
|
# Getting elements by tag
|
255
280
|
def find_by_tag(hash, vars, depth = 0)
|
256
281
|
correction = "/*" * depth.to_i
|
257
|
-
find_by_locator(xpath: "//*[self::#{process_string(hash["value"], vars)}')]#{correction}")
|
282
|
+
find_by_locator(xpath: "//*[self::#{process_string(hash["value"], vars)}')]#{correction}[not(@id = 'locatine_magic_div')]")
|
258
283
|
end
|
259
284
|
|
260
285
|
##
|
261
286
|
# Getting elements by text
|
262
287
|
def find_by_text(hash, vars, depth = 0)
|
263
288
|
correction = "/*" * depth.to_i
|
264
|
-
find_by_locator(xpath: "//*[contains(text(), '#{process_string(hash["value"], vars)}')]#{correction}")
|
289
|
+
find_by_locator(xpath: "//*[contains(text(), '#{process_string(hash["value"], vars)}')]#{correction}[not(@id = 'locatine_magic_div')]")
|
265
290
|
end
|
266
291
|
|
267
292
|
##
|
@@ -308,16 +333,16 @@ module Locatine
|
|
308
333
|
timeout = @cold_time
|
309
334
|
@cold_time = 0
|
310
335
|
name.split(" ").each do |part|
|
311
|
-
all = all + find_by_locator({
|
312
|
-
all = all + find_by_locator({xpath: "//*[contains(text(),'#{part}')]"}).to_a
|
313
|
-
all = all + find_by_locator({xpath: "//*[@*[contains(., '#{part}')]]"}).to_a
|
336
|
+
all = all + find_by_locator({xpath: "//#{part}[not(@id = 'locatine_magic_div')]"}).to_a
|
337
|
+
all = all + find_by_locator({xpath: "//*[contains(text(),'#{part}')][not(@id = 'locatine_magic_div')]"}).to_a
|
338
|
+
all = all + find_by_locator({xpath: "//*[@*[contains(., '#{part}')]][not(@id = 'locatine_magic_div')]"}).to_a
|
314
339
|
end
|
315
340
|
if all.length>0
|
316
341
|
max = all.count(all.max_by {|i| all.count(i)})
|
317
342
|
guess = (all.select {|i| all.count(i) == max}).uniq
|
318
343
|
guess_data = generate_data(guess, vars)
|
319
344
|
by_data = find_by_data(guess_data, vars)
|
320
|
-
if by_data.nil? || (engine.elements.length/
|
345
|
+
if by_data.nil? || (engine.elements.length/by_data.length <=4)
|
321
346
|
set_title "Locatine has no good guess for #{name} in #{scope}. Try to change the name. Or just define it."
|
322
347
|
guess = nil
|
323
348
|
guess_data = {}
|
@@ -336,9 +361,9 @@ module Locatine
|
|
336
361
|
element, attributes, finished, old_tag, old_index, old_element = result, {}, false, nil, nil, nil
|
337
362
|
if !element.nil?
|
338
363
|
attributes = generate_data(element, vars)
|
339
|
-
|
364
|
+
elsif name.length >= 5
|
340
365
|
set_title("Locatine is trying to guess what is #{name} in #{scope}.")
|
341
|
-
element, attributes = find_by_guess(scope, name, vars)
|
366
|
+
element, attributes = find_by_guess(scope, name, vars)
|
342
367
|
end
|
343
368
|
while !finished do
|
344
369
|
sleep 0.1
|
@@ -556,11 +581,14 @@ module Locatine
|
|
556
581
|
#
|
557
582
|
# Params:
|
558
583
|
# +result+ must be Watir::HTMLElementCollection or Array
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
584
|
+
#
|
585
|
+
# +collection+ nil, true or false
|
586
|
+
def to_subtype(result, collection)
|
587
|
+
case collection
|
588
|
+
when true
|
563
589
|
return result
|
590
|
+
when false
|
591
|
+
return result.first.to_subtype
|
564
592
|
end
|
565
593
|
end
|
566
594
|
end
|
data/lib/locatine/version.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Locatine
|
2
2
|
# constants here...
|
3
|
-
VERSION = "0.
|
3
|
+
VERSION = "0.01084"
|
4
4
|
NAME = "locatine"
|
5
5
|
HOME = File.readable?("#{Dir.pwd}/lib/#{Locatine::NAME}")? "#{Dir.pwd}/lib/#{Locatine::NAME}" : "#{Gem.dir}/gems/#{Locatine::NAME}-#{Locatine::VERSION}/lib/#{Locatine::NAME}"
|
6
6
|
end
|
data/lib/locatine.rb
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
|
1
|
+
require "locatine/search"
|
2
|
+
require "locatine/version"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: locatine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.01084'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sergei Seleznev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-03-
|
11
|
+
date: 2019-03-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -115,6 +115,15 @@ extensions: []
|
|
115
115
|
extra_rdoc_files: []
|
116
116
|
files:
|
117
117
|
- lib/locatine.rb
|
118
|
+
- lib/locatine/app/background.js
|
119
|
+
- lib/locatine/app/content.css
|
120
|
+
- lib/locatine/app/content.js
|
121
|
+
- lib/locatine/app/devtools.html
|
122
|
+
- lib/locatine/app/devtools.js
|
123
|
+
- lib/locatine/app/manifest.json
|
124
|
+
- lib/locatine/app/popup.css
|
125
|
+
- lib/locatine/app/popup.html
|
126
|
+
- lib/locatine/app/popup.js
|
118
127
|
- lib/locatine/search.rb
|
119
128
|
- lib/locatine/version.rb
|
120
129
|
homepage: https://github.com/sseleznevqa/locatine
|