Merge branch 'notes'
Conflicts: Vendorfile
This commit is contained in:
commit
0c8ad2f86e
84 changed files with 3162 additions and 107 deletions
1
Gemfile
1
Gemfile
|
@ -29,6 +29,7 @@ gem 'paperclip', '~> 2.0'
|
|||
gem 'deadlock_retry', '>= 1.2.0'
|
||||
gem 'i18n-js', '>= 3.0.0.rc2'
|
||||
gem 'rack-cors'
|
||||
gem 'jsonify-rails'
|
||||
|
||||
# We need ruby-openid 2.2.0 or later for ruby 1.9 support
|
||||
gem 'ruby-openid', '>= 2.2.0'
|
||||
|
|
|
@ -67,6 +67,11 @@ GEM
|
|||
railties (>= 3.0, < 5.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (1.7.7)
|
||||
jsonify (0.3.1)
|
||||
multi_json (~> 1.0)
|
||||
jsonify-rails (0.3.2)
|
||||
actionpack
|
||||
jsonify (< 0.4.0)
|
||||
jwt (0.1.8)
|
||||
multi_json (>= 1.5)
|
||||
libv8 (3.3.10.4)
|
||||
|
@ -183,6 +188,7 @@ DEPENDENCIES
|
|||
i18n-js (>= 3.0.0.rc2)
|
||||
iconv (= 0.1)
|
||||
jquery-rails
|
||||
jsonify-rails
|
||||
libxml-ruby (>= 2.0.5)
|
||||
memcached (>= 1.4.1)
|
||||
minitest
|
||||
|
|
|
@ -29,6 +29,13 @@ folder 'vendor/assets' do
|
|||
folder 'jquery' do
|
||||
from 'git://github.com/jevin/Autogrow-Textarea.git' do
|
||||
file 'jquery.autogrowtextarea.js', 'jquery.autogrowtextarea.js'
|
||||
end
|
||||
end
|
||||
|
||||
folder 'ohauth' do
|
||||
from 'git://github.com/tmcw/ohauth.git' do
|
||||
file 'ohauth.js'
|
||||
file 'sha.js'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
BIN
app/assets/images/closed_note_marker.png
Normal file
BIN
app/assets/images/closed_note_marker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
app/assets/images/new_note_marker.png
Normal file
BIN
app/assets/images/new_note_marker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
BIN
app/assets/images/open_note_marker.png
Normal file
BIN
app/assets/images/open_note_marker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
|
@ -10,6 +10,7 @@
|
|||
//= require leaflet.pan
|
||||
//= require leaflet.zoom
|
||||
//= require i18n/translations
|
||||
//= require oauth
|
||||
//= require osm
|
||||
//= require piwik
|
||||
//= require map
|
||||
|
|
|
@ -43,7 +43,27 @@ $(document).ready(function () {
|
|||
});
|
||||
|
||||
var centre = bbox.getCenter();
|
||||
updatelinks(centre.lng, centre.lat, 16, null, params.minlon, params.minlat, params.maxlon, params.maxlat);
|
||||
updatelinks(centre.lon, centre.lat, 16, null, params.minlon, params.minlat, params.maxlon, params.maxlat);
|
||||
} else if (params.type == "note") {
|
||||
var object = {type: params.type, id: params.id};
|
||||
|
||||
map.setView([params.lat, params.lon], 16);
|
||||
|
||||
L.marker([params.lat, params.lon], { icon: getUserIcon() }).addTo(map);
|
||||
|
||||
var bbox = map.getBounds();
|
||||
|
||||
$("#loading").hide();
|
||||
$("#browse_map .geolink").show();
|
||||
|
||||
$("a[data-editor=remote]").click(function () {
|
||||
return remoteEditHandler(bbox);
|
||||
});
|
||||
|
||||
updatelinks(params.lon, params.lat, 16, null,
|
||||
bbox.getWestLng(), bbox.getSouthLat(),
|
||||
bbox.getEastLng(), bbox.getNorthLat(),
|
||||
object);
|
||||
} else {
|
||||
$("#object_larger_map").hide();
|
||||
$("#object_edit").hide();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
//= require index/browse
|
||||
//= require index/export
|
||||
//= require index/key
|
||||
//= require index/notes
|
||||
|
||||
$(document).ready(function () {
|
||||
var permalinks = $("#permalink").detach().html();
|
||||
|
|
281
app/assets/javascripts/index/notes.js.erb
Normal file
281
app/assets/javascripts/index/notes.js.erb
Normal file
|
@ -0,0 +1,281 @@
|
|||
//= require templates/notes/show
|
||||
//= require templates/notes/new
|
||||
|
||||
$(document).ready(function () {
|
||||
var params = OSM.mapParams();
|
||||
|
||||
var noteIcons = {
|
||||
"new": L.icon({
|
||||
iconUrl: "<%= image_path 'new_note_marker.png' %>",
|
||||
iconSize: [22, 22],
|
||||
iconAnchor: [11, 11]
|
||||
}),
|
||||
"open": L.icon({
|
||||
iconUrl: "<%= image_path 'open_note_marker.png' %>",
|
||||
iconSize: [22, 22],
|
||||
iconAnchor: [11, 11]
|
||||
}),
|
||||
"closed": L.icon({
|
||||
iconUrl: "<%= image_path 'closed_note_marker.png' %>",
|
||||
iconSize: [22, 22],
|
||||
iconAnchor: [11, 11]
|
||||
})
|
||||
};
|
||||
|
||||
var noteLayer = new L.LayerGroup();
|
||||
var notes = {};
|
||||
var newNote;
|
||||
|
||||
map.on("layeradd", function (e) {
|
||||
if (e.layer == noteLayer) {
|
||||
loadNotes();
|
||||
map.on("moveend", loadNotes);
|
||||
}
|
||||
});
|
||||
|
||||
map.on("layerremove", function (e) {
|
||||
if (e.layer == noteLayer) {
|
||||
map.off("moveend", loadNotes);
|
||||
noteLayer.clearLayers();
|
||||
notes = {};
|
||||
}
|
||||
});
|
||||
|
||||
map.on("popupopen", function (e) {
|
||||
$(e.popup._container).find(".comment").focus();
|
||||
});
|
||||
|
||||
map.on("popupclose", function (e) {
|
||||
if (newNote && e.popup == newNote._popup) {
|
||||
$(newNote).oneTime(10, "removenote", function () {
|
||||
map.removeLayer(newNote);
|
||||
newNote = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (OSM.STATUS != 'api_offline' && OSM.STATUS != 'database_offline') {
|
||||
map.layersControl.addOverlay(noteLayer, I18n.t("browse.start_rjs.notes_layer_name"));
|
||||
|
||||
if (params.notes) map.addLayer(noteLayer);
|
||||
|
||||
if (params.note) {
|
||||
$.ajax({
|
||||
url: "/api/" + OSM.API_VERSION + "/notes/" + params.note + ".json",
|
||||
success: function (feature) {
|
||||
var marker = updateMarker(notes[feature.properties.id], feature);
|
||||
|
||||
notes[feature.properties.id] = marker;
|
||||
|
||||
map.addLayer(noteLayer);
|
||||
marker.openPopup();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateMarker(marker, feature) {
|
||||
if (marker)
|
||||
{
|
||||
marker.setIcon(noteIcons[feature.properties.status]);
|
||||
marker._popup.setContent(createPopupContent(marker, feature.properties));
|
||||
}
|
||||
else
|
||||
{
|
||||
marker = L.marker(feature.geometry.coordinates.reverse(), {
|
||||
icon: noteIcons[feature.properties.status],
|
||||
opacity: 0.7
|
||||
});
|
||||
|
||||
marker.addTo(noteLayer).bindPopup(
|
||||
createPopupContent(marker, feature.properties),
|
||||
popupOptions()
|
||||
);
|
||||
}
|
||||
|
||||
return marker;
|
||||
}
|
||||
|
||||
var noteLoader;
|
||||
|
||||
function loadNotes() {
|
||||
var bounds = map.getBounds();
|
||||
var size = bounds.getSize();
|
||||
|
||||
if (size <= OSM.MAX_NOTE_REQUEST_AREA) {
|
||||
var url = "/api/" + OSM.API_VERSION + "/notes.json?bbox=" + bounds.toBBOX();
|
||||
|
||||
if (noteLoader) noteLoader.abort();
|
||||
|
||||
noteLoader = $.ajax({
|
||||
url: url,
|
||||
success: function (json) {
|
||||
var oldNotes = notes;
|
||||
|
||||
notes = {};
|
||||
|
||||
json.features.forEach(function (feature) {
|
||||
var marker = oldNotes[feature.properties.id];
|
||||
|
||||
delete oldNotes[feature.properties.id];
|
||||
|
||||
notes[feature.properties.id] = updateMarker(marker, feature);
|
||||
});
|
||||
|
||||
for (id in oldNotes) {
|
||||
noteLayer.removeLayer(oldNotes[id]);
|
||||
}
|
||||
|
||||
noteLoader = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function popupOptions() {
|
||||
var mapSize = map.getSize();
|
||||
|
||||
return {
|
||||
minWidth: 320,
|
||||
maxWidth: mapSize.y * 1 / 3,
|
||||
maxHeight: mapSize.y * 2 / 3,
|
||||
offset: new L.Point(0, -3),
|
||||
autoPanPadding: new L.Point(60, 40)
|
||||
};
|
||||
}
|
||||
|
||||
function createPopupContent(marker, properties) {
|
||||
var content = $(JST["templates/notes/show"]({ note: properties }));
|
||||
|
||||
content.find("textarea").on("input", function (e) {
|
||||
var form = e.target.form;
|
||||
|
||||
if ($(e.target).val() == "") {
|
||||
$(form.close).val(I18n.t("javascripts.notes.show.resolve"));
|
||||
$(form.comment).prop("disabled", true);
|
||||
} else {
|
||||
$(form.close).val(I18n.t("javascripts.notes.show.comment_and_resolve"));
|
||||
$(form.comment).prop("disabled", false);
|
||||
}
|
||||
});
|
||||
|
||||
content.find("input[type=submit]").on("click", function (e) {
|
||||
e.preventDefault();
|
||||
var data = $(e.target).data();
|
||||
updateNote(marker, e.target.form, data.method, data.url);
|
||||
});
|
||||
|
||||
return content[0];
|
||||
}
|
||||
|
||||
function createNote(marker, form, url) {
|
||||
var location = marker.getLatLng();
|
||||
|
||||
$(form).find("input[type=submit]").prop("disabled", true);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: "POST",
|
||||
oauth: true,
|
||||
data: {
|
||||
lat: location.lat,
|
||||
lon: location.lng,
|
||||
text: $(form.text).val()
|
||||
},
|
||||
success: function (feature) {
|
||||
notes[feature.properties.id] = updateMarker(marker, feature);
|
||||
newNote = null;
|
||||
|
||||
$("#createnoteanchor").removeClass("disabled").addClass("geolink");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateNote(marker, form, method, url) {
|
||||
$(form).find("input[type=submit]").prop("disabled", true);
|
||||
|
||||
$.ajax({
|
||||
url: url,
|
||||
type: method,
|
||||
oauth: true,
|
||||
data: {
|
||||
text: $(form.text).val()
|
||||
},
|
||||
success: function (feature) {
|
||||
if (feature.properties.status == "hidden") {
|
||||
noteLayer.removeLayer(marker);
|
||||
|
||||
delete notes[feature.properties.id];
|
||||
} else {
|
||||
var popupContent = createPopupContent(marker, feature.properties);
|
||||
|
||||
marker.setIcon(noteIcons[feature.properties.status]);
|
||||
marker._popup.setContent(popupContent);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$("#createnoteanchor").click(function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
if ($(e.target).hasClass("disabled")) return;
|
||||
|
||||
$(e.target).removeClass("geolink").addClass("disabled");
|
||||
|
||||
map.addLayer(noteLayer);
|
||||
|
||||
var mapSize = map.getSize();
|
||||
var markerPosition;
|
||||
|
||||
if (mapSize.y > 800)
|
||||
{
|
||||
markerPosition = [mapSize.x / 2, mapSize.y / 2];
|
||||
}
|
||||
else if (mapSize.y > 400)
|
||||
{
|
||||
markerPosition = [mapSize.x / 2, 400];
|
||||
}
|
||||
else
|
||||
{
|
||||
markerPosition = [mapSize.x / 2, mapSize.y];
|
||||
}
|
||||
|
||||
newNote = L.marker(map.containerPointToLatLng(markerPosition), {
|
||||
icon: noteIcons["new"],
|
||||
opacity: 0.7,
|
||||
draggable: true
|
||||
});
|
||||
|
||||
var popupContent = $(JST["templates/notes/new"]({ create_url: $(e.target).attr("href") }));
|
||||
|
||||
popupContent.find("textarea").on("input", function (e) {
|
||||
var form = e.target.form;
|
||||
|
||||
if ($(e.target).val() == "") {
|
||||
$(form.add).prop("disabled", true);
|
||||
} else {
|
||||
$(form.add).prop("disabled", false);
|
||||
}
|
||||
});
|
||||
|
||||
popupContent.find("input[type=submit]").on("click", function (e) {
|
||||
e.preventDefault();
|
||||
createNote(newNote, e.target.form, $(e.target).data("url"));
|
||||
});
|
||||
|
||||
newNote.addTo(noteLayer).bindPopup(popupContent[0], popupOptions()).openPopup();
|
||||
|
||||
newNote.on("remove", function (e) {
|
||||
$("#createnoteanchor").removeClass("disabled").addClass("geolink");
|
||||
});
|
||||
|
||||
newNote.on("dragstart", function (e) {
|
||||
$(newNote).stopTime("removenote");
|
||||
});
|
||||
|
||||
newNote.on("dragend", function (e) {
|
||||
e.target.openPopup();
|
||||
});
|
||||
});
|
||||
});
|
25
app/assets/javascripts/oauth.js
Normal file
25
app/assets/javascripts/oauth.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
//= require sha
|
||||
//= require ohauth
|
||||
|
||||
$.ajaxPrefilter(function(options, jqxhr) {
|
||||
if (options.oauth && OSM.oauth_token) {
|
||||
var ohauth = window.ohauth;
|
||||
var url = options.url.replace(/\?$/, "");
|
||||
var params = {
|
||||
oauth_consumer_key: OSM.oauth_consumer_key,
|
||||
oauth_token: OSM.oauth_token,
|
||||
oauth_signature_method: "HMAC-SHA1",
|
||||
oauth_timestamp: ohauth.timestamp(),
|
||||
oauth_nonce: ohauth.nonce()
|
||||
};
|
||||
|
||||
params.oauth_signature = ohauth.signature(
|
||||
OSM.oauth_consumer_secret,
|
||||
OSM.oauth_token_secret,
|
||||
ohauth.baseString(options.type, url, $.extend({}, params, jqxhr.data))
|
||||
);
|
||||
|
||||
options.headers = options.headers || {};
|
||||
options.headers.Authorization = "OAuth " + ohauth.authHeader(params);
|
||||
}
|
||||
});
|
|
@ -1,13 +1,14 @@
|
|||
OSM = {
|
||||
<% if defined?(PIWIK_LOCATION) and defined?(PIWIK_SITE) %>
|
||||
PIWIK_LOCATION: <%= PIWIK_LOCATION.to_json %>,
|
||||
PIWIK_SITE: <%= PIWIK_SITE.to_json %>,
|
||||
PIWIK_LOCATION: <%= PIWIK_LOCATION.to_json %>,
|
||||
PIWIK_SITE: <%= PIWIK_SITE.to_json %>,
|
||||
<% end %>
|
||||
|
||||
MAX_REQUEST_AREA: <%= MAX_REQUEST_AREA.to_json %>,
|
||||
SERVER_URL: <%= SERVER_URL.to_json %>,
|
||||
API_VERSION: <%= API_VERSION.to_json %>,
|
||||
STATUS: <%= STATUS.to_json %>,
|
||||
MAX_REQUEST_AREA: <%= MAX_REQUEST_AREA.to_json %>,
|
||||
SERVER_URL: <%= SERVER_URL.to_json %>,
|
||||
API_VERSION: <%= API_VERSION.to_json %>,
|
||||
STATUS: <%= STATUS.to_json %>,
|
||||
MAX_NOTE_REQUEST_AREA: <%= MAX_NOTE_REQUEST_AREA.to_json %>,
|
||||
|
||||
apiUrl: function (object) {
|
||||
var url = "/api/" + OSM.API_VERSION + "/" + object.type + "/" + object.id;
|
||||
|
@ -110,6 +111,12 @@ OSM = {
|
|||
mapParams.lat = (mapParams.minlat + mapParams.maxlat) / 2;
|
||||
}
|
||||
|
||||
mapParams.notes = params.notes == "yes";
|
||||
|
||||
if (params.note) {
|
||||
mapParams.note = parseInt(params.note);
|
||||
}
|
||||
|
||||
var scale = parseFloat(params.scale);
|
||||
if (scale > 0) {
|
||||
mapParams.zoom = Math.log(360.0 / (scale * 512.0)) / Math.log(2.0);
|
||||
|
|
12
app/assets/javascripts/templates/notes/new.jst.ejs
Normal file
12
app/assets/javascripts/templates/notes/new.jst.ejs
Normal file
|
@ -0,0 +1,12 @@
|
|||
<div class="note">
|
||||
<p><%- I18n.t('javascripts.notes.new.intro') %></p>
|
||||
<form action="#">
|
||||
<input type="hidden" name="lon">
|
||||
<input type="hidden" name="lat">
|
||||
<textarea class="comment" name="text" cols="40" rows="10"></textarea>
|
||||
<br/>
|
||||
<div class="buttons">
|
||||
<input type="submit" name="add" value="<%- I18n.t('javascripts.notes.new.add') %>" data-url="<%- create_url %>" disabled="1">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
34
app/assets/javascripts/templates/notes/show.jst.ejs
Normal file
34
app/assets/javascripts/templates/notes/show.jst.ejs
Normal file
|
@ -0,0 +1,34 @@
|
|||
<div class="note">
|
||||
<h2><a href="/?note=<%- note.id %>"><%- I18n.t('javascripts.notes.show.title', { id: note.id }) %></a></h2>
|
||||
<% if (note.comments.some(function (comment) { return !comment.user })) { %>
|
||||
<small class="warning"><%- I18n.t('javascripts.notes.show.anonymous_warning') %></small>
|
||||
<% } %>
|
||||
<% note.comments.forEach(function (comment) { %>
|
||||
<div>
|
||||
<small class="deemphasize">
|
||||
<% if (comment.user) { %>
|
||||
<%= I18n.t('javascripts.notes.show.' + comment.action + '_by', {
|
||||
user: comment.user, user_url: comment.user_url,
|
||||
time: I18n.l("time.formats.long", comment.date)
|
||||
}) %>
|
||||
<% } else { %>
|
||||
<%- I18n.t('javascripts.notes.show.' + comment.action + '_by_anonymous', {
|
||||
time: I18n.l("time.formats.long", comment.date)
|
||||
}) %>
|
||||
<% } %>
|
||||
</small>
|
||||
<div class="comment_body"><%= comment.html %></div>
|
||||
</div>
|
||||
<% }) %>
|
||||
<% if (note.status == "open") { %>
|
||||
<form action="#">
|
||||
<textarea class="comment" name="text" cols="40" rows="5"></textarea>
|
||||
<br/>
|
||||
<div class="buttons">
|
||||
<input type="submit" name="hide" value="<%- I18n.t('javascripts.notes.show.hide') %>" class="hide_unless_moderator" data-method="DELETE" data-url="<%- note.url %>">
|
||||
<input type="submit" name="close" value="<%- I18n.t('javascripts.notes.show.resolve') %>" class="hide_unless_logged_in" data-method="POST" data-url="<%- note.close_url %>">
|
||||
<input type="submit" name="comment" value="<%- I18n.t('javascripts.notes.show.comment') %>" data-method="POST" data-url="<%- note.comment_url %>" disabled="1">
|
||||
</div>
|
||||
</form>
|
||||
<% } %>
|
||||
</div>
|
|
@ -511,6 +511,7 @@ table {
|
|||
#greeting {
|
||||
float: right;
|
||||
padding-top: 3px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.greeting-bar-unread {
|
||||
|
@ -537,6 +538,12 @@ table {
|
|||
background:#fff;
|
||||
}
|
||||
|
||||
.leaflet-control-attribution a.disabled {
|
||||
color: #99c9dc;
|
||||
cursor: default;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.site-index .leaflet-top,
|
||||
.site-export .leaflet-top {
|
||||
top: 10px !important;
|
||||
|
@ -550,6 +557,12 @@ table {
|
|||
left: 10px !important;
|
||||
}
|
||||
|
||||
.leaflet-popup-scrolled {
|
||||
padding-right: 20px;
|
||||
border-bottom: 0px !important;
|
||||
border-top: 0px !important;
|
||||
}
|
||||
|
||||
/* Rules for edit menu */
|
||||
|
||||
.menuicon {
|
||||
|
@ -922,12 +935,20 @@ ul.results-list li { border-bottom: 1px solid #ccc; }
|
|||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
&.warning {
|
||||
color: #ff7070;
|
||||
font-weight: bold;
|
||||
}
|
||||
h4, p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
p, ul, .bbox {
|
||||
margin-left: 33.3333%;
|
||||
}
|
||||
ul p {
|
||||
margin-left: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
h4 {
|
||||
width: 33.3333%;
|
||||
float: left;
|
||||
|
@ -1488,6 +1509,9 @@ ul.secondary-actions {
|
|||
border-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
&:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1677,3 +1701,53 @@ a.button.submit {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Rules for the user notes list */
|
||||
|
||||
.note_list {
|
||||
tr.creator {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Rules for the notes interface */
|
||||
|
||||
.note {
|
||||
h2 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
display: block;
|
||||
background-color: #ff7070;
|
||||
padding: 4px 6px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.comment_body {
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
|
||||
p {
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.comment {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
margin-top: 5px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,10 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
end
|
||||
|
||||
def require_oauth
|
||||
@oauth = @user.access_token(OAUTH_KEY) if @user and defined? OAUTH_KEY
|
||||
end
|
||||
|
||||
##
|
||||
# requires the user to be logged in by the token or HTTP methods, or have an
|
||||
# OAuth token with the right capability. this method is a bit of a pain to call
|
||||
|
@ -112,6 +116,9 @@ class ApplicationController < ActionController::Base
|
|||
def require_allow_write_gpx
|
||||
require_capability(:allow_write_gpx)
|
||||
end
|
||||
def require_allow_write_notes
|
||||
require_capability(:allow_write_notes)
|
||||
end
|
||||
|
||||
##
|
||||
# require that the user is a moderator, or fill out a helpful error message
|
||||
|
|
|
@ -76,4 +76,13 @@ class BrowseController < ApplicationController
|
|||
rescue ActiveRecord::RecordNotFound
|
||||
render :action => "not_found", :status => :not_found
|
||||
end
|
||||
|
||||
def note
|
||||
@type = "note"
|
||||
@note = Note.find(params[:id])
|
||||
@next = Note.find(:first, :order => "id ASC", :conditions => [ "status != 'hidden' AND id > :id", { :id => @note.id }] )
|
||||
@prev = Note.find(:first, :order => "id DESC", :conditions => [ "status != 'hidden' AND id < :id", { :id => @note.id }] )
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render :action => "not_found", :status => :not_found
|
||||
end
|
||||
end
|
||||
|
|
328
app/controllers/notes_controller.rb
Normal file
328
app/controllers/notes_controller.rb
Normal file
|
@ -0,0 +1,328 @@
|
|||
class NotesController < ApplicationController
|
||||
|
||||
layout 'site', :only => [:mine]
|
||||
|
||||
before_filter :check_api_readable
|
||||
before_filter :authorize_web, :only => [:mine]
|
||||
before_filter :setup_user_auth, :only => [:create, :comment]
|
||||
before_filter :authorize, :only => [:close, :destroy]
|
||||
before_filter :require_moderator, :only => [:destroy]
|
||||
before_filter :check_api_writable, :only => [:create, :comment, :close, :destroy]
|
||||
before_filter :require_allow_write_notes, :only => [:create, :comment, :close, :destroy]
|
||||
before_filter :set_locale, :only => [:mine]
|
||||
after_filter :compress_output
|
||||
around_filter :api_call_handle_error, :api_call_timeout
|
||||
|
||||
##
|
||||
# Return a list of notes in a given area
|
||||
def index
|
||||
# Figure out the bbox - we prefer a bbox argument but also
|
||||
# support the old, deprecated, method with four arguments
|
||||
if params[:bbox]
|
||||
bbox = BoundingBox.from_bbox_params(params)
|
||||
else
|
||||
raise OSM::APIBadUserInput.new("No l was given") unless params[:l]
|
||||
raise OSM::APIBadUserInput.new("No r was given") unless params[:r]
|
||||
raise OSM::APIBadUserInput.new("No b was given") unless params[:b]
|
||||
raise OSM::APIBadUserInput.new("No t was given") unless params[:t]
|
||||
|
||||
bbox = BoundingBox.from_lrbt_params(params)
|
||||
end
|
||||
|
||||
# Get any conditions that need to be applied
|
||||
notes = closed_condition(Note.scoped)
|
||||
|
||||
# Check that the boundaries are valid
|
||||
bbox.check_boundaries
|
||||
|
||||
# Check the the bounding box is not too big
|
||||
bbox.check_size(MAX_NOTE_REQUEST_AREA)
|
||||
|
||||
# Find the notes we want to return
|
||||
@notes = notes.bbox(bbox).order("updated_at DESC").limit(result_limit).preload(:comments)
|
||||
|
||||
# Render the result
|
||||
respond_to do |format|
|
||||
format.rss
|
||||
format.xml
|
||||
format.json
|
||||
format.gpx
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Create a new note
|
||||
def create
|
||||
# Check the arguments are sane
|
||||
raise OSM::APIBadUserInput.new("No lat was given") unless params[:lat]
|
||||
raise OSM::APIBadUserInput.new("No lon was given") unless params[:lon]
|
||||
raise OSM::APIBadUserInput.new("No text was given") if params[:text].blank?
|
||||
|
||||
# Extract the arguments
|
||||
lon = params[:lon].to_f
|
||||
lat = params[:lat].to_f
|
||||
comment = params[:text]
|
||||
|
||||
# Include in a transaction to ensure that there is always a note_comment for every note
|
||||
Note.transaction do
|
||||
# Create the note
|
||||
@note = Note.create(:lat => lat, :lon => lon)
|
||||
raise OSM::APIBadUserInput.new("The note is outside this world") unless @note.in_world?
|
||||
|
||||
# Save the note
|
||||
@note.save!
|
||||
|
||||
# Add a comment to the note
|
||||
add_comment(@note, comment, "opened")
|
||||
end
|
||||
|
||||
# Return a copy of the new note
|
||||
respond_to do |format|
|
||||
format.xml { render :action => :show }
|
||||
format.json { render :action => :show }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Add a comment to an existing note
|
||||
def comment
|
||||
# Check the arguments are sane
|
||||
raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
|
||||
raise OSM::APIBadUserInput.new("No text was given") if params[:text].blank?
|
||||
|
||||
# Extract the arguments
|
||||
id = params[:id].to_i
|
||||
comment = params[:text]
|
||||
|
||||
# Find the note and check it is valid
|
||||
@note = Note.find(id)
|
||||
raise OSM::APINotFoundError unless @note
|
||||
raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
|
||||
raise OSM::APINoteAlreadyClosedError.new(@note) if @note.closed?
|
||||
|
||||
# Add a comment to the note
|
||||
Note.transaction do
|
||||
add_comment(@note, comment, "commented")
|
||||
end
|
||||
|
||||
# Return a copy of the updated note
|
||||
respond_to do |format|
|
||||
format.xml { render :action => :show }
|
||||
format.json { render :action => :show }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Close a note
|
||||
def close
|
||||
# Check the arguments are sane
|
||||
raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
|
||||
|
||||
# Extract the arguments
|
||||
id = params[:id].to_i
|
||||
comment = params[:text]
|
||||
|
||||
# Find the note and check it is valid
|
||||
@note = Note.find_by_id(id)
|
||||
raise OSM::APINotFoundError unless @note
|
||||
raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
|
||||
raise OSM::APINoteAlreadyClosedError.new(@note) if @note.closed?
|
||||
|
||||
# Close the note and add a comment
|
||||
Note.transaction do
|
||||
@note.close
|
||||
|
||||
add_comment(@note, comment, "closed")
|
||||
end
|
||||
|
||||
# Return a copy of the updated note
|
||||
respond_to do |format|
|
||||
format.xml { render :action => :show }
|
||||
format.json { render :action => :show }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Get a feed of recent notes and comments
|
||||
def feed
|
||||
# Get any conditions that need to be applied
|
||||
notes = closed_condition(Note.scoped)
|
||||
|
||||
# Process any bbox
|
||||
if params[:bbox]
|
||||
bbox = BoundingBox.from_bbox_params(params)
|
||||
|
||||
bbox.check_boundaries
|
||||
bbox.check_size(MAX_NOTE_REQUEST_AREA)
|
||||
|
||||
notes = notes.bbox(bbox)
|
||||
end
|
||||
|
||||
# Find the comments we want to return
|
||||
@comments = NoteComment.where(:note_id => notes).order("created_at DESC").limit(result_limit).preload(:note)
|
||||
|
||||
# Render the result
|
||||
respond_to do |format|
|
||||
format.rss
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Read a note
|
||||
def show
|
||||
# Check the arguments are sane
|
||||
raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
|
||||
|
||||
# Find the note and check it is valid
|
||||
@note = Note.find(params[:id])
|
||||
raise OSM::APINotFoundError unless @note
|
||||
raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
|
||||
|
||||
# Render the result
|
||||
respond_to do |format|
|
||||
format.xml
|
||||
format.rss
|
||||
format.json
|
||||
format.gpx
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Delete (hide) a note
|
||||
def destroy
|
||||
# Check the arguments are sane
|
||||
raise OSM::APIBadUserInput.new("No id was given") unless params[:id]
|
||||
|
||||
# Extract the arguments
|
||||
id = params[:id].to_i
|
||||
comment = params[:text]
|
||||
|
||||
# Find the note and check it is valid
|
||||
@note = Note.find(id)
|
||||
raise OSM::APINotFoundError unless @note
|
||||
raise OSM::APIAlreadyDeletedError.new("note", @note.id) unless @note.visible?
|
||||
|
||||
# Mark the note as hidden
|
||||
Note.transaction do
|
||||
@note.status = "hidden"
|
||||
@note.save
|
||||
|
||||
add_comment(@note, comment, "hidden")
|
||||
end
|
||||
|
||||
# Return a copy of the updated note
|
||||
respond_to do |format|
|
||||
format.xml { render :action => :show }
|
||||
format.json { render :action => :show }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Return a list of notes matching a given string
|
||||
def search
|
||||
# Check the arguments are sane
|
||||
raise OSM::APIBadUserInput.new("No query string was given") unless params[:q]
|
||||
|
||||
# Get any conditions that need to be applied
|
||||
@notes = closed_condition(Note.scoped)
|
||||
@notes = @notes.joins(:comments).where("note_comments.body ~ ?", params[:q])
|
||||
|
||||
# Find the notes we want to return
|
||||
@notes = @notes.order("updated_at DESC").limit(result_limit).preload(:comments)
|
||||
|
||||
# Render the result
|
||||
respond_to do |format|
|
||||
format.rss { render :action => :index }
|
||||
format.xml { render :action => :index }
|
||||
format.json { render :action => :index }
|
||||
format.gpx { render :action => :index }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Display a list of notes by a specified user
|
||||
def mine
|
||||
if params[:display_name]
|
||||
if @this_user = User.active.find_by_display_name(params[:display_name])
|
||||
@title = t 'note.mine.title', :user => @this_user.display_name
|
||||
@heading = t 'note.mine.heading', :user => @this_user.display_name
|
||||
@description = t 'note.mine.subheading', :user => render_to_string(:partial => "user", :object => @this_user)
|
||||
@page = (params[:page] || 1).to_i
|
||||
@page_size = 10
|
||||
@notes = @this_user.notes.order("updated_at DESC, id").uniq.offset((@page - 1) * @page_size).limit(@page_size).preload(:comments => :author)
|
||||
else
|
||||
@title = t 'user.no_such_user.title'
|
||||
@not_found_user = params[:display_name]
|
||||
|
||||
render :template => 'user/no_such_user', :status => :not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
#------------------------------------------------------------
|
||||
# utility functions below.
|
||||
#------------------------------------------------------------
|
||||
|
||||
##
|
||||
# Render an OK response
|
||||
def render_ok
|
||||
if params[:format] == "js"
|
||||
render :text => "osbResponse();", :content_type => "text/javascript"
|
||||
else
|
||||
render :text => "ok " + @note.id.to_s + "\n", :content_type => "text/plain" if @note
|
||||
render :text => "ok\n", :content_type => "text/plain" unless @note
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Get the maximum number of results to return
|
||||
def result_limit
|
||||
if params[:limit] and params[:limit].to_i > 0 and params[:limit].to_i < 10000
|
||||
params[:limit].to_i
|
||||
else
|
||||
100
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Generate a condition to choose which bugs we want based
|
||||
# on their status and the user's request parameters
|
||||
def closed_condition(notes)
|
||||
if params[:closed]
|
||||
closed_since = params[:closed].to_i
|
||||
else
|
||||
closed_since = 7
|
||||
end
|
||||
|
||||
if closed_since < 0
|
||||
notes = notes.where("status != 'hidden'")
|
||||
elsif closed_since > 0
|
||||
notes = notes.where("(status = 'open' OR (status = 'closed' AND closed_at > '#{Time.now - closed_since.days}'))")
|
||||
else
|
||||
notes = notes.where("status = 'open'")
|
||||
end
|
||||
|
||||
return notes
|
||||
end
|
||||
|
||||
##
|
||||
# Add a comment to a note
|
||||
def add_comment(note, text, event)
|
||||
attributes = { :visible => true, :event => event, :body => text }
|
||||
|
||||
if @user
|
||||
attributes[:author_id] = @user.id
|
||||
else
|
||||
attributes[:author_ip] = request.remote_ip
|
||||
end
|
||||
|
||||
comment = note.comments.create(attributes, :without_protection => true)
|
||||
|
||||
note.comments.map { |c| c.author }.uniq.each do |user|
|
||||
if user and user != @user
|
||||
Notifier.note_comment_notification(comment, user).deliver
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -5,6 +5,7 @@ class SiteController < ApplicationController
|
|||
before_filter :authorize_web
|
||||
before_filter :set_locale
|
||||
before_filter :require_user, :only => [:edit]
|
||||
before_filter :require_oauth, :only => [:index]
|
||||
|
||||
def index
|
||||
unless STATUS == :database_readonly or STATUS == :database_offline
|
||||
|
|
|
@ -26,6 +26,7 @@ module ApplicationHelper
|
|||
css << ".hide_if_user_#{@user.id} { display: none !important }" if @user;
|
||||
css << ".show_if_user_#{@user.id} { display: inline !important }" if @user;
|
||||
css << ".hide_unless_administrator { display: none !important }" unless @user and @user.administrator?;
|
||||
css << ".hide_unless_moderator { display: none !important }" unless @user and @user.moderator?;
|
||||
|
||||
return content_tag(:style, css, :type => "text/css")
|
||||
end
|
||||
|
@ -95,4 +96,8 @@ module ApplicationHelper
|
|||
I18n.t("html.dir")
|
||||
end
|
||||
end
|
||||
|
||||
def friendly_date(date)
|
||||
content_tag(:span, time_ago_in_words(date), :title => l(date, :format => :friendly))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,22 +22,6 @@ module GeocoderHelper
|
|||
end
|
||||
|
||||
def describe_location(lat, lon, zoom = nil, language = nil)
|
||||
zoom = zoom || 14
|
||||
language = language || request.user_preferred_languages.join(',')
|
||||
url = "http://nominatim.openstreetmap.org/reverse?lat=#{lat}&lon=#{lon}&zoom=#{zoom}&accept-language=#{language}"
|
||||
|
||||
begin
|
||||
response = OSM::Timer.timeout(4) do
|
||||
REXML::Document.new(Net::HTTP.get(URI.parse(url)))
|
||||
end
|
||||
rescue Exception
|
||||
response = nil
|
||||
end
|
||||
|
||||
if response and result = response.get_text("reversegeocode/result")
|
||||
result.to_s
|
||||
else
|
||||
"#{number_with_precision(lat, :precision => 3)}, #{number_with_precision(lon, :precision => 3)}"
|
||||
end
|
||||
Nominatim.describe_location(lat, lon, zoom, language)
|
||||
end
|
||||
end
|
||||
|
|
17
app/helpers/note_helper.rb
Normal file
17
app/helpers/note_helper.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
module NoteHelper
|
||||
def note_event(at, by)
|
||||
if by.nil?
|
||||
I18n.t("browse.note.at_html", :when => friendly_date(at)).html_safe
|
||||
else
|
||||
I18n.t("browse.note.at_by_html", :when => friendly_date(at), :user => note_author(by)).html_safe
|
||||
end
|
||||
end
|
||||
|
||||
def note_author(author, link_options = {})
|
||||
if author.nil?
|
||||
""
|
||||
else
|
||||
link_to h(author.display_name), link_options.merge({:controller => "user", :action => "view", :display_name => author.display_name})
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,7 +16,8 @@ class ClientApplication < ActiveRecord::Base
|
|||
attr_accessible :name, :url, :support_url, :callback_url,
|
||||
:allow_read_prefs, :allow_write_prefs,
|
||||
:allow_write_diary, :allow_write_api,
|
||||
:allow_read_gpx, :allow_write_gpx
|
||||
:allow_read_gpx, :allow_write_gpx,
|
||||
:allow_write_notes
|
||||
|
||||
before_validation :generate_keys, :on => :create
|
||||
|
||||
|
@ -87,7 +88,8 @@ protected
|
|||
# have to say up-front what permissions they want and when users sign up they
|
||||
# can agree or not agree to each of them.
|
||||
PERMISSIONS = [:allow_read_prefs, :allow_write_prefs, :allow_write_diary,
|
||||
:allow_write_api, :allow_read_gpx, :allow_write_gpx ]
|
||||
:allow_write_api, :allow_read_gpx, :allow_write_gpx,
|
||||
:allow_write_notes]
|
||||
|
||||
def generate_keys
|
||||
self.key = OAuth::Helper.generate_key(40)[0,40]
|
||||
|
|
76
app/models/note.rb
Normal file
76
app/models/note.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
class Note < ActiveRecord::Base
|
||||
include GeoRecord
|
||||
|
||||
has_many :comments, :class_name => "NoteComment",
|
||||
:foreign_key => :note_id,
|
||||
:order => :created_at,
|
||||
:conditions => { :visible => true }
|
||||
|
||||
validates_presence_of :id, :on => :update
|
||||
validates_uniqueness_of :id
|
||||
validates_numericality_of :latitude, :only_integer => true
|
||||
validates_numericality_of :longitude, :only_integer => true
|
||||
validates_presence_of :closed_at if :status == "closed"
|
||||
validates_inclusion_of :status, :in => ["open", "closed", "hidden"]
|
||||
validate :validate_position
|
||||
|
||||
attr_accessible :lat, :lon
|
||||
|
||||
after_initialize :set_defaults
|
||||
|
||||
# Sanity check the latitude and longitude and add an error if it's broken
|
||||
def validate_position
|
||||
errors.add(:base, "Note is not in the world") unless in_world?
|
||||
end
|
||||
|
||||
# Close a note
|
||||
def close
|
||||
self.status = "closed"
|
||||
self.closed_at = Time.now.getutc
|
||||
self.save
|
||||
end
|
||||
|
||||
# Return a flattened version of the comments for a note
|
||||
def flatten_comment(separator_char, upto_timestamp = :nil)
|
||||
resp = ""
|
||||
comment_no = 1
|
||||
self.comments.each do |comment|
|
||||
next if upto_timestamp != :nil and comment.created_at > upto_timestamp
|
||||
resp += (comment_no == 1 ? "" : separator_char)
|
||||
resp += comment.body if comment.body
|
||||
resp += " [ "
|
||||
resp += comment.author.display_name if comment.author
|
||||
resp += " " + comment.created_at.to_s + " ]"
|
||||
comment_no += 1
|
||||
end
|
||||
|
||||
return resp
|
||||
end
|
||||
|
||||
# Check if a note is visible
|
||||
def visible?
|
||||
status != "hidden"
|
||||
end
|
||||
|
||||
# Check if a note is closed
|
||||
def closed?
|
||||
not closed_at.nil?
|
||||
end
|
||||
|
||||
# Return the author object, derived from the first comment
|
||||
def author
|
||||
self.comments.first.author
|
||||
end
|
||||
|
||||
# Return the author IP address, derived from the first comment
|
||||
def author_ip
|
||||
self.comments.first.author_ip
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Fill in default values for new notes
|
||||
def set_defaults
|
||||
self.status = "open" unless self.attribute_present?(:status)
|
||||
end
|
||||
end
|
17
app/models/note_comment.rb
Normal file
17
app/models/note_comment.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
class NoteComment < ActiveRecord::Base
|
||||
belongs_to :note, :foreign_key => :note_id, :touch => true
|
||||
belongs_to :author, :class_name => "User", :foreign_key => :author_id
|
||||
|
||||
validates_presence_of :id, :on => :update
|
||||
validates_uniqueness_of :id
|
||||
validates_presence_of :note_id
|
||||
validates_associated :note
|
||||
validates_presence_of :visible
|
||||
validates_associated :author
|
||||
validates_inclusion_of :event, :in => [ "opened", "closed", "reopened", "commented", "hidden" ]
|
||||
|
||||
# Return the comment text
|
||||
def body
|
||||
RichText.new("text", read_attribute(:body))
|
||||
end
|
||||
end
|
|
@ -6,7 +6,7 @@ class Notifier < ActionMailer::Base
|
|||
|
||||
def signup_confirm(user, token)
|
||||
@locale = user.preferred_language_from(I18n.available_locales)
|
||||
|
||||
|
||||
# If we are passed an email address verification token, create
|
||||
# the confirumation URL for account activation.
|
||||
#
|
||||
|
@ -19,7 +19,7 @@ class Notifier < ActionMailer::Base
|
|||
:display_name => user.display_name,
|
||||
:confirm_string => token.token)
|
||||
end
|
||||
|
||||
|
||||
mail :to => user.email,
|
||||
:subject => I18n.t('notifier.signup_confirm.subject', :locale => @locale)
|
||||
end
|
||||
|
@ -67,7 +67,7 @@ class Notifier < ActionMailer::Base
|
|||
mail :to => trace.user.email,
|
||||
:subject => I18n.t('notifier.gpx_notification.failure.subject', :locale => @locale)
|
||||
end
|
||||
|
||||
|
||||
def message_notification(message)
|
||||
@locale = message.recipient.preferred_language_from(I18n.available_locales)
|
||||
@to_user = message.recipient.display_name
|
||||
|
@ -123,6 +123,28 @@ class Notifier < ActionMailer::Base
|
|||
:subject => I18n.t('notifier.friend_notification.subject', :user => friend.befriender.display_name, :locale => @locale)
|
||||
end
|
||||
|
||||
def note_comment_notification(comment, recipient)
|
||||
@locale = recipient.preferred_language_from(I18n.available_locales)
|
||||
@noteurl = browse_note_url(comment.note, :host => SERVER_URL)
|
||||
@place = Nominatim.describe_location(comment.note.lat, comment.note.lon, 14, @locale)
|
||||
@comment = comment.body
|
||||
@owner = recipient == comment.note.author
|
||||
|
||||
if comment.author
|
||||
@commenter = comment.author.display_name
|
||||
else
|
||||
@commenter = I18n.t("notifier.note_comment_notification.anonymous")
|
||||
end
|
||||
|
||||
if @owner
|
||||
subject = I18n.t('notifier.note_comment_notification.subject_own', :commenter => @commenter)
|
||||
else
|
||||
subject = I18n.t('notifier.note_comment_notification.subject_other', :commenter => @commenter)
|
||||
end
|
||||
|
||||
mail :to => recipient.email, :subject => subject
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def from_address(name, type, id, digest)
|
||||
|
|
|
@ -12,6 +12,8 @@ class User < ActiveRecord::Base
|
|||
has_many :tokens, :class_name => "UserToken"
|
||||
has_many :preferences, :class_name => "UserPreference"
|
||||
has_many :changesets, :order => 'created_at DESC'
|
||||
has_many :note_comments, :foreign_key => :author_id
|
||||
has_many :notes, :through => :note_comments
|
||||
|
||||
has_many :client_applications
|
||||
has_many :oauth_tokens, :class_name => "OauthToken", :order => "authorized_at desc", :include => [:client_application]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
</iframe>
|
||||
|
||||
<div id="browse_map" class='clearfix content_map'>
|
||||
<% if map.instance_of? Changeset or (map.instance_of? Node and map.version > 1) or map.visible %>
|
||||
<% if map.instance_of? Changeset or (map.instance_of? Node and map.version > 1) or map.visible? %>
|
||||
|
||||
<% content_for :head do %>
|
||||
<%= javascript_include_tag "browse" %>
|
||||
|
@ -19,6 +19,13 @@
|
|||
:maxlon => bbox.max_lon,
|
||||
:maxlat => bbox.max_lat
|
||||
}
|
||||
elsif map.instance_of? Note
|
||||
data = {
|
||||
:type => "note",
|
||||
:id => map.id,
|
||||
:lon => map.lon,
|
||||
:lat => map.lat
|
||||
}
|
||||
else
|
||||
data = {
|
||||
:type => map.class.name.downcase,
|
||||
|
@ -33,10 +40,17 @@
|
|||
|
||||
<ul class='secondary-actions clearfix'>
|
||||
<li>
|
||||
<%= link_to t("browse.map.larger.area"),
|
||||
root_path(:box => "yes"),
|
||||
:id => "area_larger_map",
|
||||
:class => "geolink bbox" %>
|
||||
<% if map.instance_of? Note -%>
|
||||
<%= link_to t("browse.map.larger.area"),
|
||||
root_path(:notes => "yes"),
|
||||
:id => "area_larger_map",
|
||||
:class => "geolink bbox" %>
|
||||
<% else -%>
|
||||
<%= link_to t("browse.map.larger.area"),
|
||||
root_path(:box => "yes"),
|
||||
:id => "area_larger_map",
|
||||
:class => "geolink bbox" %>
|
||||
<% end -%>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to h(t("browse.map.edit.area")) + content_tag(:span, "▼", :class => "menuicon"),
|
||||
|
|
67
app/views/browse/note.html.erb
Normal file
67
app/views/browse/note.html.erb
Normal file
|
@ -0,0 +1,67 @@
|
|||
<% content_for :head do %>
|
||||
<%= stylesheet_link_tag 'browse' %>
|
||||
<% end %>
|
||||
|
||||
<% content_for :heading do %>
|
||||
<h2>
|
||||
<%= image_tag "#{@note.status}_note_marker.png", :alt => @note.status %>
|
||||
<%= t "browse.note.#{@note.status}_title", :note_name => @note.id %>
|
||||
</h2>
|
||||
<% end %>
|
||||
|
||||
<%= render :partial => "navigation" %>
|
||||
|
||||
<%= render :partial => "map", :object => @note %>
|
||||
|
||||
<div class='column-1'>
|
||||
|
||||
<% if @note.comments.find { |comment| comment.author.nil? } -%>
|
||||
<div class='browse-section common warning'>
|
||||
<%= t "javascripts.notes.show.anonymous_warning" %>
|
||||
</div>
|
||||
<% end -%>
|
||||
|
||||
<div class='browse-section common'>
|
||||
<div>
|
||||
<h4><%= t "browse.note.opened" %></h4>
|
||||
<p><%= note_event(@note.created_at, @note.author) %></p>
|
||||
</div>
|
||||
|
||||
<% if @note.status == "closed" %>
|
||||
<div>
|
||||
<h4><%= t "browse.note.closed" %></h4>
|
||||
<p><%= note_event(@note.closed_at, @note.comments.last.author) %></p>
|
||||
</div>
|
||||
<% elsif @note.comments.length > 1 %>
|
||||
<div>
|
||||
<h4><%= t "browse.note.last_modified" %></h4>
|
||||
<p><%= note_event(@note.updated_at, @note.comments.last.author) %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div>
|
||||
<h4><%= t "browse.note.description" %></h4>
|
||||
<p><%= h(@note.comments.first.body) %></p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h4><%= t "browse.node_details.coordinates" %></h4>
|
||||
<p><div class="geo"><%= link_to ("<span class='latitude'>#{number_with_delimiter(@note.lat)}</span>, <span class='longitude'>#{number_with_delimiter(@note.lon)}</span>".html_safe), {:controller => 'site', :action => 'index', :lat => h(@note.lat), :lon => h(@note.lon), :zoom => "18"} %></div></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @note.comments.length > 1 %>
|
||||
<div class='browse-section clearfix'>
|
||||
<h4><%= t "browse.note.comments" %></h4>
|
||||
<ul>
|
||||
<% @note.comments[1..-1].each do |comment| %>
|
||||
<li>
|
||||
<%= comment.body.to_html %>
|
||||
<small class="deemphasize"><%= note_event(comment.created_at, comment.author) %></small>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
</div>
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
<a href="<%= url_for :controller => 'site', :action => 'index', :lat => location.latitude, :lon => location.longitude, :zoom => 14 %>">
|
||||
<abbr class="geo" title="<%= number_with_precision(location.latitude, :precision => 4) %>; <%= number_with_precision(location.longitude, :precision => 4) %>">
|
||||
<% cache(:controller => 'diary_entry', :action => 'view', :display_name => location.user.display_name, :id => location.id, :part => "location") do %>
|
||||
<%= describe_location location.latitude, location.longitude, 14, location.language_code %>
|
||||
<% end %>
|
||||
</abbr>
|
||||
</a>
|
||||
|
|
|
@ -25,16 +25,19 @@
|
|||
I18n.defaultLocale = "<%= I18n.default_locale %>";
|
||||
I18n.locale = "<%= I18n.locale %>";
|
||||
I18n.fallbacks = true;
|
||||
|
||||
<% if @user and !@user.home_lon.nil? and !@user.home_lat.nil? %>
|
||||
<% if @user and !@user.home_lon.nil? and !@user.home_lat.nil? -%>
|
||||
OSM.home = <%= { :lat => @user.home_lat, :lon => @user.home_lon }.to_json.html_safe %>;
|
||||
<% end %>
|
||||
|
||||
<% if session[:location] %>
|
||||
<% end -%>
|
||||
<% if session[:location] -%>
|
||||
OSM.location = <%= session[:location].to_json.html_safe %>;
|
||||
<% end %>
|
||||
|
||||
<% end -%>
|
||||
OSM.preferred_editor = <%= preferred_editor.to_json.html_safe %>;
|
||||
<% if @oauth -%>
|
||||
OSM.oauth_token = "<%= @oauth.token %>";
|
||||
OSM.oauth_token_secret = "<%= @oauth.secret %>";
|
||||
OSM.oauth_consumer_key = "<%= @oauth.client_application.key %>";
|
||||
OSM.oauth_consumer_secret = "<%= @oauth.client_application.secret %>";
|
||||
<% end -%>
|
||||
</script>
|
||||
<title><%= t 'layouts.project_name.title' %><%= ' | '+ @title if @title %></title>
|
||||
</head>
|
||||
|
|
12
app/views/notes/_description.html.erb
Normal file
12
app/views/notes/_description.html.erb
Normal file
|
@ -0,0 +1,12 @@
|
|||
<div>
|
||||
<% description.comments.each do |comment| -%>
|
||||
<div class="note-comment" style="margin-top: 5px">
|
||||
<% if comment.author.nil? -%>
|
||||
<div class="note-comment-description" style="font-size: smaller; color: #999999"><%= t "note.description.#{comment.event}_at", :when => friendly_date(comment.created_at) %></div>
|
||||
<% else -%>
|
||||
<div class="note-comment-description" style="font-size: smaller; color: #999999"><%= t "note.description.#{comment.event}_at_by", :when => friendly_date(comment.created_at), :user => note_author(comment.author, :only_path => false) %></div>
|
||||
<% end -%>
|
||||
<div class="note-comment-text"><%= comment.body %></div>
|
||||
</div>
|
||||
<% end -%>
|
||||
</div>
|
18
app/views/notes/_note.gpx.builder
Normal file
18
app/views/notes/_note.gpx.builder
Normal file
|
@ -0,0 +1,18 @@
|
|||
xml.wpt("lon" => note.lon, "lat" => note.lat) do
|
||||
xml.desc do
|
||||
xml.cdata! render(:partial => "description", :object => note, :formats => [ :html ])
|
||||
end
|
||||
|
||||
xml.extension do
|
||||
if note.status = "open"
|
||||
xml.closed "0"
|
||||
else
|
||||
xml.closed "1"
|
||||
end
|
||||
|
||||
xml.id note.id
|
||||
xml.url note_url(note, :format => params[:format])
|
||||
xml.comment_url comment_note_url(note, :format => params[:format])
|
||||
xml.close_url close_note_url(note, :format => params[:format])
|
||||
end
|
||||
end
|
33
app/views/notes/_note.json.jsonify
Normal file
33
app/views/notes/_note.json.jsonify
Normal file
|
@ -0,0 +1,33 @@
|
|||
json.type "Feature"
|
||||
|
||||
json.geometry do
|
||||
json.type "Point"
|
||||
json.coordinates [ note.lon, note.lat ]
|
||||
end
|
||||
|
||||
json.properties do
|
||||
json.id note.id
|
||||
json.url note_url(note, :format => params[:format])
|
||||
json.comment_url comment_note_url(note, :format => params[:format])
|
||||
json.close_url close_note_url(note, :format => params[:format])
|
||||
json.date_created note.created_at
|
||||
json.status note.status
|
||||
json.closed_at note.closed_at if note.status == "closed"
|
||||
|
||||
json.comments(note.comments) do |comment|
|
||||
json.date comment.created_at
|
||||
|
||||
if comment.author
|
||||
json.uid comment.author.id
|
||||
json.user comment.author.display_name
|
||||
json.user_url user_url(:display_name => comment.author.display_name)
|
||||
end
|
||||
|
||||
json.action comment.event
|
||||
|
||||
if comment.body
|
||||
json.text comment.body.to_text
|
||||
json.html comment.body.to_html
|
||||
end
|
||||
end
|
||||
end
|
24
app/views/notes/_note.rss.builder
Normal file
24
app/views/notes/_note.rss.builder
Normal file
|
@ -0,0 +1,24 @@
|
|||
xml.item do
|
||||
location = describe_location(note.lat, note.lon, 14, locale)
|
||||
|
||||
if note.status == "closed"
|
||||
xml.title t('note.rss.closed', :place => location)
|
||||
elsif note.comments.length > 1
|
||||
xml.title t('note.rss.comment', :place => location)
|
||||
else
|
||||
xml.title t('note.rss.new', :place => location)
|
||||
end
|
||||
|
||||
xml.link browse_note_url(note)
|
||||
xml.guid note_url(note)
|
||||
xml.description render(:partial => "description", :object => note, :formats => [ :html ])
|
||||
|
||||
if note.author
|
||||
xml.author note.author_display_name
|
||||
end
|
||||
|
||||
xml.pubDate note.updated_at.to_s(:rfc822)
|
||||
xml.geo :lat, note.lat
|
||||
xml.geo :long, note.lon
|
||||
xml.georss :point, "#{note.lat} #{note.lon}"
|
||||
end
|
31
app/views/notes/_note.xml.builder
Normal file
31
app/views/notes/_note.xml.builder
Normal file
|
@ -0,0 +1,31 @@
|
|||
xml.note("lon" => note.lon, "lat" => note.lat) do
|
||||
xml.id note.id
|
||||
xml.url note_url(note, :format => params[:format])
|
||||
xml.comment_url comment_note_url(note, :format => params[:format])
|
||||
xml.close_url close_note_url(note, :format => params[:format])
|
||||
xml.date_created note.created_at
|
||||
xml.status note.status
|
||||
|
||||
if note.status == "closed"
|
||||
xml.date_closed note.closed_at
|
||||
end
|
||||
|
||||
xml.comments do
|
||||
note.comments.each do |comment|
|
||||
xml.comment do
|
||||
xml.date comment.created_at
|
||||
|
||||
if comment.author
|
||||
xml.uid comment.author.id
|
||||
xml.user comment.author.display_name
|
||||
xml.user_url user_url(:display_name => comment.author.display_name)
|
||||
end
|
||||
|
||||
if comment.body
|
||||
xml.text comment.body.to_text
|
||||
xml.html comment.body.to_html
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
app/views/notes/_notes_paging_nav.html.erb
Normal file
17
app/views/notes/_notes_paging_nav.html.erb
Normal file
|
@ -0,0 +1,17 @@
|
|||
<p>
|
||||
|
||||
<% if @page > 1 %>
|
||||
<%= link_to t('changeset.changeset_paging_nav.previous'), params.merge({ :page => @page - 1 }) %>
|
||||
<% else %>
|
||||
<%= t('changeset.changeset_paging_nav.previous') %>
|
||||
<% end %>
|
||||
|
||||
| <%= t('changeset.changeset_paging_nav.showing_page', :page => @page) %> |
|
||||
|
||||
<% if @notes.size < @page_size %>
|
||||
<%= t('changeset.changeset_paging_nav.next') %>
|
||||
<% else %>
|
||||
<%= link_to t('changeset.changeset_paging_nav.next'), params.merge({ :page => @page + 1 }) %>
|
||||
<% end %>
|
||||
|
||||
</p>
|
1
app/views/notes/_user.html.erb
Normal file
1
app/views/notes/_user.html.erb
Normal file
|
@ -0,0 +1 @@
|
|||
<%= link_to user.display_name, :controller => "user", :action => "view", :display_name => user.display_name %>
|
52
app/views/notes/feed.rss.builder
Normal file
52
app/views/notes/feed.rss.builder
Normal file
|
@ -0,0 +1,52 @@
|
|||
xml.instruct!
|
||||
|
||||
xml.rss("version" => "2.0",
|
||||
"xmlns:geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
|
||||
"xmlns:georss" => "http://www.georss.org/georss") do
|
||||
xml.channel do
|
||||
xml.title t('note.rss.title')
|
||||
xml.description t('note.rss.description_area', :min_lat => @min_lat, :min_lon => @min_lon, :max_lat => @max_lat, :max_lon => @max_lon )
|
||||
xml.link url_for(:controller => "site", :action => "index", :only_path => false)
|
||||
|
||||
@comments.each do |comment|
|
||||
location = describe_location(comment.note.lat, comment.note.lon, 14, locale)
|
||||
|
||||
xml.item do
|
||||
if comment.event == "closed"
|
||||
xml.title t('note.rss.closed', :place => location)
|
||||
elsif comment.event == "commented"
|
||||
xml.title t('note.rss.comment', :place => location)
|
||||
elsif comment.event == "opened"
|
||||
xml.title t('note.rss.new', :place => location)
|
||||
else
|
||||
xml.title "unknown event"
|
||||
end
|
||||
|
||||
xml.link url_for(:controller => "browse", :action => "note", :id => comment.note.id, :only_path => false)
|
||||
xml.guid url_for(:controller => "browse", :action => "note", :id => comment.note.id, :only_path => false)
|
||||
|
||||
description_text = ""
|
||||
|
||||
if comment.event == "commented" and not comment.nil?
|
||||
description_text += "<b>Comment:</b><br>"
|
||||
description_text += comment.body.to_html
|
||||
description_text += "<br>"
|
||||
end
|
||||
|
||||
description_text += "<b>Full note:</b><br>"
|
||||
description_text += comment.note.flatten_comment("<br>", comment.created_at)
|
||||
|
||||
xml.description description_text
|
||||
|
||||
if comment.author
|
||||
xml.author comment.author.display_name
|
||||
end
|
||||
|
||||
xml.pubDate comment.created_at.to_s(:rfc822)
|
||||
xml.geo :lat, comment.note.lat
|
||||
xml.geo :long, comment.note.lon
|
||||
xml.georss :point, "#{comment.note.lat} #{comment.note.lon}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
7
app/views/notes/index.gpx.builder
Normal file
7
app/views/notes/index.gpx.builder
Normal file
|
@ -0,0 +1,7 @@
|
|||
xml.instruct!
|
||||
|
||||
xml.gpx("version" => "1.1",
|
||||
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
|
||||
"xsi:schemaLocation" => "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd") do
|
||||
xml << render(:partial => "note", :collection => @notes)
|
||||
end
|
5
app/views/notes/index.json.jsonify
Normal file
5
app/views/notes/index.json.jsonify
Normal file
|
@ -0,0 +1,5 @@
|
|||
json.type "FeatureCollection"
|
||||
|
||||
json.features(@notes) do |note|
|
||||
json.ingest! render(:partial => "note", :object => note)
|
||||
end
|
13
app/views/notes/index.rss.builder
Normal file
13
app/views/notes/index.rss.builder
Normal file
|
@ -0,0 +1,13 @@
|
|||
xml.instruct!
|
||||
|
||||
xml.rss("version" => "2.0",
|
||||
"xmlns:geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
|
||||
"xmlns:georss" => "http://www.georss.org/georss") do
|
||||
xml.channel do
|
||||
xml.title t('note.rss.title')
|
||||
xml.description t('note.rss.description_area', :min_lat => @min_lat, :min_lon => @min_lon, :max_lat => @max_lat, :max_lon => @max_lon )
|
||||
xml.link url_for(:controller => "site", :action => "index", :only_path => false)
|
||||
|
||||
xml << render(:partial => "note", :collection => @notes)
|
||||
end
|
||||
end
|
3
app/views/notes/index.xml.builder
Normal file
3
app/views/notes/index.xml.builder
Normal file
|
@ -0,0 +1,3 @@
|
|||
xml.instruct!
|
||||
|
||||
xml << render(:partial => "note", :collection => @notes)
|
35
app/views/notes/mine.html.erb
Normal file
35
app/views/notes/mine.html.erb
Normal file
|
@ -0,0 +1,35 @@
|
|||
<% content_for :heading do %>
|
||||
<h2><%= @heading %></h2>
|
||||
<p><%= raw @description %></p>
|
||||
<% end %>
|
||||
|
||||
<%= render :partial => 'notes_paging_nav' %>
|
||||
|
||||
<table class="note_list">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th><%= t'note.mine.id' %></th>
|
||||
<th><%= t'note.mine.creator' %></th>
|
||||
<th><%= t'note.mine.description' %></th>
|
||||
<th><%= t'note.mine.created_at' %></th>
|
||||
<th><%= t'note.mine.last_changed' %></th>
|
||||
</tr>
|
||||
<% @notes.each do |note| -%>
|
||||
<tr<% if note.author != @user2 %> class="creator"<% end %>>
|
||||
<td>
|
||||
<% if note.status == "closed" %>
|
||||
<%= image_tag("closed_note_marker.png", :alt => 'closed') %>
|
||||
<% else %>
|
||||
<%= image_tag("open_note_marker.png", :alt => 'open') %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td><%= link_to note.id.to_s, :controller => "browse", :action => "note", :id => note.id %></td>
|
||||
<td><%= note_author(note.author) %></td>
|
||||
<td><%= note.comments.first.body.to_html %></td>
|
||||
<td><%= t 'note.mine.ago_html', :when => friendly_date(note.created_at) %></td>
|
||||
<td><%= t 'note.mine.ago_html', :when => friendly_date(note.updated_at) %></td>
|
||||
</tr>
|
||||
<% end -%>
|
||||
</table>
|
||||
|
||||
<%= render :partial => 'notes_paging_nav' %>
|
7
app/views/notes/show.gpx.builder
Normal file
7
app/views/notes/show.gpx.builder
Normal file
|
@ -0,0 +1,7 @@
|
|||
xml.instruct!
|
||||
|
||||
xml.gpx("version" => "1.1",
|
||||
"xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
|
||||
"xsi:schemaLocation" => "http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd") do
|
||||
xml << render(:partial => "note", :object => @note)
|
||||
end
|
1
app/views/notes/show.json.jsonify
Normal file
1
app/views/notes/show.json.jsonify
Normal file
|
@ -0,0 +1 @@
|
|||
json.ingest! render(:partial => "note", :object => @note)
|
13
app/views/notes/show.rss.builder
Normal file
13
app/views/notes/show.rss.builder
Normal file
|
@ -0,0 +1,13 @@
|
|||
xml.instruct!
|
||||
|
||||
xml.rss("version" => "2.0",
|
||||
"xmlns:geo" => "http://www.w3.org/2003/01/geo/wgs84_pos#",
|
||||
"xmlns:georss" => "http://www.georss.org/georss") do
|
||||
xml.channel do
|
||||
xml.title t('note.rss.title')
|
||||
xml.description t('note.rss.description_item', :id => @note.id)
|
||||
xml.link url_for(:controller => "site", :action => "index", :only_path => false)
|
||||
|
||||
xml << render(:partial => "note", :object => @note)
|
||||
end
|
||||
end
|
3
app/views/notes/show.xml.builder
Normal file
3
app/views/notes/show.xml.builder
Normal file
|
@ -0,0 +1,3 @@
|
|||
xml.instruct!
|
||||
|
||||
xml << render(:partial => "note", :object => @note)
|
13
app/views/notifier/note_comment_notification.html.erb
Normal file
13
app/views/notifier/note_comment_notification.html.erb
Normal file
|
@ -0,0 +1,13 @@
|
|||
<p><%= t 'notifier.note_comment_notification.greeting' %></p>
|
||||
|
||||
<% if @owner %>
|
||||
<p><%= t 'notifier.note_comment_notification.your_note', :commenter => @commenter, :place => @place %></p>
|
||||
<% else %>
|
||||
<p><%= t 'notifier.note_comment_notification.commented_note', :commenter => @commenter, :place => @place %></p>
|
||||
<% end %>
|
||||
|
||||
==
|
||||
<%= @comment.to_html %>
|
||||
==
|
||||
|
||||
<p><%= raw t 'notifier.note_comment_notification.details', :url => link_to(@noteurl, @noteurl) %></p>
|
13
app/views/notifier/note_comment_notification.text.erb
Normal file
13
app/views/notifier/note_comment_notification.text.erb
Normal file
|
@ -0,0 +1,13 @@
|
|||
<%= t 'notifier.note_comment_notification.greeting' %>
|
||||
|
||||
<% if @owner %>
|
||||
<%= t 'notifier.note_comment_notification.your_note', :commenter => @commenter, :place => @place %>
|
||||
<% else %>
|
||||
<%= t 'notifier.note_comment_notification.commented_note', :commenter => @commenter, :place => @place %>
|
||||
<% end %>
|
||||
|
||||
==
|
||||
<%= @comment.to_text %>
|
||||
==
|
||||
|
||||
<%= t 'notifier.note_comment_notification.details', :url => @noteurl %>
|
|
@ -19,8 +19,16 @@
|
|||
|
||||
<div id="map">
|
||||
<div id="permalink">
|
||||
<a href="/" id="permalinkanchor" class="geolink llz layers object"><%= t 'site.index.permalink' %></a>
|
||||
<a href="/" id="shortlinkanchor"><%= t 'site.index.shortlink' %></a>
|
||||
<ul class="secondary-actions">
|
||||
<li><a href="/" id="permalinkanchor" class="geolink llz layers object"><%= t 'site.index.permalink' %></a></li>
|
||||
<li><a href="/" id="shortlinkanchor"><%= t 'site.index.shortlink' %></a></li>
|
||||
<li><%= link_to t("site.index.createnote"), notes_url(:format => :json),
|
||||
:id => "createnoteanchor",
|
||||
:data => { :minzoom => 12 },
|
||||
:title => "javascripts.site.createnote_tooltip",
|
||||
:class => "geolink"
|
||||
%></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -38,3 +46,4 @@
|
|||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
<%= link_to t('user.view.my edits'), :controller => 'changeset', :action => 'list', :display_name => @user.display_name %>
|
||||
<span class='count-number'><%= number_with_delimiter(@user.changesets.size) %></span>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to t('user.view.my notes'), :controller => 'notes', :action=> 'mine' %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to t('user.view.my traces'), :controller => 'trace', :action=>'mine' %>
|
||||
<span class='count-number'><%= number_with_delimiter(@user.traces.size) %></span>
|
||||
|
@ -49,6 +52,9 @@
|
|||
<%= link_to t('user.view.edits'), :controller => 'changeset', :action => 'list', :display_name => @this_user.display_name %>
|
||||
<span class='count-number'><%= number_with_delimiter(@this_user.changesets.size) %></span>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to t('user.view.notes'), :controller => 'notes', :action=> 'mine' %>
|
||||
</li>
|
||||
<li>
|
||||
<%= link_to t('user.view.traces'), :controller => 'trace', :action => 'list', :display_name => @this_user.display_name %>
|
||||
<span class='count-number'><%= number_with_delimiter(@this_user.traces.size) %></span>
|
||||
|
|
|
@ -27,6 +27,8 @@ defaults: &defaults
|
|||
max_number_of_nodes: 50000
|
||||
# Maximum number of nodes that can be in a way (checked on save)
|
||||
max_number_of_way_nodes: 2000
|
||||
# The maximum area you're allowed to request notes from, in square degrees
|
||||
max_note_request_area: 25
|
||||
# Zoom level to use for postcode results from the geocoder
|
||||
postcode_zoom: 15
|
||||
# Zoom level to use for geonames results from the geocoder
|
||||
|
@ -74,6 +76,8 @@ defaults: &defaults
|
|||
default_editor: "potlatch2"
|
||||
# OAuth consumer key for Potlatch 2
|
||||
#potlatch2_key: ""
|
||||
# OAuth consumer key for the web site
|
||||
#oauth_key: ""
|
||||
# Whether to require users to view the CTs before continuing to edit...
|
||||
require_terms_seen: false
|
||||
# Whether to require users to agree to the CTs before editing
|
||||
|
|
|
@ -23,6 +23,8 @@
|
|||
translations:
|
||||
- file: "app/assets/javascripts/i18n/translations.js"
|
||||
only:
|
||||
- "*.date"
|
||||
- "*.time"
|
||||
- "*.browse.start_rjs.*"
|
||||
- "*.export.start_rjs.*"
|
||||
- "*.javascripts.*"
|
||||
|
|
32
config/initializers/action_view.rb
Normal file
32
config/initializers/action_view.rb
Normal file
|
@ -0,0 +1,32 @@
|
|||
#
|
||||
# Make :formats work when rendering one partial from another
|
||||
#
|
||||
# Taken from https://github.com/rails/rails/pull/6626
|
||||
#
|
||||
module ActionView
|
||||
class AbstractRenderer #:nodoc:
|
||||
def prepend_formats(formats)
|
||||
formats = Array(formats)
|
||||
return if formats.empty?
|
||||
@lookup_context.formats = formats | @lookup_context.formats
|
||||
end
|
||||
end
|
||||
|
||||
class PartialRenderer
|
||||
def setup_with_formats(context, options, block)
|
||||
prepend_formats(options[:formats])
|
||||
setup_without_formats(context, options, block)
|
||||
end
|
||||
|
||||
alias_method_chain :setup, :formats
|
||||
end
|
||||
|
||||
class TemplateRenderer
|
||||
def render_with_formats(context, options)
|
||||
prepend_formats(options[:formats])
|
||||
render_without_formats(context, options)
|
||||
end
|
||||
|
||||
alias_method_chain :render, :formats
|
||||
end
|
||||
end
|
|
@ -4,3 +4,4 @@
|
|||
# Mime::Type.register "text/richtext", :rtf
|
||||
# Mime::Type.register_alias "text/html", :iphone
|
||||
Mime::Type.register "application/x-amf", :amf
|
||||
Mime::Type.register "application/gpx+xml", :gpx
|
||||
|
|
|
@ -953,6 +953,22 @@ de:
|
|||
history_disabled_tooltip: Reinzoomen um Änderungen für diesen Bereich anzuzeigen
|
||||
history_tooltip: Änderungen für diesen Bereich anzeigen
|
||||
history_zoom_alert: Du musst näher heranzoomen, um die Chronik zu sehen
|
||||
osb:
|
||||
Fixed Error: Behobener Fehler
|
||||
Unresolved Error: Offener Fehler
|
||||
Description: Beschreibung
|
||||
Comment: Kommentar
|
||||
Has been fixed: Der Fehler wurde bereits behoben. Es kann jedoch bis zu einigen Tagen dauern, bis die Kartenansicht aktualisiert wird.
|
||||
Comment/Close: Kommentieren/Schließen
|
||||
Nickname: Benutzername
|
||||
Add comment: Kommentar hinzufügen
|
||||
Mark as fixed: Als behoben markieren
|
||||
Cancel: Abbrechen
|
||||
Create OpenStreetBug: OpenStreetBug melden
|
||||
Create bug: Bug anlegen
|
||||
Bug description: Fehlerbeschreibung
|
||||
Create: Anlegeeen
|
||||
Permalink: Permalink
|
||||
layouts:
|
||||
community: Gemeinschaft
|
||||
community_blogs: Blogs
|
||||
|
|
|
@ -121,6 +121,8 @@ en:
|
|||
next_relation_tooltip: "Next relation"
|
||||
prev_changeset_tooltip: "Previous changeset"
|
||||
next_changeset_tooltip: "Next changeset"
|
||||
prev_note_tooltip: "Previous note"
|
||||
next_note_tooltip: "Next note"
|
||||
changeset_details:
|
||||
created_at: "Created at:"
|
||||
closed_at: "Closed at:"
|
||||
|
@ -157,11 +159,13 @@ en:
|
|||
node: "View node on larger map"
|
||||
way: "View way on larger map"
|
||||
relation: "View relation on larger map"
|
||||
note: "View note on larger map"
|
||||
edit:
|
||||
area: "Edit area"
|
||||
node: "Edit node"
|
||||
way: "Edit way"
|
||||
relation: "Edit relation"
|
||||
note: "Edit note"
|
||||
node_details:
|
||||
coordinates: "Coordinates:"
|
||||
part_of: "Part of:"
|
||||
|
@ -221,6 +225,7 @@ en:
|
|||
download_xml: "Download XML"
|
||||
view_history: "View history"
|
||||
start_rjs:
|
||||
notes_layer_name: "Browse Notes"
|
||||
data_layer_name: "Browse Map Data"
|
||||
data_frame_title: "Data"
|
||||
zoom_or_select: "Zoom in or select an area of the map to view"
|
||||
|
@ -280,6 +285,16 @@ en:
|
|||
download_xml: "Download XML"
|
||||
view_history: "View history"
|
||||
edit: "Edit way"
|
||||
note:
|
||||
open_title: "Unresolved issue: %{note_name}"
|
||||
closed_title: "Resolved issue: %{note_name}"
|
||||
opened: "Opened:"
|
||||
last_modified: "Last modified:"
|
||||
closed: "Closed:"
|
||||
at_html: "%{when} ago"
|
||||
at_by_html: "%{when} ago by %{user}"
|
||||
description: "Description:"
|
||||
comments: "Comments:"
|
||||
changeset:
|
||||
changeset_paging_nav:
|
||||
showing_page: "Page %{page}"
|
||||
|
@ -1192,6 +1207,14 @@ en:
|
|||
greeting: "Hi,"
|
||||
hopefully_you: "Someone (possibly you) has asked for the password to be reset on this email address's openstreetmap.org account."
|
||||
click_the_link: "If this is you, please click the link below to reset your password."
|
||||
note_comment_notification:
|
||||
anonymous: An anonymous user
|
||||
subject_own: "[OpenStreetMap] %{commenter} has commented on one of your notes"
|
||||
subject_other: "[OpenStreetMap] %{commenter} has commented on a note you are interested in"
|
||||
greeting: "Hi,"
|
||||
your_note: "%{commenter} has left a comment on one of your map notes near %{place}."
|
||||
commented_note: "%{commenter} has left a comment on a map note you have commented on. The note is near %{place}."
|
||||
details: "More details about the note can be found at %{url}."
|
||||
message:
|
||||
inbox:
|
||||
title: "Inbox"
|
||||
|
@ -1266,6 +1289,7 @@ en:
|
|||
js_2: "OpenStreetMap uses JavaScript for its slippy map."
|
||||
permalink: Permalink
|
||||
shortlink: Shortlink
|
||||
createnote: Add a note
|
||||
license:
|
||||
copyright: "Copyright OpenStreetMap and contributors, under an open license"
|
||||
license_url: "http://openstreetmap.org/copyright"
|
||||
|
@ -1676,6 +1700,7 @@ en:
|
|||
new diary entry: new diary entry
|
||||
my edits: my edits
|
||||
my traces: my traces
|
||||
my notes: my map notes
|
||||
my settings: my settings
|
||||
my comments: my comments
|
||||
oauth settings: oauth settings
|
||||
|
@ -1685,6 +1710,7 @@ en:
|
|||
diary: diary
|
||||
edits: edits
|
||||
traces: traces
|
||||
notes: map notes
|
||||
remove as friend: unfriend
|
||||
add as friend: add friend
|
||||
mapper since: "Mapper since:"
|
||||
|
@ -1955,6 +1981,33 @@ en:
|
|||
back: "View all blocks"
|
||||
revoker: "Revoker:"
|
||||
needs_view: "The user needs to log in before this block will be cleared."
|
||||
note:
|
||||
description:
|
||||
opened_at: "Created %{when} ago"
|
||||
opened_at_by: "Created %{when} ago by %{user}"
|
||||
commented_at: "Updated %{when} ago"
|
||||
commented_at_by: "Updated %{when} ago by %{user}"
|
||||
closed_at: "Resolved %{when} ago"
|
||||
closed_at_by: "Resolved %{when} ago by %{user}"
|
||||
reopened_at: "Reactivated %{when} ago"
|
||||
reopened_at_by: "Reactivated %{when} ago by %{user}"
|
||||
rss:
|
||||
title: "OpenStreetMap Notes"
|
||||
description_area: "A list of notes, reported, commented on or closed in your area [(%{min_lat}|%{min_lon}) -- (%{max_lat}|%{max_lon})]"
|
||||
description_item: "An rss feed for note %{id}"
|
||||
closed: "closed note (near %{place})"
|
||||
new: "new note (near %{place})"
|
||||
comment: "new comment (near %{place})"
|
||||
mine:
|
||||
title: "Notes submitted or commented on by %{user}"
|
||||
heading: "%{user}'s notes"
|
||||
subheading: "Notes submitted or commented on by %{user}"
|
||||
id: "Id"
|
||||
creator: "Creator"
|
||||
description: "Description"
|
||||
created_at: "Created at"
|
||||
last_changed: "Last changed"
|
||||
ago_html: "%{when} ago"
|
||||
javascripts:
|
||||
map:
|
||||
base:
|
||||
|
@ -1971,6 +2024,28 @@ en:
|
|||
history_tooltip: View edits for this area
|
||||
history_disabled_tooltip: Zoom in to view edits for this area
|
||||
history_zoom_alert: You must zoom in to view edits for this area
|
||||
createnote_tooltip: Add a note to the map
|
||||
createnote_disabled_tooltip: Zoom in to add a note to the map
|
||||
createnote_zoom_alert: You must zoom in to add a note to the map
|
||||
notes:
|
||||
new:
|
||||
intro: "In order to improve the map the information you enter is shown to other mappers, so please be as descriptive and precise as possible when moving the marker to the correct position and entering your note below."
|
||||
add: Add Note
|
||||
show:
|
||||
title: Note %{id}
|
||||
anonymous_warning: This note includes comments from anonymous users which should be independently verified.
|
||||
opened_by: "created by <a href='%{user_url}'>%{user}</a> at %{time}"
|
||||
opened_by_anonymous: "created by anonymous at %{time}"
|
||||
commented_by: "comment from <a href='%{user_url}'>%{user}</a> at %{time}"
|
||||
commented_by_anonymous: "comment from anonymous at %{time}"
|
||||
closed_by: "resolved by <a href='%{user_url}'>%{user}</a> at %{time}"
|
||||
closed_by_anonymous: "resolved by anonymous at %{time}"
|
||||
reopened_by: "reactivated by <a href='%{user_url}'>%{user}</a> at %{time}"
|
||||
reopened_by_anonymous: "reactivated by anonymous at %{time}"
|
||||
hide: Hide
|
||||
resolve: Resolve
|
||||
comment_and_resolve: Comment & Resolve
|
||||
comment: Comment
|
||||
redaction:
|
||||
edit:
|
||||
description: "Description"
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
OpenLayers/Map.js
|
||||
OpenLayers/Control/ArgParser.js
|
||||
OpenLayers/Control/Attribution.js
|
||||
OpenLayers/Control/DragFeature.js
|
||||
OpenLayers/Control/DrawFeature.js
|
||||
OpenLayers/Control/LayerSwitcher.js
|
||||
OpenLayers/Control/Navigation.js
|
||||
|
@ -25,12 +26,16 @@ OpenLayers/Tile.js
|
|||
OpenLayers/Tile/Image.js
|
||||
OpenLayers/Feature/Vector.js
|
||||
OpenLayers/Strategy/Fixed.js
|
||||
OpenLayers/Strategy/BBOX.js
|
||||
OpenLayers/Protocol/HTTP.js
|
||||
OpenLayers/Format/QueryStringFilter.js
|
||||
OpenLayers/Format/GeoJSON.js
|
||||
OpenLayers/Format/OSM.js
|
||||
OpenLayers/Geometry/Point.js
|
||||
OpenLayers/Geometry/LinearRing.js
|
||||
OpenLayers/Handler/Point.js
|
||||
OpenLayers/Handler/RegularPolygon.js
|
||||
OpenLayers/Protocol/HTTP.js
|
||||
OpenLayers/Renderer.js
|
||||
OpenLayers/Renderer/Canvas.js
|
||||
OpenLayers/Renderer/SVG.js
|
||||
|
|
|
@ -75,11 +75,31 @@ OpenStreetMap::Application.routes.draw do
|
|||
match 'api/0.6/gpx/:id/data' => 'trace#api_data', :via => :get
|
||||
|
||||
# AMF (ActionScript) API
|
||||
|
||||
match 'api/0.6/amf/read' => 'amf#amf_read', :via => :post
|
||||
match 'api/0.6/amf/write' => 'amf#amf_write', :via => :post
|
||||
match 'api/0.6/swf/trackpoints' => 'swf#trackpoints', :via => :get
|
||||
|
||||
# Map notes API
|
||||
scope "api/0.6" do
|
||||
resources :notes, :except => [ :new, :edit, :update ], :constraints => { :id => /\d+/ }, :defaults => { :format => "xml" } do
|
||||
collection do
|
||||
get 'search'
|
||||
get 'feed', :defaults => { :format => "rss" }
|
||||
end
|
||||
|
||||
member do
|
||||
post 'comment'
|
||||
post 'close'
|
||||
end
|
||||
end
|
||||
|
||||
match 'notes/addPOIexec' => 'notes#create', :via => :post
|
||||
match 'notes/closePOIexec' => 'notes#close', :via => :post
|
||||
match 'notes/editPOIexec' => 'notes#comment', :via => :post
|
||||
match 'notes/getGPX' => 'notes#index', :via => :get, :format => "gpx"
|
||||
match 'notes/getRSSfeed' => 'notes#feed', :via => :get, :format => "rss"
|
||||
end
|
||||
|
||||
# Data browsing
|
||||
match '/browse/start' => 'browse#start', :via => :get
|
||||
match '/browse/way/:id' => 'browse#way', :via => :get, :id => /\d+/
|
||||
|
@ -89,8 +109,10 @@ OpenStreetMap::Application.routes.draw do
|
|||
match '/browse/relation/:id' => 'browse#relation', :via => :get, :id => /\d+/
|
||||
match '/browse/relation/:id/history' => 'browse#relation_history', :via => :get, :id => /\d+/
|
||||
match '/browse/changeset/:id' => 'browse#changeset', :via => :get, :as => :changeset, :id => /\d+/
|
||||
match '/browse/note/:id' => 'browse#note', :via => :get, :id => /\d+/, :as => "browse_note"
|
||||
match '/user/:display_name/edits' => 'changeset#list', :via => :get
|
||||
match '/user/:display_name/edits/feed' => 'changeset#feed', :via => :get, :format => :atom
|
||||
match '/user/:display_name/notes' => 'notes#mine', :via => :get
|
||||
match '/browse/friends' => 'changeset#list', :via => :get, :friends => true, :as => "friend_changesets"
|
||||
match '/browse/nearby' => 'changeset#list', :via => :get, :nearby => true, :as => "nearby_changesets"
|
||||
match '/browse/changesets' => 'changeset#list', :via => :get
|
||||
|
|
33
db/migrate/053_add_map_bug_tables.rb
Normal file
33
db/migrate/053_add_map_bug_tables.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
require 'migrate'
|
||||
|
||||
class AddMapBugTables < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_enumeration :map_bug_status_enum, ["open", "closed", "hidden"]
|
||||
|
||||
create_table :map_bugs do |t|
|
||||
t.column :id, :bigint, :null => false
|
||||
t.integer :latitude, :null => false
|
||||
t.integer :longitude, :null => false
|
||||
t.column :tile, :bigint, :null => false
|
||||
t.datetime :last_changed, :null => false
|
||||
t.datetime :date_created, :null => false
|
||||
t.string :nearby_place
|
||||
t.string :text
|
||||
t.column :status, :map_bug_status_enum, :null => false
|
||||
end
|
||||
|
||||
add_index :map_bugs, [:tile, :status], :name => "map_bugs_tile_idx"
|
||||
add_index :map_bugs, [:last_changed], :name => "map_bugs_changed_idx"
|
||||
add_index :map_bugs, [:date_created], :name => "map_bugs_created_idx"
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_index :map_bugs, :name => "map_bugs_tile_idx"
|
||||
remove_index :map_bugs, :name => "map_bugs_changed_idx"
|
||||
remove_index :map_bugs, :name => "map_bugs_created_idx"
|
||||
|
||||
drop_table :map_bugs
|
||||
|
||||
drop_enumeration :map_bug_status_enum
|
||||
end
|
||||
end
|
34
db/migrate/054_refactor_map_bug_tables.rb
Normal file
34
db/migrate/054_refactor_map_bug_tables.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
require 'migrate'
|
||||
|
||||
class RefactorMapBugTables < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :map_bug_comment do |t|
|
||||
t.column :id, :bigint, :null => false
|
||||
t.column :bug_id, :bigint, :null => false
|
||||
t.boolean :visible, :null => false
|
||||
t.datetime :date_created, :null => false
|
||||
t.string :commenter_name
|
||||
t.string :commenter_ip
|
||||
t.column :commenter_id, :bigint
|
||||
t.string :comment
|
||||
end
|
||||
|
||||
remove_column :map_bugs, :text
|
||||
|
||||
add_index :map_bug_comment, [:bug_id], :name => "map_bug_comment_id_idx"
|
||||
|
||||
add_foreign_key :map_bug_comment, [:bug_id], :map_bugs, [:id]
|
||||
add_foreign_key :map_bug_comment, [:commenter_id], :users, [:id]
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_foreign_key :map_bug_comment, [:commenter_id]
|
||||
remove_foreign_key :map_bug_comment, [:bug_id]
|
||||
|
||||
remove_index :map_bugs, :name => "map_bug_comment_id_idx"
|
||||
|
||||
add_column :map_bugs, :text, :string
|
||||
|
||||
drop_table :map_bug_comment
|
||||
end
|
||||
end
|
11
db/migrate/055_change_map_bug_comment_type.rb
Normal file
11
db/migrate/055_change_map_bug_comment_type.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
require 'migrate'
|
||||
|
||||
class ChangeMapBugCommentType < ActiveRecord::Migration
|
||||
def self.up
|
||||
change_column :map_bug_comment, :comment, :text
|
||||
end
|
||||
|
||||
def self.down
|
||||
change_column :map_bug_comment, :comment, :string
|
||||
end
|
||||
end
|
11
db/migrate/056_add_date_closed.rb
Normal file
11
db/migrate/056_add_date_closed.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
require 'migrate'
|
||||
|
||||
class AddDateClosed < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :map_bugs, :date_closed, :timestamp
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :map_bugs, :date_closed
|
||||
end
|
||||
end
|
15
db/migrate/057_add_map_bug_comment_event.rb
Normal file
15
db/migrate/057_add_map_bug_comment_event.rb
Normal file
|
@ -0,0 +1,15 @@
|
|||
require 'migrate'
|
||||
|
||||
class AddMapBugCommentEvent < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_enumeration :map_bug_event_enum, ["opened", "closed", "reopened", "commented", "hidden"]
|
||||
|
||||
add_column :map_bug_comment, :event, :map_bug_event_enum
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :map_bug_comment, :event
|
||||
|
||||
drop_enumeration :map_bug_event_enum
|
||||
end
|
||||
end
|
25
db/migrate/20110508145337_cleanup_bug_tables.rb
Normal file
25
db/migrate/20110508145337_cleanup_bug_tables.rb
Normal file
|
@ -0,0 +1,25 @@
|
|||
class CleanupBugTables < ActiveRecord::Migration
|
||||
def self.up
|
||||
rename_column :map_bugs, :date_created, :created_at
|
||||
rename_column :map_bugs, :last_changed, :updated_at
|
||||
rename_column :map_bugs, :date_closed, :closed_at
|
||||
|
||||
rename_column :map_bug_comment, :date_created, :created_at
|
||||
rename_column :map_bug_comment, :commenter_name, :author_name
|
||||
rename_column :map_bug_comment, :commenter_ip, :author_ip
|
||||
rename_column :map_bug_comment, :commenter_id, :author_id
|
||||
rename_column :map_bug_comment, :comment, :body
|
||||
end
|
||||
|
||||
def self.down
|
||||
rename_column :map_bug_comment, :body, :comment
|
||||
rename_column :map_bug_comment, :author_id, :commenter_id
|
||||
rename_column :map_bug_comment, :author_ip, :commenter_ip
|
||||
rename_column :map_bug_comment, :author_name, :commenter_name
|
||||
rename_column :map_bug_comment, :created_at, :date_created
|
||||
|
||||
rename_column :map_bugs, :closed_at, :date_closed
|
||||
rename_column :map_bugs, :updated_at, :last_changed
|
||||
rename_column :map_bugs, :created_at, :date_created
|
||||
end
|
||||
end
|
51
db/migrate/20110521142405_rename_bugs_to_notes.rb
Normal file
51
db/migrate/20110521142405_rename_bugs_to_notes.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
require 'migrate'
|
||||
|
||||
class RenameBugsToNotes < ActiveRecord::Migration
|
||||
def self.up
|
||||
rename_enumeration "map_bug_status_enum", "note_status_enum"
|
||||
rename_enumeration "map_bug_event_enum", "note_event_enum"
|
||||
|
||||
rename_table :map_bugs, :notes
|
||||
rename_index :notes, "map_bugs_pkey", "notes_pkey"
|
||||
rename_index :notes, "map_bugs_changed_idx", "notes_updated_at_idx"
|
||||
rename_index :notes, "map_bugs_created_idx", "notes_created_at_idx"
|
||||
rename_index :notes, "map_bugs_tile_idx", "notes_tile_status_idx"
|
||||
|
||||
remove_foreign_key :map_bug_comment, [:bug_id], :map_bugs, [:id]
|
||||
rename_column :map_bug_comment, :author_id, :commenter_id
|
||||
remove_foreign_key :map_bug_comment, [:commenter_id], :users, [:id]
|
||||
rename_column :map_bug_comment, :commenter_id, :author_id
|
||||
|
||||
rename_table :map_bug_comment, :note_comments
|
||||
rename_column :note_comments, :bug_id, :note_id
|
||||
rename_index :note_comments, "map_bug_comment_pkey", "note_comments_pkey"
|
||||
rename_index :note_comments, "map_bug_comment_id_idx", "note_comments_note_id_idx"
|
||||
|
||||
add_foreign_key :note_comments, [:note_id], :notes, [:id]
|
||||
add_foreign_key :note_comments, [:author_id], :users, [:id]
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_foreign_key :note_comments, [:author_id], :users, [:id]
|
||||
remove_foreign_key :note_comments, [:note_id], :notes, [:id]
|
||||
|
||||
rename_index :note_comments, "note_comments_note_id_idx", "map_bug_comment_id_idx"
|
||||
rename_index :notes, "note_comments_pkey", "map_bug_comment_pkey"
|
||||
rename_column :note_comments, :note_id, :bug_id
|
||||
rename_table :note_comments, :map_bug_comment
|
||||
|
||||
rename_column :map_bug_comment, :author_id, :commenter_id
|
||||
add_foreign_key :map_bug_comment, [:commenter_id], :users, [:id]
|
||||
rename_column :map_bug_comment, :commenter_id, :author_id
|
||||
add_foreign_key :map_bug_comment, [:bug_id], :notes, [:id]
|
||||
|
||||
rename_index :notes, "notes_tile_status_idx", "map_bugs_tile_idx"
|
||||
rename_index :notes, "notes_created_at_idx", "map_bugs_created_idx"
|
||||
rename_index :notes, "notes_updated_at_idx", "map_bugs_changed_idx"
|
||||
rename_index :notes, "notes_pkey", "map_bugs_pkey"
|
||||
rename_table :notes, :map_bugs
|
||||
|
||||
rename_enumeration "note_event_enum", "map_bug_event_enum"
|
||||
rename_enumeration "note_status_enum", "map_bug_status_enum"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class DropNearbyPlaceFromNotes < ActiveRecord::Migration
|
||||
def up
|
||||
remove_column :notes, :nearby_place
|
||||
end
|
||||
|
||||
def down
|
||||
add_column :notes, :nearby_place, :string
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
class RemoveAuthorNameFromNoteComment < ActiveRecord::Migration
|
||||
def up
|
||||
remove_column :note_comments, :author_name
|
||||
end
|
||||
|
||||
def down
|
||||
add_column :note_comments, :author_name, :string
|
||||
end
|
||||
end
|
9
db/migrate/20121203124841_change_note_address_to_inet.rb
Normal file
9
db/migrate/20121203124841_change_note_address_to_inet.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class ChangeNoteAddressToInet < ActiveRecord::Migration
|
||||
def up
|
||||
execute "ALTER TABLE note_comments ALTER COLUMN author_ip TYPE inet USING CAST(author_ip AS inet)"
|
||||
end
|
||||
|
||||
def down
|
||||
change_column :note_comments, :author_ip, :string
|
||||
end
|
||||
end
|
11
db/migrate/20130328184137_add_write_notes_permission.rb
Normal file
11
db/migrate/20130328184137_add_write_notes_permission.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class AddWriteNotesPermission < ActiveRecord::Migration
|
||||
def up
|
||||
add_column :oauth_tokens, :allow_write_notes, :boolean, :null => false, :default => false
|
||||
add_column :client_applications, :allow_write_notes, :boolean, :null => false, :default => false
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :client_applications, :allow_write_notes
|
||||
remove_column :oauth_tokens, :allow_write_notes
|
||||
end
|
||||
end
|
224
db/structure.sql
224
db/structure.sql
|
@ -61,6 +61,30 @@ CREATE TYPE gpx_visibility_enum AS ENUM (
|
|||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: note_event_enum; Type: TYPE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TYPE note_event_enum AS ENUM (
|
||||
'opened',
|
||||
'closed',
|
||||
'reopened',
|
||||
'commented',
|
||||
'hidden'
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: note_status_enum; Type: TYPE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE TYPE note_status_enum AS ENUM (
|
||||
'open',
|
||||
'closed',
|
||||
'hidden'
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: nwr_enum; Type: TYPE; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -101,7 +125,7 @@ CREATE TYPE user_status_enum AS ENUM (
|
|||
|
||||
CREATE FUNCTION maptile_for_point(bigint, bigint, integer) RETURNS integer
|
||||
LANGUAGE c STRICT
|
||||
AS '/srv/www/master.osm.compton.nu/db/functions/libpgosm.so', 'maptile_for_point';
|
||||
AS '/srv/www/notes.osm.compton.nu/db/functions/libpgosm.so', 'maptile_for_point';
|
||||
|
||||
|
||||
--
|
||||
|
@ -110,7 +134,7 @@ CREATE FUNCTION maptile_for_point(bigint, bigint, integer) RETURNS integer
|
|||
|
||||
CREATE FUNCTION tile_for_point(integer, integer) RETURNS bigint
|
||||
LANGUAGE c STRICT
|
||||
AS '/srv/www/master.osm.compton.nu/db/functions/libpgosm.so', 'tile_for_point';
|
||||
AS '/srv/www/notes.osm.compton.nu/db/functions/libpgosm.so', 'tile_for_point';
|
||||
|
||||
|
||||
--
|
||||
|
@ -119,7 +143,7 @@ CREATE FUNCTION tile_for_point(integer, integer) RETURNS bigint
|
|||
|
||||
CREATE FUNCTION xid_to_int4(xid) RETURNS integer
|
||||
LANGUAGE c IMMUTABLE STRICT
|
||||
AS '/srv/www/master.osm.compton.nu/db/functions/libpgosm.so', 'xid_to_int4';
|
||||
AS '/srv/www/notes.osm.compton.nu/db/functions/libpgosm.so', 'xid_to_int4';
|
||||
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
@ -218,14 +242,15 @@ CREATE TABLE client_applications (
|
|||
key character varying(50),
|
||||
secret character varying(50),
|
||||
user_id integer,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
allow_read_prefs boolean DEFAULT false NOT NULL,
|
||||
allow_write_prefs boolean DEFAULT false NOT NULL,
|
||||
allow_write_diary boolean DEFAULT false NOT NULL,
|
||||
allow_write_api boolean DEFAULT false NOT NULL,
|
||||
allow_read_gpx boolean DEFAULT false NOT NULL,
|
||||
allow_write_gpx boolean DEFAULT false NOT NULL
|
||||
allow_write_gpx boolean DEFAULT false NOT NULL,
|
||||
allow_write_notes boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
@ -700,6 +725,76 @@ CREATE TABLE nodes (
|
|||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: note_comments; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE note_comments (
|
||||
id integer NOT NULL,
|
||||
note_id bigint NOT NULL,
|
||||
visible boolean NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
author_ip inet,
|
||||
author_id bigint,
|
||||
body text,
|
||||
event note_event_enum
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: note_comments_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE note_comments_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: note_comments_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE note_comments_id_seq OWNED BY note_comments.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: notes; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE TABLE notes (
|
||||
id integer NOT NULL,
|
||||
latitude integer NOT NULL,
|
||||
longitude integer NOT NULL,
|
||||
tile bigint NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
status note_status_enum NOT NULL,
|
||||
closed_at timestamp without time zone
|
||||
);
|
||||
|
||||
|
||||
--
|
||||
-- Name: notes_id_seq; Type: SEQUENCE; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
CREATE SEQUENCE notes_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
|
||||
--
|
||||
-- Name: notes_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER SEQUENCE notes_id_seq OWNED BY notes.id;
|
||||
|
||||
|
||||
--
|
||||
-- Name: oauth_nonces; Type: TABLE; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
@ -708,8 +803,8 @@ CREATE TABLE oauth_nonces (
|
|||
id integer NOT NULL,
|
||||
nonce character varying(255),
|
||||
"timestamp" integer,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
@ -745,8 +840,8 @@ CREATE TABLE oauth_tokens (
|
|||
secret character varying(50),
|
||||
authorized_at timestamp without time zone,
|
||||
invalidated_at timestamp without time zone,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
allow_read_prefs boolean DEFAULT false NOT NULL,
|
||||
allow_write_prefs boolean DEFAULT false NOT NULL,
|
||||
allow_write_diary boolean DEFAULT false NOT NULL,
|
||||
|
@ -756,7 +851,8 @@ CREATE TABLE oauth_tokens (
|
|||
callback_url character varying(255),
|
||||
verifier character varying(20),
|
||||
scope character varying(255),
|
||||
valid_to timestamp without time zone
|
||||
valid_to timestamp without time zone,
|
||||
allow_write_notes boolean DEFAULT false NOT NULL
|
||||
);
|
||||
|
||||
|
||||
|
@ -874,8 +970,8 @@ CREATE TABLE user_blocks (
|
|||
ends_at timestamp without time zone NOT NULL,
|
||||
needs_view boolean DEFAULT false NOT NULL,
|
||||
revoker_id bigint,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
reason_format format_enum DEFAULT 'html'::format_enum NOT NULL
|
||||
);
|
||||
|
||||
|
@ -917,8 +1013,8 @@ CREATE TABLE user_preferences (
|
|||
CREATE TABLE user_roles (
|
||||
id integer NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
created_at timestamp without time zone,
|
||||
updated_at timestamp without time zone,
|
||||
created_at timestamp without time zone NOT NULL,
|
||||
updated_at timestamp without time zone NOT NULL,
|
||||
role user_role_enum NOT NULL,
|
||||
granter_id bigint NOT NULL
|
||||
);
|
||||
|
@ -1000,9 +1096,9 @@ CREATE TABLE users (
|
|||
status user_status_enum DEFAULT 'pending'::user_status_enum NOT NULL,
|
||||
terms_agreed timestamp without time zone,
|
||||
consider_pd boolean DEFAULT false NOT NULL,
|
||||
openid_url character varying(255),
|
||||
preferred_editor character varying(255),
|
||||
terms_seen boolean DEFAULT false NOT NULL,
|
||||
openid_url character varying(255),
|
||||
description_format format_enum DEFAULT 'html'::format_enum NOT NULL,
|
||||
image_fingerprint character varying(255),
|
||||
changesets_count integer DEFAULT 0 NOT NULL,
|
||||
|
@ -1160,6 +1256,20 @@ ALTER TABLE ONLY gpx_files ALTER COLUMN id SET DEFAULT nextval('gpx_files_id_seq
|
|||
ALTER TABLE ONLY messages ALTER COLUMN id SET DEFAULT nextval('messages_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY note_comments ALTER COLUMN id SET DEFAULT nextval('note_comments_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY notes ALTER COLUMN id SET DEFAULT nextval('notes_id_seq'::regclass);
|
||||
|
||||
|
||||
--
|
||||
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -1377,6 +1487,22 @@ ALTER TABLE ONLY nodes
|
|||
ADD CONSTRAINT nodes_pkey PRIMARY KEY (node_id, version);
|
||||
|
||||
|
||||
--
|
||||
-- Name: note_comments_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY note_comments
|
||||
ADD CONSTRAINT note_comments_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: notes_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY notes
|
||||
ADD CONSTRAINT notes_pkey PRIMARY KEY (id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: oauth_nonces_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
@ -1727,6 +1853,34 @@ CREATE INDEX nodes_tile_idx ON nodes USING btree (tile);
|
|||
CREATE INDEX nodes_timestamp_idx ON nodes USING btree ("timestamp");
|
||||
|
||||
|
||||
--
|
||||
-- Name: note_comments_note_id_idx; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX note_comments_note_id_idx ON note_comments USING btree (note_id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: notes_created_at_idx; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX notes_created_at_idx ON notes USING btree (created_at);
|
||||
|
||||
|
||||
--
|
||||
-- Name: notes_tile_status_idx; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX notes_tile_status_idx ON notes USING btree (tile, status);
|
||||
|
||||
|
||||
--
|
||||
-- Name: notes_updated_at_idx; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
||||
CREATE INDEX notes_updated_at_idx ON notes USING btree (updated_at);
|
||||
|
||||
|
||||
--
|
||||
-- Name: points_gpxid_idx; Type: INDEX; Schema: public; Owner: -; Tablespace:
|
||||
--
|
||||
|
@ -2061,6 +2215,22 @@ ALTER TABLE ONLY nodes
|
|||
ADD CONSTRAINT nodes_redaction_id_fkey FOREIGN KEY (redaction_id) REFERENCES redactions(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: note_comments_author_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY note_comments
|
||||
ADD CONSTRAINT note_comments_author_id_fkey FOREIGN KEY (author_id) REFERENCES users(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: note_comments_note_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
||||
ALTER TABLE ONLY note_comments
|
||||
ADD CONSTRAINT note_comments_note_id_fkey FOREIGN KEY (note_id) REFERENCES notes(id);
|
||||
|
||||
|
||||
--
|
||||
-- Name: oauth_tokens_client_application_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
|
||||
--
|
||||
|
@ -2245,6 +2415,10 @@ INSERT INTO schema_migrations (version) VALUES ('20101114011429');
|
|||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20110322001319');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20110508145337');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20110521142405');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20110925112722');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20111116184519');
|
||||
|
@ -2273,6 +2447,14 @@ INSERT INTO schema_migrations (version) VALUES ('20121005195010');
|
|||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20121012044047');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20121119165817');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20121202155309');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20121203124841');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('20130328184137');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('21');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('22');
|
||||
|
@ -2343,6 +2525,16 @@ INSERT INTO schema_migrations (version) VALUES ('51');
|
|||
|
||||
INSERT INTO schema_migrations (version) VALUES ('52');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('53');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('54');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('55');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('56');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('57');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('6');
|
||||
|
||||
INSERT INTO schema_migrations (version) VALUES ('7');
|
||||
|
|
|
@ -37,6 +37,13 @@ public
|
|||
from_bbox_array(bbox_array)
|
||||
end
|
||||
|
||||
def self.from_lrbt_params(params)
|
||||
if params[:l] and params[:b] and params[:t] and params[:t]
|
||||
bbox_array = [params[:l], params[:b], params[:r], params[:t]]
|
||||
end
|
||||
from_bbox_array(bbox_array)
|
||||
end
|
||||
|
||||
def expand!(bbox, margin = 0)
|
||||
update!(bbox) unless complete?
|
||||
# only try to expand the bbox if there is a value for every coordinate
|
||||
|
@ -71,10 +78,10 @@ public
|
|||
self
|
||||
end
|
||||
|
||||
def check_size
|
||||
def check_size(max_area = MAX_REQUEST_AREA)
|
||||
# check the bbox isn't too large
|
||||
if area > MAX_REQUEST_AREA
|
||||
raise OSM::APIBadBoundingBox.new("The maximum bbox size is " + MAX_REQUEST_AREA.to_s +
|
||||
if area > max_area
|
||||
raise OSM::APIBadBoundingBox.new("The maximum bbox size is " + max_area.to_s +
|
||||
", and your request was too large. Either request a smaller area, or use planet.osm")
|
||||
end
|
||||
self
|
||||
|
|
|
@ -45,4 +45,3 @@ private
|
|||
180/Math::PI * Math.log(Math.tan(Math::PI/4+a*(Math::PI/180)/2))
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -87,18 +87,23 @@ module ActiveRecord
|
|||
@enumerations ||= Hash.new
|
||||
end
|
||||
|
||||
def create_enumeration (enumeration_name, values)
|
||||
def create_enumeration(enumeration_name, values)
|
||||
enumerations[enumeration_name] = values
|
||||
execute "create type #{enumeration_name} as enum ('#{values.join '\',\''}')"
|
||||
execute "CREATE TYPE #{enumeration_name} AS ENUM ('#{values.join '\',\''}')"
|
||||
end
|
||||
|
||||
def drop_enumeration (enumeration_name)
|
||||
execute "drop type #{enumeration_name}"
|
||||
def drop_enumeration(enumeration_name)
|
||||
execute "DROP TYPE #{enumeration_name}"
|
||||
enumerations.delete(enumeration_name)
|
||||
end
|
||||
|
||||
def rename_enumeration(old_name, new_name)
|
||||
execute "ALTER TYPE #{quote_table_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
||||
end
|
||||
|
||||
def alter_primary_key(table_name, new_columns)
|
||||
execute "alter table #{table_name} drop constraint #{table_name}_pkey; alter table #{table_name} add primary key (#{new_columns.join(',')})"
|
||||
execute "ALTER TABLE #{table_name} DROP CONSTRAINT #{table_name}_pkey"
|
||||
execute "ALTER TABLE #{table_name} ADD PRIMARY KEY (#{new_columns.join(',')})"
|
||||
end
|
||||
|
||||
def interval_constant(interval)
|
||||
|
@ -125,6 +130,10 @@ module ActiveRecord
|
|||
|
||||
execute "CREATE #{index_type} INDEX #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} USING #{index_method} (#{quoted_column_names})"
|
||||
end
|
||||
|
||||
def rename_index(table_name, old_name, new_name)
|
||||
execute "ALTER INDEX #{quote_table_name(old_name)} RENAME TO #{quote_table_name(new_name)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
26
lib/nominatim.rb
Normal file
26
lib/nominatim.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
module Nominatim
|
||||
extend ActionView::Helpers::NumberHelper
|
||||
|
||||
def self.describe_location(lat, lon, zoom = nil, language = nil)
|
||||
zoom = zoom || 14
|
||||
language = language || request.user_preferred_languages.join(',')
|
||||
|
||||
Rails.cache.fetch "/nominatim/location/#{lat}/#{lon}/#{zoom}/#{language}" do
|
||||
url = "http://nominatim.openstreetmap.org/reverse?lat=#{lat}&lon=#{lon}&zoom=#{zoom}&accept-language=#{language}"
|
||||
|
||||
begin
|
||||
response = OSM::Timer.timeout(4) do
|
||||
REXML::Document.new(Net::HTTP.get(URI.parse(url)))
|
||||
end
|
||||
rescue Exception
|
||||
response = nil
|
||||
end
|
||||
|
||||
if response and result = response.get_text("reversegeocode/result")
|
||||
result.to_s
|
||||
else
|
||||
"#{number_with_precision(lat, :precision => 3)}, #{number_with_precision(lon, :precision => 3)}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
lib/osm.rb
17
lib/osm.rb
|
@ -281,6 +281,23 @@ module OSM
|
|||
end
|
||||
end
|
||||
|
||||
# Raised when the note provided is already closed
|
||||
class APINoteAlreadyClosedError < APIError
|
||||
def initialize(note)
|
||||
@note = note
|
||||
end
|
||||
|
||||
attr_reader :note
|
||||
|
||||
def status
|
||||
:conflict
|
||||
end
|
||||
|
||||
def to_s
|
||||
"The note #{@note.id} was closed at #{@note.closed_at}"
|
||||
end
|
||||
end
|
||||
|
||||
# Helper methods for going to/from mercator and lat/lng.
|
||||
class Mercator
|
||||
include Math
|
||||
|
|
107
test/fixtures/note_comments.yml
vendored
Normal file
107
test/fixtures/note_comments.yml
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
t1:
|
||||
id: 1
|
||||
note_id: 1
|
||||
visible: true
|
||||
created_at: 2007-01-01 00:00:00
|
||||
author_ip: '192.168.1.1'
|
||||
body: 'This is the initial description of the note 1'
|
||||
|
||||
t2:
|
||||
id: 2
|
||||
note_id: 2
|
||||
visible: true
|
||||
created_at: 2007-01-01 00:00:00
|
||||
author_ip: '192.168.1.1'
|
||||
body: 'This is the initial description of the note 2'
|
||||
|
||||
t3:
|
||||
id: 3
|
||||
note_id: 2
|
||||
visible: true
|
||||
created_at: 2007-02-01 00:00:00
|
||||
author_ip: '192.168.1.1'
|
||||
body: 'This is an additional comment for note 2'
|
||||
|
||||
t4:
|
||||
id: 4
|
||||
note_id: 3
|
||||
visible: true
|
||||
created_at: 2007-01-01 00:00:00
|
||||
author_ip: '192.168.1.1'
|
||||
body: 'This is the initial comment for note 3'
|
||||
|
||||
t5:
|
||||
id: 5
|
||||
note_id: 4
|
||||
visible: true
|
||||
created_at: 2007-01-01 00:00:00
|
||||
author_ip: '192.168.1.1'
|
||||
body: 'Spam for note 4'
|
||||
|
||||
t6:
|
||||
id: 6
|
||||
note_id: 5
|
||||
visible: true
|
||||
created_at: 2007-01-01 00:00:00
|
||||
author_ip: '192.168.1.1'
|
||||
body: 'Valid comment for note 5'
|
||||
|
||||
t7:
|
||||
id: 7
|
||||
note_id: 5
|
||||
visible: false
|
||||
created_at: 2007-02-01 00:00:00
|
||||
author_ip: '192.168.1.1'
|
||||
body: 'Spam for note 5'
|
||||
|
||||
t8:
|
||||
id: 8
|
||||
note_id: 5
|
||||
visible: true
|
||||
created_at: 2007-02-01 00:00:00
|
||||
author_ip: '192.168.1.1'
|
||||
body: 'Another valid comment for note 5'
|
||||
|
||||
t9:
|
||||
id: 9
|
||||
note_id: 6
|
||||
visible: true
|
||||
created_at: 2007-01-01 00:00:00
|
||||
event: opened
|
||||
author_id: 1
|
||||
body: 'This is a note with from a logged-in user'
|
||||
|
||||
t10:
|
||||
id: 10
|
||||
note_id: 6
|
||||
visible: true
|
||||
created_at: 2007-02-01 00:00:00
|
||||
event: commented
|
||||
author_id: 4
|
||||
body: 'A comment from another logged-in user'
|
||||
|
||||
t11:
|
||||
id: 11
|
||||
note_id: 7
|
||||
visible: true
|
||||
event: opened
|
||||
created_at: 2007-01-01 00:00:00
|
||||
author_ip: '192.168.1.1'
|
||||
body: 'Initial note description'
|
||||
|
||||
t12:
|
||||
id: 12
|
||||
note_id: 7
|
||||
visible: true
|
||||
event: commented
|
||||
created_at: 2007-02-01 00:00:00
|
||||
author_ip: '192.168.1.1'
|
||||
body: 'A comment description'
|
||||
|
||||
t13:
|
||||
id: 13
|
||||
note_id: 7
|
||||
visible: true
|
||||
event: closed
|
||||
created_at: 2007-03-01 00:00:00
|
||||
author_id: 4
|
67
test/fixtures/notes.yml
vendored
Normal file
67
test/fixtures/notes.yml
vendored
Normal file
|
@ -0,0 +1,67 @@
|
|||
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
|
||||
<% SCALE = 10000000 unless defined?(SCALE) %>
|
||||
|
||||
open_note:
|
||||
id: 1
|
||||
latitude: <%= 1*SCALE %>
|
||||
longitude: <%= 1*SCALE %>
|
||||
status: open
|
||||
tile: <%= QuadTile.tile_for_point(1,1) %>
|
||||
created_at: 2007-01-01 00:00:00
|
||||
updated_at: 2007-01-01 00:00:00
|
||||
|
||||
open_note_with_comment:
|
||||
id: 2
|
||||
latitude: <%= 1.1*SCALE %>
|
||||
longitude: <%= 1.1*SCALE %>
|
||||
status: open
|
||||
tile: <%= QuadTile.tile_for_point(1.1,1.1) %>
|
||||
created_at: 2007-01-01 00:00:00
|
||||
updated_at: 2007-02-01 00:00:00
|
||||
|
||||
closed_note_with_comment:
|
||||
id: 3
|
||||
latitude: <%= 1.2*SCALE %>
|
||||
longitude: <%= 1.2*SCALE %>
|
||||
status: closed
|
||||
tile: <%= QuadTile.tile_for_point(1.2,1.2) %>
|
||||
created_at: 2007-01-01 00:00:00
|
||||
updated_at: 2007-03-01 00:00:00
|
||||
closed_at: 2007-03-01 00:00:00
|
||||
|
||||
hidden_note_with_comment:
|
||||
id: 4
|
||||
latitude: <%= 1.3*SCALE %>
|
||||
longitude: <%= 1.3*SCALE %>
|
||||
status: hidden
|
||||
tile: <%= QuadTile.tile_for_point(1.3,1.3) %>
|
||||
created_at: 2007-01-01 00:00:00
|
||||
updated_at: 2007-03-01 00:00:00
|
||||
|
||||
note_with_hidden_comment:
|
||||
id: 5
|
||||
latitude: <%= 1.4*SCALE %>
|
||||
longitude: <%= 1.4*SCALE %>
|
||||
status: open
|
||||
tile: <%= QuadTile.tile_for_point(1.4,1.4) %>
|
||||
created_at: 2007-01-01 00:00:00
|
||||
updated_at: 2007-03-01 00:00:00
|
||||
|
||||
note_with_comments_by_users:
|
||||
id: 6
|
||||
latitude: <%= 1.5*SCALE %>
|
||||
longitude: <%= 1.5*SCALE %>
|
||||
status: open
|
||||
tile: <%= QuadTile.tile_for_point(1.5,1.5) %>
|
||||
created_at: 2007-01-01 00:00:00
|
||||
updated_at: 2007-03-01 00:00:00
|
||||
|
||||
note_closed_by_user:
|
||||
id: 7
|
||||
latitude: <%= 1.6*SCALE %>
|
||||
longitude: <%= 1.6*SCALE %>
|
||||
status: closed
|
||||
tile: <%= QuadTile.tile_for_point(1.6,1.6) %>
|
||||
created_at: 2007-01-01 00:00:00
|
||||
updated_at: 2007-03-01 00:00:00
|
||||
closed_at: 2007-03-01 00:00:00
|
|
@ -39,6 +39,10 @@ class BrowseControllerTest < ActionController::TestCase
|
|||
{ :path => "/browse/changeset/1", :method => :get },
|
||||
{ :controller => "browse", :action => "changeset", :id => "1" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/browse/note/1", :method => :get },
|
||||
{ :controller => "browse", :action => "note", :id => "1" }
|
||||
)
|
||||
end
|
||||
|
||||
def test_start
|
||||
|
|
523
test/functional/notes_controller_test.rb
Normal file
523
test/functional/notes_controller_test.rb
Normal file
|
@ -0,0 +1,523 @@
|
|||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
|
||||
class NotesControllerTest < ActionController::TestCase
|
||||
fixtures :users, :notes, :note_comments
|
||||
|
||||
##
|
||||
# test all routes which lead to this controller
|
||||
def test_routes
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes", :method => :post },
|
||||
{ :controller => "notes", :action => "create", :format => "xml" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/1", :method => :get },
|
||||
{ :controller => "notes", :action => "show", :id => "1", :format => "xml" }
|
||||
)
|
||||
assert_recognizes(
|
||||
{ :controller => "notes", :action => "show", :id => "1", :format => "xml" },
|
||||
{ :path => "/api/0.6/notes/1.xml", :method => :get }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/1.rss", :method => :get },
|
||||
{ :controller => "notes", :action => "show", :id => "1", :format => "rss" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/1.json", :method => :get },
|
||||
{ :controller => "notes", :action => "show", :id => "1", :format => "json" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/1.gpx", :method => :get },
|
||||
{ :controller => "notes", :action => "show", :id => "1", :format => "gpx" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/1/comment", :method => :post },
|
||||
{ :controller => "notes", :action => "comment", :id => "1", :format => "xml" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/1/close", :method => :post },
|
||||
{ :controller => "notes", :action => "close", :id => "1", :format => "xml" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/1", :method => :delete },
|
||||
{ :controller => "notes", :action => "destroy", :id => "1", :format => "xml" }
|
||||
)
|
||||
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes", :method => :get },
|
||||
{ :controller => "notes", :action => "index", :format => "xml" }
|
||||
)
|
||||
assert_recognizes(
|
||||
{ :controller => "notes", :action => "index", :format => "xml" },
|
||||
{ :path => "/api/0.6/notes.xml", :method => :get }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes.rss", :method => :get },
|
||||
{ :controller => "notes", :action => "index", :format => "rss" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes.json", :method => :get },
|
||||
{ :controller => "notes", :action => "index", :format => "json" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes.gpx", :method => :get },
|
||||
{ :controller => "notes", :action => "index", :format => "gpx" }
|
||||
)
|
||||
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/search", :method => :get },
|
||||
{ :controller => "notes", :action => "search", :format => "xml" }
|
||||
)
|
||||
assert_recognizes(
|
||||
{ :controller => "notes", :action => "search", :format => "xml" },
|
||||
{ :path => "/api/0.6/notes/search.xml", :method => :get }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/search.rss", :method => :get },
|
||||
{ :controller => "notes", :action => "search", :format => "rss" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/search.json", :method => :get },
|
||||
{ :controller => "notes", :action => "search", :format => "json" }
|
||||
)
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/search.gpx", :method => :get },
|
||||
{ :controller => "notes", :action => "search", :format => "gpx" }
|
||||
)
|
||||
|
||||
assert_routing(
|
||||
{ :path => "/api/0.6/notes/feed", :method => :get },
|
||||
{ :controller => "notes", :action => "feed", :format => "rss" }
|
||||
)
|
||||
|
||||
assert_recognizes(
|
||||
{ :controller => "notes", :action => "create" },
|
||||
{ :path => "/api/0.6/notes/addPOIexec", :method => :post }
|
||||
)
|
||||
assert_recognizes(
|
||||
{ :controller => "notes", :action => "close" },
|
||||
{ :path => "/api/0.6/notes/closePOIexec", :method => :post }
|
||||
)
|
||||
assert_recognizes(
|
||||
{ :controller => "notes", :action => "comment" },
|
||||
{ :path => "/api/0.6/notes/editPOIexec", :method => :post }
|
||||
)
|
||||
assert_recognizes(
|
||||
{ :controller => "notes", :action => "index", :format => "gpx" },
|
||||
{ :path => "/api/0.6/notes/getGPX", :method => :get }
|
||||
)
|
||||
assert_recognizes(
|
||||
{ :controller => "notes", :action => "feed", :format => "rss" },
|
||||
{ :path => "/api/0.6/notes/getRSSfeed", :method => :get }
|
||||
)
|
||||
|
||||
assert_routing(
|
||||
{ :path => "/user/username/notes", :method => :get },
|
||||
{ :controller => "notes", :action => "mine", :display_name => "username" }
|
||||
)
|
||||
end
|
||||
|
||||
def test_note_create_success
|
||||
assert_difference('Note.count') do
|
||||
assert_difference('NoteComment.count') do
|
||||
post :create, {:lat => -1.0, :lon => -1.0, :text => "This is a comment", :format => "json"}
|
||||
end
|
||||
end
|
||||
assert_response :success
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal "Feature", js["type"]
|
||||
assert_equal "Point", js["geometry"]["type"]
|
||||
assert_equal [-1.0, -1.0], js["geometry"]["coordinates"]
|
||||
assert_equal "open", js["properties"]["status"]
|
||||
assert_equal 1, js["properties"]["comments"].count
|
||||
assert_equal "opened", js["properties"]["comments"].last["action"]
|
||||
assert_equal "This is a comment", js["properties"]["comments"].last["text"]
|
||||
assert_nil js["properties"]["comments"].last["user"]
|
||||
id = js["properties"]["id"]
|
||||
|
||||
get :show, {:id => id, :format => "json"}
|
||||
assert_response :success
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal "Feature", js["type"]
|
||||
assert_equal "Point", js["geometry"]["type"]
|
||||
assert_equal [-1.0, -1.0], js["geometry"]["coordinates"]
|
||||
assert_equal id, js["properties"]["id"]
|
||||
assert_equal "open", js["properties"]["status"]
|
||||
assert_equal 1, js["properties"]["comments"].count
|
||||
assert_equal "opened", js["properties"]["comments"].last["action"]
|
||||
assert_equal "This is a comment", js["properties"]["comments"].last["text"]
|
||||
assert_nil js["properties"]["comments"].last["user"]
|
||||
end
|
||||
|
||||
def test_note_create_fail
|
||||
assert_no_difference('Note.count') do
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :create, {:lon => -1.0, :text => "This is a comment"}
|
||||
end
|
||||
end
|
||||
assert_response :bad_request
|
||||
|
||||
assert_no_difference('Note.count') do
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :create, {:lat => -1.0, :text => "This is a comment"}
|
||||
end
|
||||
end
|
||||
assert_response :bad_request
|
||||
|
||||
assert_no_difference('Note.count') do
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :create, {:lat => -1.0, :lon => -1.0}
|
||||
end
|
||||
end
|
||||
assert_response :bad_request
|
||||
|
||||
assert_no_difference('Note.count') do
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :create, {:lat => -1.0, :lon => -1.0, :text => ""}
|
||||
end
|
||||
end
|
||||
assert_response :bad_request
|
||||
|
||||
assert_no_difference('Note.count') do
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :create, {:lat => -100.0, :lon => -1.0, :text => "This is a comment"}
|
||||
end
|
||||
end
|
||||
assert_response :bad_request
|
||||
|
||||
assert_no_difference('Note.count') do
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :create, {:lat => -1.0, :lon => -200.0, :text => "This is a comment"}
|
||||
end
|
||||
end
|
||||
assert_response :bad_request
|
||||
end
|
||||
|
||||
def test_note_comment_create_success
|
||||
assert_difference('NoteComment.count') do
|
||||
post :comment, {:id => notes(:open_note_with_comment).id, :text => "This is an additional comment", :format => "json"}
|
||||
end
|
||||
assert_response :success
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal "Feature", js["type"]
|
||||
assert_equal notes(:open_note_with_comment).id, js["properties"]["id"]
|
||||
assert_equal "open", js["properties"]["status"]
|
||||
assert_equal 3, js["properties"]["comments"].count
|
||||
assert_equal "commented", js["properties"]["comments"].last["action"]
|
||||
assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
|
||||
assert_nil js["properties"]["comments"].last["user"]
|
||||
|
||||
get :show, {:id => notes(:open_note_with_comment).id, :format => "json"}
|
||||
assert_response :success
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal "Feature", js["type"]
|
||||
assert_equal notes(:open_note_with_comment).id, js["properties"]["id"]
|
||||
assert_equal "open", js["properties"]["status"]
|
||||
assert_equal 3, js["properties"]["comments"].count
|
||||
assert_equal "commented", js["properties"]["comments"].last["action"]
|
||||
assert_equal "This is an additional comment", js["properties"]["comments"].last["text"]
|
||||
assert_nil js["properties"]["comments"].last["user"]
|
||||
end
|
||||
|
||||
def test_note_comment_create_fail
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :comment, {:text => "This is an additional comment"}
|
||||
end
|
||||
assert_response :bad_request
|
||||
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :comment, {:id => notes(:open_note_with_comment).id}
|
||||
end
|
||||
assert_response :bad_request
|
||||
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :comment, {:id => notes(:open_note_with_comment).id, :text => ""}
|
||||
end
|
||||
assert_response :bad_request
|
||||
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :comment, {:id => 12345, :text => "This is an additional comment"}
|
||||
end
|
||||
assert_response :not_found
|
||||
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :comment, {:id => notes(:hidden_note_with_comment).id, :text => "This is an additional comment"}
|
||||
end
|
||||
assert_response :gone
|
||||
|
||||
assert_no_difference('NoteComment.count') do
|
||||
post :comment, {:id => notes(:closed_note_with_comment).id, :text => "This is an additional comment"}
|
||||
end
|
||||
assert_response :conflict
|
||||
end
|
||||
|
||||
def test_note_close_success
|
||||
post :close, {:id => notes(:open_note_with_comment).id, :text => "This is a close comment", :format => "json"}
|
||||
assert_response :unauthorized
|
||||
|
||||
basic_authorization(users(:public_user).email, "test")
|
||||
|
||||
post :close, {:id => notes(:open_note_with_comment).id, :text => "This is a close comment", :format => "json"}
|
||||
assert_response :success
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal "Feature", js["type"]
|
||||
assert_equal notes(:open_note_with_comment).id, js["properties"]["id"]
|
||||
assert_equal "closed", js["properties"]["status"]
|
||||
assert_equal 3, js["properties"]["comments"].count
|
||||
assert_equal "closed", js["properties"]["comments"].last["action"]
|
||||
assert_equal "This is a close comment", js["properties"]["comments"].last["text"]
|
||||
assert_equal "test2", js["properties"]["comments"].last["user"]
|
||||
|
||||
get :show, {:id => notes(:open_note_with_comment).id, :format => "json"}
|
||||
assert_response :success
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal "Feature", js["type"]
|
||||
assert_equal notes(:open_note_with_comment).id, js["properties"]["id"]
|
||||
assert_equal "closed", js["properties"]["status"]
|
||||
assert_equal 3, js["properties"]["comments"].count
|
||||
assert_equal "closed", js["properties"]["comments"].last["action"]
|
||||
assert_equal "This is a close comment", js["properties"]["comments"].last["text"]
|
||||
assert_equal "test2", js["properties"]["comments"].last["user"]
|
||||
end
|
||||
|
||||
def test_note_close_fail
|
||||
post :close
|
||||
assert_response :unauthorized
|
||||
|
||||
basic_authorization(users(:public_user).email, "test")
|
||||
|
||||
post :close
|
||||
assert_response :bad_request
|
||||
|
||||
post :close, {:id => 12345}
|
||||
assert_response :not_found
|
||||
|
||||
post :close, {:id => notes(:hidden_note_with_comment).id}
|
||||
assert_response :gone
|
||||
|
||||
post :close, {:id => notes(:closed_note_with_comment).id}
|
||||
assert_response :conflict
|
||||
end
|
||||
|
||||
def test_note_read_success
|
||||
get :show, {:id => notes(:open_note).id, :format => "xml"}
|
||||
assert_response :success
|
||||
assert_equal "application/xml", @response.content_type
|
||||
|
||||
get :show, {:id => notes(:open_note).id, :format => "rss"}
|
||||
assert_response :success
|
||||
assert_equal "application/rss+xml", @response.content_type
|
||||
|
||||
get :show, {:id => notes(:open_note).id, :format => "json"}
|
||||
assert_response :success
|
||||
assert_equal "application/json", @response.content_type
|
||||
|
||||
get :show, {:id => notes(:open_note).id, :format => "gpx"}
|
||||
assert_response :success
|
||||
assert_equal "application/gpx+xml", @response.content_type
|
||||
end
|
||||
|
||||
def test_note_read_hidden_comment
|
||||
get :show, {:id => notes(:note_with_hidden_comment).id, :format => "json"}
|
||||
assert_response :success
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal notes(:note_with_hidden_comment).id, js["properties"]["id"]
|
||||
assert_equal 2, js["properties"]["comments"].count
|
||||
assert_equal "Valid comment for note 5", js["properties"]["comments"][0]["text"]
|
||||
assert_equal "Another valid comment for note 5", js["properties"]["comments"][1]["text"]
|
||||
end
|
||||
|
||||
def test_note_read_fail
|
||||
get :show, {:id => 12345}
|
||||
assert_response :not_found
|
||||
|
||||
get :show, {:id => notes(:hidden_note_with_comment).id}
|
||||
assert_response :gone
|
||||
end
|
||||
|
||||
def test_note_delete_success
|
||||
delete :destroy, {:id => notes(:open_note_with_comment).id, :text => "This is a hide comment", :format => "json"}
|
||||
assert_response :unauthorized
|
||||
|
||||
basic_authorization(users(:public_user).email, "test")
|
||||
|
||||
delete :destroy, {:id => notes(:open_note_with_comment).id, :text => "This is a hide comment", :format => "json"}
|
||||
assert_response :forbidden
|
||||
|
||||
basic_authorization(users(:moderator_user).email, "test")
|
||||
|
||||
delete :destroy, {:id => notes(:open_note_with_comment).id, :text => "This is a hide comment", :format => "json"}
|
||||
assert_response :success
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal "Feature", js["type"]
|
||||
assert_equal notes(:open_note_with_comment).id, js["properties"]["id"]
|
||||
assert_equal "hidden", js["properties"]["status"]
|
||||
assert_equal 3, js["properties"]["comments"].count
|
||||
assert_equal "hidden", js["properties"]["comments"].last["action"]
|
||||
assert_equal "This is a hide comment", js["properties"]["comments"].last["text"]
|
||||
assert_equal "moderator", js["properties"]["comments"].last["user"]
|
||||
|
||||
get :show, {:id => notes(:open_note_with_comment).id, :format => 'json'}
|
||||
assert_response :gone
|
||||
end
|
||||
|
||||
def test_note_delete_fail
|
||||
delete :destroy, {:id => 12345, :format => "json"}
|
||||
assert_response :unauthorized
|
||||
|
||||
basic_authorization(users(:public_user).email, "test")
|
||||
|
||||
delete :destroy, {:id => 12345, :format => "json"}
|
||||
assert_response :forbidden
|
||||
|
||||
basic_authorization(users(:moderator_user).email, "test")
|
||||
|
||||
delete :destroy, {:id => 12345, :format => "json"}
|
||||
assert_response :not_found
|
||||
|
||||
delete :destroy, {:id => notes(:hidden_note_with_comment).id, :format => "json"}
|
||||
assert_response :gone
|
||||
end
|
||||
|
||||
def test_get_notes_success
|
||||
# get :index, {:bbox => '1,1,1.2,1.2'}
|
||||
# assert_response :success
|
||||
# assert_equal "text/javascript", @response.content_type
|
||||
|
||||
get :index, {:bbox => '1,1,1.2,1.2', :format => 'rss'}
|
||||
assert_response :success
|
||||
assert_equal "application/rss+xml", @response.content_type
|
||||
|
||||
get :index, {:bbox => '1,1,1.2,1.2', :format => 'json'}
|
||||
assert_response :success
|
||||
assert_equal "application/json", @response.content_type
|
||||
|
||||
get :index, {:bbox => '1,1,1.2,1.2', :format => 'xml'}
|
||||
assert_response :success
|
||||
assert_equal "application/xml", @response.content_type
|
||||
|
||||
get :index, {:bbox => '1,1,1.2,1.2', :format => 'gpx'}
|
||||
assert_response :success
|
||||
assert_equal "application/gpx+xml", @response.content_type
|
||||
end
|
||||
|
||||
def test_get_notes_large_area
|
||||
# get :index, {:bbox => '-2.5,-2.5,2.5,2.5'}
|
||||
# assert_response :success
|
||||
|
||||
# get :index, {:l => '-2.5', :b => '-2.5', :r => '2.5', :t => '2.5'}
|
||||
# assert_response :success
|
||||
|
||||
get :index, {:bbox => '-10,-10,12,12'}
|
||||
assert_response :bad_request
|
||||
|
||||
get :index, {:l => '-10', :b => '-10', :r => '12', :t => '12'}
|
||||
assert_response :bad_request
|
||||
end
|
||||
|
||||
def test_get_notes_closed
|
||||
get :index, {:bbox => '1,1,1.7,1.7', :closed => '7', :format => 'json'}
|
||||
assert_response :success
|
||||
assert_equal "application/json", @response.content_type
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal "FeatureCollection", js["type"]
|
||||
assert_equal 4, js["features"].count
|
||||
|
||||
get :index, {:bbox => '1,1,1.7,1.7', :closed => '0', :format => 'json'}
|
||||
assert_response :success
|
||||
assert_equal "application/json", @response.content_type
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal "FeatureCollection", js["type"]
|
||||
assert_equal 4, js["features"].count
|
||||
|
||||
get :index, {:bbox => '1,1,1.7,1.7', :closed => '-1', :format => 'json'}
|
||||
assert_response :success
|
||||
assert_equal "application/json", @response.content_type
|
||||
js = ActiveSupport::JSON.decode(@response.body)
|
||||
assert_not_nil js
|
||||
assert_equal "FeatureCollection", js["type"]
|
||||
assert_equal 6, js["features"].count
|
||||
end
|
||||
|
||||
def test_get_notes_bad_params
|
||||
get :index, {:bbox => '-2.5,-2.5,2.5'}
|
||||
assert_response :bad_request
|
||||
|
||||
get :index, {:bbox => '-2.5,-2.5,2.5,2.5,2.5'}
|
||||
assert_response :bad_request
|
||||
|
||||
get :index, {:b => '-2.5', :r => '2.5', :t => '2.5'}
|
||||
assert_response :bad_request
|
||||
|
||||
get :index, {:l => '-2.5', :r => '2.5', :t => '2.5'}
|
||||
assert_response :bad_request
|
||||
|
||||
get :index, {:l => '-2.5', :b => '-2.5', :t => '2.5'}
|
||||
assert_response :bad_request
|
||||
|
||||
get :index, {:l => '-2.5', :b => '-2.5', :r => '2.5'}
|
||||
assert_response :bad_request
|
||||
end
|
||||
|
||||
def test_search_success
|
||||
get :search, {:q => 'note 1', :format => 'xml'}
|
||||
assert_response :success
|
||||
assert_equal "application/xml", @response.content_type
|
||||
|
||||
get :search, {:q => 'note 1', :format => 'json'}
|
||||
assert_response :success
|
||||
assert_equal "application/json", @response.content_type
|
||||
|
||||
get :search, {:q => 'note 1', :format => 'rss'}
|
||||
assert_response :success
|
||||
assert_equal "application/rss+xml", @response.content_type
|
||||
|
||||
get :search, {:q => 'note 1', :format => 'gpx'}
|
||||
assert_response :success
|
||||
assert_equal "application/gpx+xml", @response.content_type
|
||||
end
|
||||
|
||||
def test_search_bad_params
|
||||
get :search
|
||||
assert_response :bad_request
|
||||
end
|
||||
|
||||
def test_rss_success
|
||||
get :feed, {:format => "rss"}
|
||||
assert_response :success
|
||||
assert_equal "application/rss+xml", @response.content_type
|
||||
|
||||
get :feed, {:bbox => "1,1,1.2,1.2", :format => "rss"}
|
||||
assert_response :success
|
||||
assert_equal "application/rss+xml", @response.content_type
|
||||
end
|
||||
|
||||
def test_rss_fail
|
||||
get :feed, {:bbox => "1,1,1.2"}
|
||||
assert_response :bad_request
|
||||
|
||||
get :feed, {:bbox => "1,1,1.2,1.2,1.2"}
|
||||
assert_response :bad_request
|
||||
end
|
||||
|
||||
def test_user_notes_success
|
||||
get :mine, {:display_name => "test"}
|
||||
assert_response :success
|
||||
|
||||
get :mine, {:display_name => "pulibc_test2"}
|
||||
assert_response :success
|
||||
|
||||
get :mine, {:display_name => "non-existent"}
|
||||
assert_response :not_found
|
||||
end
|
||||
end
|
86
vendor/assets/ohauth/ohauth.js
vendored
Normal file
86
vendor/assets/ohauth/ohauth.js
vendored
Normal file
|
@ -0,0 +1,86 @@
|
|||
(function(context) {
|
||||
|
||||
var ohauth = {};
|
||||
|
||||
ohauth.qsString = function(obj) {
|
||||
return Object.keys(obj).sort().map(function(key) {
|
||||
return encodeURIComponent(key) + '=' +
|
||||
encodeURIComponent(obj[key]);
|
||||
}).join('&');
|
||||
};
|
||||
|
||||
ohauth.sha = sha1();
|
||||
|
||||
ohauth.stringQs = function(str) {
|
||||
return str.split('&').reduce(function(obj, pair){
|
||||
var parts = pair.split('=');
|
||||
obj[parts[0]] = (null === parts[1]) ?
|
||||
'' : decodeURIComponent(parts[1]);
|
||||
return obj;
|
||||
}, {});
|
||||
};
|
||||
|
||||
ohauth.rawxhr = function(method, url, data, headers, callback) {
|
||||
var xhr = new XMLHttpRequest(), twoHundred = /^20\d$/;
|
||||
xhr.onreadystatechange = function() {
|
||||
if (4 == xhr.readyState && 0 !== xhr.status) {
|
||||
if (twoHundred.test(xhr.status)) callback(null, xhr);
|
||||
else return callback(xhr, null);
|
||||
}
|
||||
};
|
||||
xhr.onerror = function(e) { return callback(e, null); };
|
||||
xhr.open(method, url, true);
|
||||
for (var h in headers) xhr.setRequestHeader(h, headers[h]);
|
||||
xhr.send(data);
|
||||
};
|
||||
|
||||
ohauth.xhr = function(method, url, auth, data, options, callback) {
|
||||
var headers = (options && options.header) || {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
};
|
||||
headers.Authorization = 'OAuth ' + ohauth.authHeader(auth);
|
||||
ohauth.rawxhr(method, url, auth, data, headers, callback);
|
||||
};
|
||||
|
||||
ohauth.nonce = function() {
|
||||
for (var o = ''; o.length < 6;) {
|
||||
o += '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz'[Math.floor(Math.random() * 61)];
|
||||
}
|
||||
return o;
|
||||
};
|
||||
|
||||
ohauth.authHeader = function(obj) {
|
||||
return Object.keys(obj).sort().map(function(key) {
|
||||
return encodeURIComponent(key) + '="' + encodeURIComponent(obj[key]) + '"';
|
||||
}).join(', ');
|
||||
};
|
||||
|
||||
ohauth.timestamp = function() { return ~~((+new Date()) / 1000); };
|
||||
|
||||
ohauth.percentEncode = function(s) {
|
||||
return encodeURIComponent(s)
|
||||
.replace(/\!/g, '%21').replace(/\'/g, '%27')
|
||||
.replace(/\*/g, '%2A').replace(/\(/g, '%28').replace(/\)/g, '%29');
|
||||
};
|
||||
|
||||
ohauth.baseString = function(method, url, params) {
|
||||
if (params.oauth_signature) delete params.oauth_signature;
|
||||
return [
|
||||
method,
|
||||
ohauth.percentEncode(url),
|
||||
ohauth.percentEncode(ohauth.qsString(params))].join('&');
|
||||
};
|
||||
|
||||
ohauth.signature = function(oauth_secret, token_secret, baseString) {
|
||||
return ohauth.sha.b64_hmac_sha1(
|
||||
ohauth.percentEncode(oauth_secret) + '&' +
|
||||
ohauth.percentEncode(token_secret),
|
||||
baseString);
|
||||
};
|
||||
|
||||
context.ohauth = ohauth;
|
||||
|
||||
// export for npm/browserify compatibility
|
||||
if (typeof module !== 'undefined') module.exports = ohauth;
|
||||
|
||||
})(this);
|
191
vendor/assets/ohauth/sha.js
vendored
Normal file
191
vendor/assets/ohauth/sha.js
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* A JavaScript implementation of the Secure Hash Algorithm, SHA-1, as defined
|
||||
* in FIPS PUB 180-1
|
||||
* Version 2.1a Copyright Paul Johnston 2000 - 2002.
|
||||
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
|
||||
* Distributed under the BSD License
|
||||
* See http://pajhome.org.uk/crypt/md5 for details.
|
||||
*/
|
||||
|
||||
function sha1() {
|
||||
|
||||
/*
|
||||
* Configurable variables. You may need to tweak these to be compatible with
|
||||
* the server-side, but the defaults work in most cases.
|
||||
*/
|
||||
var hexcase = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
|
||||
var b64pad = "="; /* base-64 pad character. "=" for strict RFC compliance */
|
||||
var chrsz = 8; /* bits per input character. 8 - ASCII; 16 - Unicode */
|
||||
|
||||
/*
|
||||
* These are the functions you'll usually want to call
|
||||
* They take string arguments and return either hex or base-64 encoded strings
|
||||
*/
|
||||
function hex_sha1(s){return binb2hex(core_sha1(str2binb(s),s.length * chrsz));}
|
||||
function b64_sha1(s){return binb2b64(core_sha1(str2binb(s),s.length * chrsz));}
|
||||
function str_sha1(s){return binb2str(core_sha1(str2binb(s),s.length * chrsz));}
|
||||
function hex_hmac_sha1(key, data){ return binb2hex(core_hmac_sha1(key, data));}
|
||||
function b64_hmac_sha1(key, data){ return binb2b64(core_hmac_sha1(key, data));}
|
||||
function str_hmac_sha1(key, data){ return binb2str(core_hmac_sha1(key, data));}
|
||||
|
||||
/*
|
||||
* Perform a simple self-test to see if the VM is working
|
||||
*/
|
||||
function sha1_vm_test() {
|
||||
return hex_sha1("abc") == "a9993e364706816aba3e25717850c26c9cd0d89d";
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the SHA-1 of an array of big-endian words, and a bit length
|
||||
*/
|
||||
function core_sha1(x, len) {
|
||||
/* append padding */
|
||||
x[len >> 5] |= 0x80 << (24 - len % 32);
|
||||
x[((len + 64 >> 9) << 4) + 15] = len;
|
||||
|
||||
var w = Array(80);
|
||||
var a = 1732584193;
|
||||
var b = -271733879;
|
||||
var c = -1732584194;
|
||||
var d = 271733878;
|
||||
var e = -1009589776;
|
||||
|
||||
for(var i = 0; i < x.length; i += 16) {
|
||||
var olda = a;
|
||||
var oldb = b;
|
||||
var oldc = c;
|
||||
var oldd = d;
|
||||
var olde = e;
|
||||
|
||||
for(var j = 0; j < 80; j++) {
|
||||
if(j < 16) w[j] = x[i + j];
|
||||
else w[j] = rol(w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16], 1);
|
||||
var t = safe_add(safe_add(rol(a, 5), sha1_ft(j, b, c, d)),
|
||||
safe_add(safe_add(e, w[j]), sha1_kt(j)));
|
||||
e = d;
|
||||
d = c;
|
||||
c = rol(b, 30);
|
||||
b = a;
|
||||
a = t;
|
||||
}
|
||||
|
||||
a = safe_add(a, olda);
|
||||
b = safe_add(b, oldb);
|
||||
c = safe_add(c, oldc);
|
||||
d = safe_add(d, oldd);
|
||||
e = safe_add(e, olde);
|
||||
}
|
||||
return Array(a, b, c, d, e);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform the appropriate triplet combination function for the current
|
||||
* iteration
|
||||
*/
|
||||
function sha1_ft(t, b, c, d) {
|
||||
if(t < 20) return (b & c) | ((~b) & d);
|
||||
if(t < 40) return b ^ c ^ d;
|
||||
if(t < 60) return (b & c) | (b & d) | (c & d);
|
||||
return b ^ c ^ d;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine the appropriate additive constant for the current iteration
|
||||
*/
|
||||
function sha1_kt(t) {
|
||||
return (t < 20) ? 1518500249 : (t < 40) ? 1859775393 :
|
||||
(t < 60) ? -1894007588 : -899497514;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the HMAC-SHA1 of a key and some data
|
||||
*/
|
||||
function core_hmac_sha1(key, data) {
|
||||
var bkey = str2binb(key);
|
||||
if(bkey.length > 16) bkey = core_sha1(bkey, key.length * chrsz);
|
||||
|
||||
var ipad = Array(16), opad = Array(16);
|
||||
for(var i = 0; i < 16; i++) {
|
||||
ipad[i] = bkey[i] ^ 0x36363636;
|
||||
opad[i] = bkey[i] ^ 0x5C5C5C5C;
|
||||
}
|
||||
|
||||
var hash = core_sha1(ipad.concat(str2binb(data)), 512 + data.length * chrsz);
|
||||
return core_sha1(opad.concat(hash), 512 + 160);
|
||||
}
|
||||
|
||||
/*
|
||||
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
|
||||
* to work around bugs in some JS interpreters.
|
||||
*/
|
||||
function safe_add(x, y) {
|
||||
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
|
||||
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xFFFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* Bitwise rotate a 32-bit number to the left.
|
||||
*/
|
||||
function rol(num, cnt) {
|
||||
return (num << cnt) | (num >>> (32 - cnt));
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an 8-bit or 16-bit string to an array of big-endian words
|
||||
* In 8-bit function, characters >255 have their hi-byte silently ignored.
|
||||
*/
|
||||
function str2binb(str) {
|
||||
var bin = Array();
|
||||
var mask = (1 << chrsz) - 1;
|
||||
for(var i = 0; i < str.length * chrsz; i += chrsz)
|
||||
bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (32 - chrsz - i%32);
|
||||
return bin;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of big-endian words to a string
|
||||
*/
|
||||
function binb2str(bin) {
|
||||
var str = "";
|
||||
var mask = (1 << chrsz) - 1;
|
||||
for(var i = 0; i < bin.length * 32; i += chrsz)
|
||||
str += String.fromCharCode((bin[i>>5] >>> (32 - chrsz - i%32)) & mask);
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of big-endian words to a hex string.
|
||||
*/
|
||||
function binb2hex(binarray) {
|
||||
var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
|
||||
var str = "";
|
||||
for(var i = 0; i < binarray.length * 4; i++) {
|
||||
str += hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8+4)) & 0xF) +
|
||||
hex_tab.charAt((binarray[i>>2] >> ((3 - i%4)*8 )) & 0xF);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of big-endian words to a base-64 string
|
||||
*/
|
||||
function binb2b64(binarray) {
|
||||
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
var str = "";
|
||||
for(var i = 0; i < binarray.length * 4; i += 3) {
|
||||
var triplet = (((binarray[i >> 2] >> 8 * (3 - i %4)) & 0xFF) << 16)
|
||||
| (((binarray[i+1 >> 2] >> 8 * (3 - (i+1)%4)) & 0xFF) << 8 )
|
||||
| ((binarray[i+2 >> 2] >> 8 * (3 - (i+2)%4)) & 0xFF);
|
||||
for(var j = 0; j < 4; j++) {
|
||||
if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
|
||||
else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
|
||||
}
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
return { b64_hmac_sha1: b64_hmac_sha1 };
|
||||
}
|
||||
|
187
vendor/assets/openlayers/OpenLayers.js
vendored
187
vendor/assets/openlayers/OpenLayers.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue