diff --git a/API.md b/API.md
index 8e4ab5194e..3a506b0202 100644
--- a/API.md
+++ b/API.md
@@ -8,28 +8,32 @@ iD supports several URL parameters. When constructing a URL to a standalone inst
of iD (e.g. `http://preview.ideditor.com/release/`), the following parameters are available
in the hash portion of the URL:
-* `map` - A slash separated `zoom/latitude/longitude`. Example:
- `map=20.00/38.90085/-77.02271`
-* `id` - The character 'n', 'w', or 'r', followed by the OSM ID of a node,
+* __`map`__ - A slash separated `zoom/latitude/longitude`.
+ _Example:_ `map=20.00/38.90085/-77.02271`
+* __`id`__ - The character 'n', 'w', or 'r', followed by the OSM ID of a node,
way or relation, respectively. Selects the specified entity, and, unless
a `map` parameter is also provided, centers the map on it.
-* `background` - The value from a `sourcetag` property in iD's
+* __`background`__ - The value from a `sourcetag` property in iD's
[imagery list](https://github.com/openstreetmap/iD/blob/master/data/imagery.json),
or a custom tile URL. A custom URL is specified in the format `custom:`,
where the URL can contain the standard tile URL placeholders `{x}`, `{y}` and
`{z}`/`{zoom}`, `{ty}` for flipped TMS-style Y coordinates, and `{switch:a,b,c}` for
- DNS multiplexing. Example:
- `background=custom:https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png`
-* `gpx` - A custom URL for loading a gpx track. Specifying a `gpx` parameter will
- automatically enable the gpx layer for display. Example:
- `gpx=https://tasks.hotosm.org/project/592/task/16.gpx`
-* `offset` - imagery offset in meters, formatted as `east,north`. Example:
- `offset=-10,5`
-* `comment` - Prefills the changeset comment box, for use when integrating iD with
- external task management or quality assurance tools. Example:
- `comment=CAR%20crisis%2C%20refugee%20areas%20in%20Cameroon%20%23hotosm-task-592`.
-* `rtl=true` - Force iD into right-to-left mode (useful for testing).
-* `walkthrough=true` - Start the walkthrough automatically
+ DNS multiplexing.
+ _Example:_ `background=custom:https://{switch:a,b,c}.tile.openstreetmap.org/{zoom}/{x}/{y}.png`
+* __`gpx`__ - A custom URL for loading a gpx track. Specifying a `gpx` parameter will
+ automatically enable the gpx layer for display.
+ _Example:_ `gpx=https://tasks.hotosm.org/project/592/task/16.gpx`
+* __`offset`__ - imagery offset in meters, formatted as `east,north`.
+ _Example:_ `offset=-10,5`
+* __`comment`__ - Prefills the changeset comment. Pass a url encoded string.
+ _Example:_ `comment=CAR%20crisis%2C%20refugee%20areas%20in%20Cameroon`
+* __`hashtags`__ - Prefills the changeset hashtags. Pass a url encoded list of event
+ hashtags separated by commas, semicolons, or spaces. Leading '#' symbols are
+ optional and will be added automatically. (Note that hashtag-like strings are
+ automatically detected in the `comment`).
+ _Example:_ `hashtags=%23hotosm-task-592,%23MissingMaps`
+* __`rtl=true`__ - Force iD into right-to-left mode (useful for testing).
+* __`walkthrough=true`__ - Start the walkthrough automatically
##### iD on openstreetmap.org (Rails Port)
@@ -37,14 +41,15 @@ When constructing a URL to an instance of iD embedded in the OpenStreetMap Rails
Port (e.g. `http://www.openstreetmap.org/edit?editor=id`), the following parameters
are available as regular URL query parameters:
-* `map` - same as standalone
-* `lat`, `lon`, `zoom` - Self-explanatory.
-* `node`, `way`, `relation` - Select the specified entity.
-* `background` - same as standalone
-* `gpx` - same as standalone
-* `offset` - same as standalone
-* `comment` - same as standalone
-* `walkthrough` - same as standalone
+* __`map`__ - same as standalone
+* __`lat`__, __`lon`__, __`zoom`__ - Self-explanatory.
+* __`node`__, __`way`__, __`relation`__ - Select the specified entity.
+* __`background`__ - same as standalone
+* __`gpx`__ - same as standalone
+* __`offset`__ - same as standalone
+* __`comment`__ - same as standalone
+* __`hashtags`__ - same as standalone
+* __`walkthrough`__ - same as standalone
## CSS selectors
diff --git a/css/80_app.css b/css/80_app.css
index fc334eaa1c..e652cbecf1 100644
--- a/css/80_app.css
+++ b/css/80_app.css
@@ -225,6 +225,12 @@ input[type="radio"] {
margin-right: 5px;
margin-top: 3px;
}
+[dir='rtl'] input[type="checkbox"],
+[dir='rtl'] input[type="radio"] {
+ float: right;
+ margin-left: 5px;
+ margin-right: 0;
+}
/* remove bottom border radius when combobox is open */
.combobox + * textarea:focus,
@@ -453,23 +459,37 @@ button.minor:hover {
.button-wrap:last-of-type {
padding-right: 0;
}
+[dir='rtl'] .button-wrap:last-of-type {
+ padding-left: 0;
+ padding-right: 10px;
+}
.joined button {
border-radius:0;
border-right: 1px solid rgba(0,0,0,.5);
}
+[dir='rtl'] .joined button {
+ border-left: 1px solid rgba(0,0,0,.5);
+ border-right: none;
+}
.fillL .joined button {
border-right: 1px solid white;
}
.joined button:first-child {
- border-radius:4px 0 0 4px;
+ border-radius: 4px 0 0 4px;
+}
+[dir='rtl'] .joined button:first-child {
+ border-radius: 0 4px 4px 0;
}
.joined button:last-child {
border-right-width: 0;
- border-radius:0 4px 4px 0;
+ border-radius: 0 4px 4px 0;
+}
+[dir='rtl'] .joined button:last-child {
+ border-radius: 4px 0 0 4px;
}
button.action {
@@ -516,6 +536,10 @@ button.save.has-count .count {
margin: auto;
margin-left: 9.3333%;
}
+[dir='rtl'] button.save.has-count .count {
+ margin-left: auto;
+ margin-right: 8%;
+}
button.save.has-count .count::before {
content: "";
@@ -532,6 +556,12 @@ button.save.has-count .count::before {
border-right-style: solid;
border-right-color: inherit;
}
+[dir='rtl'] button.save.has-count .count::before {
+ border-left: 6px solid rgba(255,255,255,.5);
+ border-right: none;
+ left: auto;
+ right: -6px;
+}
/* Icons */
@@ -551,6 +581,10 @@ button.save.has-count .count::before {
.icon.pre-text {
margin-right: 5px;
}
+[dir='rtl'] .icon.pre-text {
+ margin-left: 5px;
+ margin-right: 0;
+}
.icon.light {
color: #fff;
@@ -583,14 +617,20 @@ button.save.has-count .count::before {
#bar {
position: fixed;
padding: 10px 0;
- left:0;
- top:0;
- right:0;
- height:60px;
+ left: 0;
+ top: 0;
+ right: 0;
+ height: 60px;
z-index: 9;
min-width: 768px;
}
+[dir='rtl'] #bar .spacer,
+[dir='rtl'] #bar .button-wrap,
+[dir='rtl'] #bar .button-wrap button {
+ float: right;
+}
+
/* Header for modals / panes
------------------------------------------------------- */
@@ -609,6 +649,10 @@ button.save.has-count .count::before {
overflow: hidden;
padding: 20px 20px 20px 40px;
}
+[dir='rtl'] .header h3 {
+ text-align: right;
+ padding: 20px 40px 20px 20px;
+}
.header button,
.modal > button {
@@ -629,12 +673,21 @@ button.save.has-count .count::before {
right: 0;
top: 0;
}
+[dir='rtl'] .entity-editor-pane .header button.preset-close,
+[dir='rtl'] .preset-list-pane .header button.preset-choose {
+ left: 0;
+ right: auto;
+}
.entity-editor-pane .header button.preset-choose {
position: absolute;
left: 0;
top: 0;
}
+[dir='rtl'] .entity-editor-pane .header button.preset-choose {
+ left: auto;
+ right: 0;
+}
.preset-choose {
font-size: 16px;
@@ -649,6 +702,10 @@ button.save.has-count .count::before {
height: 60px;
z-index: 50;
}
+[dir='rtl'] .modal > button {
+ left: 0;
+ right: unset;
+}
.footer {
position: absolute;
@@ -680,6 +737,9 @@ button.save.has-count .count::before {
background: #f6f6f6;
-ms-user-select: element;
}
+[dir='rtl'] #sidebar {
+ float: right;
+}
.sidebar-component {
position: absolute;
@@ -736,6 +796,10 @@ button.save.has-count .count::before {
top: 80px;
pointer-events: none;
}
+[dir='rtl'] #sidebar .search-header .icon {
+ left: auto;
+ right: 10px;
+}
#sidebar .search-header input {
position: absolute;
@@ -789,6 +853,9 @@ button.save.has-count .count::before {
overflow: hidden;
border-left: 1px solid rgba(0, 0, 0, .1);
}
+[dir='rtl'] .feature-list-item .label {
+ text-align: right;
+}
.feature-list-item .label .icon {
opacity: .5;
@@ -816,6 +883,11 @@ button.save.has-count .count::before {
color: #666;
padding-left: 10px;
}
+[dir='rtl'] .feature-list-item .entity-name {
+ padding-left: 0;
+ padding-right: 10px;
+}
+
/* Presets
------------------------------------------------------- */
@@ -940,6 +1012,19 @@ button.save.has-count .count::before {
height: 24px;
}
+[dir='rtl'] .preset-list-button-wrap .preset-icon {
+ left: auto;
+ right: auto;
+}
+
+[dir='rtl'] .preset-list-button-wrap .preset-icon-28 {
+ right: 16px;
+}
+
+[dir='rtl'] .preset-list-button-wrap .preset-icon-24 {
+ right: 18px;
+}
+
.preset-list-button .label {
background-color: #f6f6f6;
text-align: left;
@@ -955,7 +1040,15 @@ button.save.has-count .count::before {
overflow: hidden;
border-left: 1px solid rgba(0, 0, 0, .1);
border-radius: 0 3px 3px 0;
- }
+}
+[dir='rtl'] .preset-list-button .label {
+ text-align: right;
+ left: 0;
+ right: 60px;
+ border-left: none;
+ border-right: 1px solid rgba(0, 0, 0, .1);
+ border-radius: 3px 0 0 3px;
+}
.preset-list-button:hover .label {
background-color: #ececec;
@@ -971,6 +1064,11 @@ button.save.has-count .count::before {
width: 32px;
background: #fafafa;
}
+[dir='rtl'] .preset-list-item button.tag-reference-button {
+ left: 0;
+ right: auto;
+ border-radius: 3px 0 0 3px;
+}
.preset-list-item button.tag-reference-button:hover {
background: #f1f1f1;
@@ -1033,13 +1131,13 @@ button.save.has-count .count::before {
margin: 0 20px 10px 20px;
}
-.preset-editor .preset-form {
+.preset-editor .form-fields-container {
padding: 10px;
margin: 0 10px 10px 10px;
border-radius: 8px;
}
-.preset-editor .preset-form:empty {
+.preset-editor .form-fields-container:empty {
display: none;
}
@@ -1272,6 +1370,9 @@ button.save.has-count .count::before {
padding: 0 20px 20px 20px;
font-weight: bold;
}
+.changeset-editor .more-fields {
+ padding: 15px 20px 0 20px;
+}
.more-fields label {
display: flex;
@@ -1295,6 +1396,9 @@ button.save.has-count .count::before {
padding: 5px 10px;
}
+[dir='rtl'] .preset-input-wrap .col6 {
+ float: right;
+}
/* preset form access */
@@ -1754,6 +1858,10 @@ div.combobox {
margin-left: -30px;
vertical-align: top;
}
+[dir='rtl'] .combobox-caret {
+ margin-left: 0;
+ margin-right: -30px;
+}
.combobox-caret::after {
content:"";
@@ -1926,18 +2034,17 @@ button.minor.tag-reference-loading {
clear: both;
}
-.tag-reference-body p,
-.tag-reference-body img {
- margin-top: 20px;
-}
-
-.tag-reference-body p:last-child {
- padding-bottom: 10px;
+.tag-reference-body .tag-reference-description {
+ margin: 10px 5px 0 5px;
}
.tag-reference-body a {
display: block;
- padding-bottom: 10px;
+}
+
+.tag-reference-body .tag-reference-description:last-child,
+.tag-reference-body a:last-child {
+ margin-bottom: 15px;
}
.preset-list .tag-reference-body {
@@ -1945,41 +2052,35 @@ button.minor.tag-reference-loading {
width: 100%;
}
-.preset-list .tag-reference-body a {
- padding-bottom: 20px;
-}
-
-.preset-list .tag-reference-body p,
-.preset-list .tag-reference-body img {
- margin-top: 10px;
-}
-
.raw-tag-editor .tag-reference-body {
- border-bottom: 1px solid #ccc;
float: left;
width: 100%;
}
-.raw-tag-editor .tag-reference-body p:last-child {
- padding-bottom: 20px;
+.raw-tag-editor .tag-row.readonly .tag-reference-body {
+ background: #f6f6f6;
+ color: #333;
}
-.raw-tag-editor .tag-reference-body a {
- padding-bottom: 20px;
+.raw-tag-editor .tag-row:not(:last-child) .tag-reference-body {
+ border-bottom: 1px solid #ccc;
}
-img.wiki-image {
+.raw-tag-editor .tag-row.readonly .tag-reference-body.expanded {
+ border-top: 1px solid #ccc;
+}
+
+img.tag-reference-wiki-image {
float: right;
width: 33.3333%;
width: -webkit-calc(33.3333% - 10px);
width: calc(33.3333% - 10px);
- margin-left: 20px;
- margin-right: 10px;
border-radius: 4px;
max-height: 200px;
- margin-bottom: 20px;
+ margin: 10px 5px 15px 20px;
}
+
/* Raw relation membership editor */
.raw-member-editor .member-list li:first-child,
@@ -2057,6 +2158,10 @@ div.full-screen > button:hover {
position: fixed;
z-index: 100;
}
+[dir='rtl'] .map-controls {
+ left: 0;
+ right: auto;
+}
.map-control > button {
width: 40px;
@@ -2078,6 +2183,9 @@ div.full-screen > button:hover {
.zoombuttons button.zoom-in {
border-radius: 4px 0 0 0;
}
+[dir='rtl'] .zoombuttons button.zoom-in {
+ border-radius: 0 4px 0 0;
+}
/* Background / Map Data Settings */
@@ -2088,6 +2196,9 @@ div.full-screen > button:hover {
.background-control button {
border-radius: 4px 0 0 0;
}
+[dir='rtl'] .background-control button {
+ border-radius: 0 4px 0 0;
+}
.map-data-control,
.background-control {
@@ -2151,6 +2262,10 @@ div.full-screen > button:hover {
float: right;
}
+[dir='rtl'] .list-item-gpx-browse svg {
+ transform: rotateY(180deg);
+}
+
/* make sure tooltip fits in map-control panel */
/* if too wide, placement will be wrong the first time it displays */
.layer-list li.best .tooltip-inner {
@@ -2188,9 +2303,13 @@ div.full-screen > button:hover {
.hide-toggle {
display: block;
- padding-left:12px;
+ padding-left: 12px;
position: relative;
}
+[dir='rtl'] .hide-toggle {
+ padding-left: 0;
+ padding-right: 12px;
+}
.hide-toggle:before {
content: '';
@@ -2204,6 +2323,12 @@ div.full-screen > button:hover {
border-bottom: 4px solid transparent;
border-left: 8px solid #7092ff;
}
+[dir='rtl'] .hide-toggle:before {
+ left: auto;
+ right: 0;
+ border-left: none;
+ border-right: 8px solid #7092ff;
+}
.hide-toggle.expanded:before {
border-top: 8px solid #7092ff;
@@ -2211,6 +2336,11 @@ div.full-screen > button:hover {
border-right: 4px solid transparent;
border-left: 4px solid transparent;
}
+[dir='rtl'] .hide-toggle.expanded:before {
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+}
+
/* Adjust Alignment controls */
@@ -2341,13 +2471,17 @@ div.full-screen > button:hover {
.opacity-options {
background: url(img/background-pattern-opacity.png) 0 0 repeat;
- height:20px;
- width:82px;
+ height: 20px;
+ width: 82px;
position: absolute;
right: 50px;
top: 20px;
border: 1px solid #ccc;
}
+[dir='rtl'] .opacity-options {
+ left: 50px;
+ right: auto;
+}
.opacity-options li {
height: 100%;
@@ -2357,8 +2491,8 @@ div.full-screen > button:hover {
.opacity-options li .select-box{
position: absolute;
- width:20px;
- height:18px;
+ width: 20px;
+ height: 18px;
z-index: 9999;
}
@@ -2393,6 +2527,12 @@ div.full-screen > button:hover {
border-left: 1px solid #CCC;
border-radius: 0;
}
+[dir='rtl'] .map-data-control .layer-list button,
+[dir='rtl'] .background-control .layer-list button {
+ float: left;
+ border-left: none;
+ border-right: 1px solid #CCC;
+}
.map-data-control .layer-list button .icon,
.background-control .layer-list button .icon {
@@ -2403,6 +2543,10 @@ div.full-screen > button:hover {
.background-control .layer-list button:first-of-type {
border-radius: 0 3px 3px 0;
}
+[dir='rtl'] .map-data-control .layer-list button:first-of-type,
+[dir='rtl'] .background-control .layer-list button:first-of-type {
+ border-radius: 3px 0 0 3px;
+}
.map-data-control .map-overlay,
.background-control .map-overlay,
@@ -2410,7 +2554,6 @@ div.full-screen > button:hover {
z-index: -1;
}
-
/* Geolocator */
.geolocate-control {
@@ -2420,6 +2563,9 @@ div.full-screen > button:hover {
.geolocate-control button {
border-radius: 0 0 0 4px;
}
+[dir='rtl'] .geolocate-control button {
+ border-radius: 0 0 4px 0;
+}
.map-overlay.content {
position: fixed;
@@ -2429,12 +2575,20 @@ div.full-screen > button:hover {
right: 0;
overflow: auto;
}
+[dir='rtl'] .map-overlay.content {
+ padding: 20px 20px 20px 50px;
+ left: 0;
+ right: auto !important;
+}
/* Help */
.help-control button {
border-radius: 0 0 0 4px;
}
+[dir='rtl'] .help-control button {
+ border-radius: 0 0 4px 0;
+}
.help-wrap p {
font-size: 15px;
@@ -3348,32 +3502,50 @@ img.tile-removing {
float: left;
height: 12px;
min-width: 12px;
- font-size:12px;
+ font-size: 12px;
line-height: 12px;
- border-radius:24px;
- padding:5px;
- background:#7092ff;
- color:#fff;
+ border-radius: 24px;
+ padding: 5px;
+ background: #7092ff;
+ color: #fff;
+}
+
+.mode-save .field-warning {
+ background: #ffb;
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ padding: 10px;
}
+.mode-save .field-warning:empty {
+ display: none;
+}
+
+.mode-save .field-warning,
+.mode-save .changeset-info,
+.mode-save .request-review,
.mode-save .commit-info {
margin-bottom: 10px;
}
.mode-save .changeset-list {
- border:1px solid #ccc;
+ border: 1px solid #ccc;
border-radius: 4px;
- background:#fff;
+ background: #fff;
+}
+
+.mode-save .warning-section {
+ background: #ffb;
}
.mode-save .warning-section .changeset-list button {
- border-left: 1px solid #CCC;
+ border-left: 1px solid #ccc;
}
.mode-save .changeset-list li {
position: relative;
- border-top:1px solid #ccc;
- padding:5px 10px;
+ border-top: 1px solid #ccc;
+ padding: 5px 10px;
cursor: pointer;
}
@@ -3386,8 +3558,8 @@ img.tile-removing {
}
.changeset-list li span.count {
- font-size:10px;
- color:#555;
+ font-size: 10px;
+ color: #555;
}
.mode-save .commit-section .changeset-list button {
@@ -3478,8 +3650,12 @@ img.tile-removing {
}
.notice .zoom-to .icon {
- margin-top:10px;
- margin-right:10px;
+ margin-top: 10px;
+ margin-right: 10px;
+}
+[dir='rtl'] .notice .zoom-to .icon {
+ margin-left: 10px;
+ margin-right: 0;
}
/* Tooltips
@@ -3691,6 +3867,10 @@ img.tile-removing {
.add-point .tooltip {
left: 33.3333% !important;
}
+[dir='rtl'] .add-point .tooltip {
+ left: inherit !important;
+}
+
.add-point .tooltip .tooltip-arrow {
left: 60px;
}
@@ -4023,204 +4203,3 @@ li.hide + li.version .badge .tooltip .tooltip-arrow {
color: #7092FF;
}
-
-/* Right-to-left localization settings */
-
-[dir='rtl'] #sidebar {
- float: right;
-}
-
-[dir='rtl'] #sidebar .search-header .icon {
- left: auto;
- right: 10px;
-}
-
-/* header */
-[dir='rtl'] .header h3 {
- text-align: right;
- padding: 20px 40px 20px 20px;
-}
-
-[dir='rtl'] .entity-editor-pane .header button.preset-choose {
- left: auto;
- right: 0;
-}
-
-[dir='rtl'] .entity-editor-pane .header button.preset-close, [dir='rtl'] .preset-list-pane .header button.preset-choose {
- left: 0;
- right: auto;
-}
-
-[dir='rtl'] .map-data-control .layer-list button, [dir='rtl'] .background-control .layer-list button {
- float: left;
- border-left: none;
- border-right: 1px solid #CCC;
-}
-
-[dir='rtl'] .map-data-control .layer-list button:first-of-type, [dir='rtl'] .background-control .layer-list button:first-of-type {
- border-radius: 3px 0 0 3px;
-}
-
-/* search */
-[dir='rtl'] .feature-list-item .label {
- text-align: right;
-}
-
-[dir='rtl'] .feature-list-item .entity-name {
- padding-left: 0;
- padding-right: 10px;
-}
-
-/* preset form */
-
-[dir='rtl'] .combobox-caret {
- margin-left: 0;
- margin-right: -30px;
-}
-
-[dir='rtl'] .icon.pre-text {
- margin-left: 5px;
- margin-right: 0;
-}
-
-[dir='rtl'] .notice .zoom-to .icon {
- margin-left: 10px;
- margin-right: 0;
-}
-
-[dir='rtl'] .preset-list-button .label {
- text-align: right;
- left: 0;
- right: 60px;
- border-left: none;
- border-right: 1px solid rgba(0, 0, 0, .1);
- border-radius: 3px 0 0 3px;
-}
-
-[dir='rtl'] .preset-list-item button.tag-reference-button {
- left: 0;
- right: auto;
- border-radius: 3px 0 0 3px;
-}
-
-[dir='rtl'] .preset-list-button-wrap .preset-icon {
- left: auto;
- right: auto;
-}
-
-[dir='rtl'] .preset-list-button-wrap .preset-icon-28 {
- right: 16px;
-}
-
-[dir='rtl'] .preset-list-button-wrap .preset-icon-24 {
- right: 18px;
-}
-
-[dir='rtl'] input[type="checkbox"], [dir='rtl'] input[type="radio"] {
- float: right;
- margin-left: 5px;
- margin-right: 0;
-}
-
-[dir='rtl'] .preset-input-wrap .col6 {
- float: right;
-}
-
-/* map control buttons */
-[dir='rtl'] .map-controls {
- left: 0;
- right: auto;
-}
-
-[dir='rtl'] .background-control button,
-[dir='rtl'] .zoombuttons button.zoom-in {
- border-radius: 0 4px 0 0;
-}
-
-[dir='rtl'] .help-control button,
-[dir='rtl'] .geolocate-control button {
- border-radius: 0 0 4px 0;
-}
-
-[dir='rtl'] .list-item-gpx-browse svg {
- transform: rotateY(180deg);
-}
-
-/* map control button overlays */
-[dir='rtl'] .map-overlay {
- padding: 20px 20px 20px 50px;
- left: 0;
- right: auto !important;
-}
-
-[dir='rtl'] .opacity-options {
- left: 50px;
- right: auto;
-}
-
-[dir='rtl'] .hide-toggle {
- padding-left: 0;
- padding-right: 12px;
-}
-
-[dir='rtl'] .hide-toggle:before {
- left: auto;
- right: 0;
- border-left: none;
- border-right: 8px solid #7092ff;
-}
-
-[dir='rtl'] .hide-toggle.expanded:before {
- border-left: 4px solid transparent;
- border-right: 4px solid transparent;
-}
-
-/* navbar */
-[dir='rtl'] #bar .spacer,
-[dir='rtl'] #bar .button-wrap,
-[dir='rtl'] #bar .button-wrap button {
- float: right;
-}
-
-[dir='rtl'] .add-point .tooltip {
- left: inherit !important;
-}
-
-[dir='rtl'] .button-wrap:last-of-type {
- padding-left: 0;
- padding-right: 10px;
-}
-
-[dir='rtl'] button.save.has-count .count {
- margin-left: auto;
- margin-right: 8%;
-}
-
-[dir='rtl'] button.save.has-count .count::before {
- border-left: 6px solid rgba(255,255,255,.5);
- border-right: none;
- left: auto;
- right: -6px;
-}
-
-[dir='rtl'] .joined button {
- border-left: 1px solid rgba(0,0,0,.5);
- border-right: none;
-}
-
-[dir='rtl'] .joined button:first-child {
- border-radius: 0 4px 4px 0;
-}
-
-[dir='rtl'] .joined button:last-child {
- border-radius: 4px 0 0 4px;
-}
-
-/* modal */
-[dir='rtl'] .modal > button {
- position: absolute;
- left: 0;
- right: unset;
- top: 0;
-}
-
diff --git a/data/core.yaml b/data/core.yaml
index 44767d2fa8..9f45ae26c3 100644
--- a/data/core.yaml
+++ b/data/core.yaml
@@ -254,10 +254,9 @@ en:
rateLimit: The API is limiting anonymous connections. You can fix this by logging in.
commit:
title: Upload to OpenStreetMap
- description_placeholder: Brief description of your contributions (required)
- message_label: Changeset Comment
upload_explanation: "The changes you upload will be visible on all maps that use OpenStreetMap data."
upload_explanation_with_user: "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data."
+ request_review: "I would like someone to review my edits."
save: Upload
cancel: Cancel
changes: "{count} Changes"
diff --git a/data/presets.yaml b/data/presets.yaml
index 5895a11b2c..aede647a20 100644
--- a/data/presets.yaml
+++ b/data/presets.yaml
@@ -339,6 +339,11 @@ en:
collection_times:
# collection_times=*
label: Collection Times
+ comment:
+ # comment=*
+ label: Changeset Comment
+ # comment field placeholder
+ placeholder: Brief description of your contributions (required)
communication_multi:
# 'communication:=*'
label: Communication Types
@@ -571,6 +576,11 @@ en:
handrail:
# handrail=*
label: Handrail
+ hashtags:
+ # hashtags=*
+ label: Hashtags
+ # hashtags field placeholder
+ placeholder: '#example'
height:
# height=*
label: Height (Meters)
@@ -1218,7 +1228,7 @@ en:
label: People Served
source:
# source=*
- label: Source
+ label: Sources
sport:
# sport=*
label: Sports
diff --git a/data/presets/fields.json b/data/presets/fields.json
index e099f5416d..2a0ea2a53e 100644
--- a/data/presets/fields.json
+++ b/data/presets/fields.json
@@ -437,6 +437,12 @@
"type": "text",
"label": "Collection Times"
},
+ "comment": {
+ "key": "comment",
+ "type": "textarea",
+ "label": "Changeset Comment",
+ "placeholder": "Brief description of your contributions (required)"
+ },
"communication_multi": {
"key": "communication:",
"type": "multiCombo",
@@ -788,6 +794,12 @@
"type": "check",
"label": "Handrail"
},
+ "hashtags": {
+ "key": "hashtags",
+ "type": "semiCombo",
+ "label": "Hashtags",
+ "placeholder": "#example"
+ },
"height": {
"key": "height",
"type": "number",
@@ -1624,10 +1636,17 @@
},
"source": {
"key": "source",
- "type": "text",
+ "type": "semiCombo",
"icon": "source",
"universal": true,
- "label": "Source"
+ "label": "Sources",
+ "options": [
+ "survey",
+ "local knowledge",
+ "gps",
+ "aerial imagery",
+ "streetlevel imagery"
+ ]
},
"sport_ice": {
"key": "sport",
diff --git a/data/presets/fields/comment.json b/data/presets/fields/comment.json
new file mode 100644
index 0000000000..71072e1d45
--- /dev/null
+++ b/data/presets/fields/comment.json
@@ -0,0 +1,6 @@
+{
+ "key": "comment",
+ "type": "textarea",
+ "label": "Changeset Comment",
+ "placeholder": "Brief description of your contributions (required)"
+}
diff --git a/data/presets/fields/hashtags.json b/data/presets/fields/hashtags.json
new file mode 100644
index 0000000000..d3045507c3
--- /dev/null
+++ b/data/presets/fields/hashtags.json
@@ -0,0 +1,6 @@
+{
+ "key": "hashtags",
+ "type": "semiCombo",
+ "label": "Hashtags",
+ "placeholder": "#example"
+}
diff --git a/data/presets/fields/source.json b/data/presets/fields/source.json
index 0cad9fa73a..c9b746500c 100644
--- a/data/presets/fields/source.json
+++ b/data/presets/fields/source.json
@@ -1,7 +1,14 @@
{
"key": "source",
- "type": "text",
+ "type": "semiCombo",
"icon": "source",
"universal": true,
- "label": "Source"
-}
\ No newline at end of file
+ "label": "Sources",
+ "options": [
+ "survey",
+ "local knowledge",
+ "gps",
+ "aerial imagery",
+ "streetlevel imagery"
+ ]
+}
diff --git a/dist/locales/en.json b/dist/locales/en.json
index 2f4cfb3f95..618af1d786 100644
--- a/dist/locales/en.json
+++ b/dist/locales/en.json
@@ -328,10 +328,9 @@
},
"commit": {
"title": "Upload to OpenStreetMap",
- "description_placeholder": "Brief description of your contributions (required)",
- "message_label": "Changeset Comment",
"upload_explanation": "The changes you upload will be visible on all maps that use OpenStreetMap data.",
"upload_explanation_with_user": "The changes you upload as {user} will be visible on all maps that use OpenStreetMap data.",
+ "request_review": "I would like someone to review my edits.",
"save": "Upload",
"cancel": "Cancel",
"changes": "{count} Changes",
@@ -1354,6 +1353,10 @@
"collection_times": {
"label": "Collection Times"
},
+ "comment": {
+ "label": "Changeset Comment",
+ "placeholder": "Brief description of your contributions (required)"
+ },
"communication_multi": {
"label": "Communication Types"
},
@@ -1566,6 +1569,10 @@
"handrail": {
"label": "Handrail"
},
+ "hashtags": {
+ "label": "Hashtags",
+ "placeholder": "#example"
+ },
"height": {
"label": "Height (Meters)"
},
@@ -2094,7 +2101,7 @@
"label": "Type"
},
"source": {
- "label": "Source"
+ "label": "Sources"
},
"sport_ice": {
"label": "Sports"
diff --git a/modules/behavior/hash.js b/modules/behavior/hash.js
index 431594a7c9..63cece2dfa 100644
--- a/modules/behavior/hash.js
+++ b/modules/behavior/hash.js
@@ -37,7 +37,9 @@ export function behaviorHash(context) {
var center = map.center(),
zoom = map.zoom(),
precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)),
- q = _.omit(utilStringQs(window.location.hash.substring(1)), ['comment', 'walkthrough']),
+ q = _.omit(utilStringQs(window.location.hash.substring(1)),
+ ['comment', 'hashtags', 'walkthrough']
+ ),
newParams = {};
delete q.id;
@@ -99,6 +101,10 @@ export function behaviorHash(context) {
context.storage('commentDate', Date.now());
}
+ if (q.hashtags) {
+ context.storage('hashtags', q.hashtags);
+ }
+
if (q.walkthrough === 'true') {
hash.startWalkthrough = true;
}
diff --git a/modules/modes/save.js b/modules/modes/save.js
index 766f667799..391fe2f47a 100644
--- a/modules/modes/save.js
+++ b/modules/modes/save.js
@@ -1,6 +1,7 @@
import * as d3 from 'd3';
import _ from 'lodash';
+import { d3keybinding } from '../lib/d3.keybinding.js';
import { t } from '../util/locale';
import { JXON } from '../util/jxon';
@@ -34,6 +35,8 @@ export function modeSave(context) {
id: 'save'
};
+ var keybinding = d3keybinding('select');
+
var commit = uiCommit(context)
.on('cancel', cancel)
.on('save', save);
@@ -360,6 +363,12 @@ export function modeSave(context) {
context.ui().sidebar.show(commit);
}
+ keybinding
+ .on('⎋', cancel, true);
+
+ d3.select(document)
+ .call(keybinding);
+
context.container().selectAll('#content')
.attr('class', 'inactive');
@@ -381,6 +390,8 @@ export function modeSave(context) {
mode.exit = function() {
+ keybinding.off();
+
context.container().selectAll('#content')
.attr('class', 'active');
diff --git a/modules/ui/changeset_editor.js b/modules/ui/changeset_editor.js
new file mode 100644
index 0000000000..f04b47e3d7
--- /dev/null
+++ b/modules/ui/changeset_editor.js
@@ -0,0 +1,141 @@
+import * as d3 from 'd3';
+import _ from 'lodash';
+import { d3combobox } from '../lib/d3.combobox.js';
+import { t } from '../util/locale';
+import { svgIcon } from '../svg';
+import { uiField } from './field';
+import { uiFormFields } from './form_fields';
+import { utilRebind, utilTriggerEvent } from '../util';
+
+
+export function uiChangesetEditor(context) {
+ var dispatch = d3.dispatch('change'),
+ formFields = uiFormFields(context),
+ fieldsArr,
+ tags,
+ changesetId;
+
+
+
+ function changesetEditor(selection) {
+ render(selection);
+ }
+
+
+ function render(selection) {
+ var initial = false;
+
+ if (!fieldsArr) {
+ initial = true;
+ var presets = context.presets();
+
+ fieldsArr = [
+ uiField(context, presets.field('comment'), null, { show: true, revert: false }),
+ uiField(context, presets.field('source'), null, { show: false, revert: false }),
+ uiField(context, presets.field('hashtags'), null, { show: false, revert: false }),
+ ];
+
+ fieldsArr.forEach(function(field) {
+ field
+ .on('change', function(t, onInput) {
+ dispatch.call('change', field, t, onInput);
+ });
+ });
+ }
+
+ fieldsArr.forEach(function(field) {
+ field
+ .tags(tags);
+ });
+
+
+ selection
+ .call(formFields.fieldsArr(fieldsArr));
+
+
+ if (initial) {
+ var commentField = selection.select('#preset-input-comment'),
+ commentNode = commentField.node();
+
+ if (commentNode) {
+ commentNode.focus();
+ commentNode.select();
+ }
+
+ // trigger a 'blur' event so that comment field can be cleaned
+ // and checked for hashtags, even if retrieved from localstorage
+ utilTriggerEvent(commentField, 'blur');
+
+ var osm = context.connection();
+ if (osm) {
+ osm.userChangesets(function (err, changesets) {
+ if (err) return;
+
+ var comments = changesets.map(function(changeset) {
+ return {
+ title: changeset.tags.comment,
+ value: changeset.tags.comment
+ };
+ });
+
+ commentField
+ .call(d3combobox()
+ .container(context.container())
+ .caseSensitive(true)
+ .data(_.uniqBy(comments, 'title'))
+ );
+ });
+ }
+ }
+
+ // Add warning if comment mentions Google
+ var hasGoogle = tags.comment.match(/google/i);
+ var commentWarning = selection.select('.form-field-comment').selectAll('.comment-warning')
+ .data(hasGoogle ? [0] : []);
+
+ commentWarning.exit()
+ .transition()
+ .duration(200)
+ .style('opacity', 0)
+ .remove();
+
+ var commentEnter = commentWarning.enter()
+ .insert('div', '.tag-reference-body')
+ .attr('class', 'field-warning comment-warning')
+ .style('opacity', 0);
+
+ commentEnter
+ .append('a')
+ .attr('target', '_blank')
+ .attr('tabindex', -1)
+ .call(svgIcon('#icon-alert', 'inline'))
+ .attr('href', t('commit.google_warning_link'))
+ .append('span')
+ .text(t('commit.google_warning'));
+
+ commentEnter
+ .transition()
+ .duration(200)
+ .style('opacity', 1);
+ }
+
+
+ changesetEditor.tags = function(_) {
+ if (!arguments.length) return tags;
+ tags = _;
+ // Don't reset fieldsArr here.
+ return changesetEditor;
+ };
+
+
+ changesetEditor.changesetID = function(_) {
+ if (!arguments.length) return changesetId;
+ if (changesetId === _) return changesetEditor;
+ changesetId = _;
+ fieldsArr = null;
+ return changesetEditor;
+ };
+
+
+ return utilRebind(changesetEditor, dispatch, 'on');
+}
diff --git a/modules/ui/commit.js b/modules/ui/commit.js
index 088d282e70..9c54a6d4fa 100644
--- a/modules/ui/commit.js
+++ b/modules/ui/commit.js
@@ -1,187 +1,140 @@
import * as d3 from 'd3';
import _ from 'lodash';
import { t } from '../util/locale';
-import { d3combobox } from '../lib/d3.combobox.js';
import { osmChangeset } from '../osm';
-import { modeSelect } from '../modes';
-import { svgIcon } from '../svg';
-import { tooltip } from '../util/tooltip';
+import { uiChangesetEditor } from './changeset_editor';
+import { uiCommitChanges } from './commit_changes';
+import { uiCommitWarnings } from './commit_warnings';
import { uiRawTagEditor } from './raw_tag_editor';
import { utilDetect } from '../util/detect';
-import {
- utilDisplayName,
- utilDisplayType,
- utilEntityOrMemberSelector,
- utilRebind,
- utilTriggerEvent
-} from '../util';
+import { utilRebind } from '../util';
var changeset;
-var readOnlyTags = ['created_by', 'imagery_used', 'host', 'locale'];
+var readOnlyTags = [
+ /^changesets_count$/,
+ /^created_by$/,
+ /^ideditor:/,
+ /^imagery_used$/,
+ /^host$/,
+ /^locale$/
+];
export function uiCommit(context) {
- var dispatch = d3.dispatch('cancel', 'save');
+ var dispatch = d3.dispatch('cancel', 'save'),
+ userDetails,
+ _selection;
+
+ var changesetEditor = uiChangesetEditor(context)
+ .on('change', changeTags);
+ var rawTagEditor = uiRawTagEditor(context)
+ .on('change', changeTags);
+ var commitChanges = uiCommitChanges(context);
+ var commitWarnings = uiCommitWarnings(context);
function commit(selection) {
+ _selection = selection;
+
var osm = context.connection();
if (!osm) return;
- if (!changeset) {
- var detected = utilDetect(),
- tags = {
- created_by: ('iD ' + context.version).substr(0, 255),
- imagery_used: context.history().imageryUsed().join(';').substr(0, 255),
- host: detected.host.substr(0, 255),
- locale: detected.locale.substr(0, 255)
- };
-
- changeset = new osmChangeset({ tags: tags });
- }
-
-
- var changes = context.history().changes(),
- summary = context.history().difference().summary(),
- rawTagEditor = uiRawTagEditor(context).on('change', changeTags),
- comment = context.storage('comment') || '',
+ var comment = context.storage('comment') || '',
commentDate = +context.storage('commentDate') || 0,
+ hashtags = context.storage('hashtags'),
currDate = Date.now(),
cutoff = 2 * 86400 * 1000; // 2 days
- // expire the stored comment if it is too old - #3947
+ // expire stored comment and hashtags after cutoff datetime - #3947
if (commentDate > currDate || currDate - commentDate > cutoff) {
comment = '';
+ hashtags = undefined;
}
- selection
- .append('div')
- .attr('class', 'header fillL')
- .append('h3')
- .text(t('commit.title'));
-
- var body = selection
- .append('div')
- .attr('class', 'body');
-
- var commentSection = body
- .append('div')
- .attr('class', 'modal-section form-field commit-form');
-
- commentSection
- .append('label')
- .attr('class', 'form-label')
- .text(t('commit.message_label'));
-
- var commentField = commentSection
- .append('textarea')
- .attr('class', 'commit-form-comment')
- .attr('placeholder', t('commit.description_placeholder'))
- .attr('maxlength', 255)
- .property('value', comment)
- .on('input.save', change(true))
- .on('change.save', change())
- .on('blur.save', function() {
- context.storage('comment', this.value);
- context.storage('commentDate', Date.now());
- });
-
-
- commentField.node().select();
-
- osm.userChangesets(function (err, changesets) {
- if (err) return;
-
- var comments = changesets.map(function(changeset) {
- return {
- title: changeset.tags.comment,
- value: changeset.tags.comment
- };
- });
-
- commentField
- .call(d3combobox()
- .container(context.container())
- .caseSensitive(true)
- .data(_.uniqBy(comments, 'title'))
- );
- });
-
- var clippyArea = commentSection.append('div')
- .attr('class', 'clippy-area');
+ var tags;
+ if (!changeset) {
+ var detected = utilDetect();
+ tags = {
+ comment: comment,
+ created_by: ('iD ' + context.version).substr(0, 255),
+ imagery_used: context.history().imageryUsed().join(';').substr(0, 255),
+ host: detected.host.substr(0, 255),
+ locale: detected.locale.substr(0, 255)
+ };
- var changeSetInfo = commentSection.append('div')
- .attr('class', 'changeset-info');
+ if (hashtags) {
+ tags.hashtags = hashtags;
+ }
- changeSetInfo.append('a')
- .attr('target', '_blank')
- .attr('tabindex', -1)
- .call(svgIcon('#icon-out-link', 'inline'))
- .attr('href', t('commit.about_changeset_comments_link'))
- .append('span')
- .text(t('commit.about_changeset_comments'));
+ changeset = new osmChangeset({ tags: tags });
+ }
+ tags = _.clone(changeset.tags);
- // Warnings
- var warnings = body.selectAll('div.warning-section')
- .data([context.history().validate(changes)]);
+ var header = selection.selectAll('.header')
+ .data([0]);
- warnings = warnings.enter()
+ header.enter()
.append('div')
- .attr('class', 'modal-section warning-section fillL2')
- .style('display', function(d) { return _.isEmpty(d) ? 'none' : null; })
- .style('background', '#ffb')
- .merge(warnings);
-
- warnings
+ .attr('class', 'header fillL')
.append('h3')
- .text(t('commit.warnings'));
+ .text(t('commit.title'));
- warnings
- .append('ul')
- .attr('class', 'changeset-list');
+ var body = selection.selectAll('.body')
+ .data([0]);
- var warningLi = warnings.select('ul').selectAll('li')
- .data(function(d) { return d; });
+ body = body.enter()
+ .append('div')
+ .attr('class', 'body')
+ .merge(body);
- warningLi = warningLi.enter()
- .append('li')
- .on('mouseover', mouseover)
- .on('mouseout', mouseout)
- .on('click', warningClick)
- .merge(warningLi);
- warningLi
- .call(svgIcon('#icon-alert', 'pre-text'));
+ // Changeset Section
+ var changesetSection = body.selectAll('.changeset-editor')
+ .data([0]);
- warningLi
- .append('strong')
- .text(function(d) { return d.message; });
+ changesetSection = changesetSection.enter()
+ .append('div')
+ .attr('class', 'modal-section changeset-editor')
+ .merge(changesetSection);
- warningLi.filter(function(d) { return d.tooltip; })
- .call(tooltip()
- .title(function(d) { return d.tooltip; })
- .placement('top')
+ changesetSection
+ .call(changesetEditor
+ .changesetID(changeset.id)
+ .tags(tags)
);
+ // Warnings
+ body.call(commitWarnings);
+
+
// Upload Explanation
- var saveSection = body
+ var saveSection = body.selectAll('.save-section')
+ .data([0]);
+
+ saveSection = saveSection.enter()
.append('div')
- .attr('class','modal-section save-section fillL cf');
+ .attr('class','modal-section save-section fillL cf')
+ .merge(saveSection);
- var prose = saveSection
+ var prose = saveSection.selectAll('.commit-info')
+ .data([0]);
+
+ prose = prose.enter()
.append('p')
.attr('class', 'commit-info')
- .html(t('commit.upload_explanation'));
-
+ .text(t('commit.upload_explanation'))
+ .merge(prose);
osm.userDetails(function(err, user) {
if (err) return;
var userLink = d3.select(document.createElement('div'));
+ userDetails = user;
+
if (user.image_url) {
userLink
.append('img')
@@ -202,217 +155,217 @@ export function uiCommit(context) {
});
+ var requestReview = saveSection.selectAll('.request-review')
+ .data([0]);
+
+ requestReview = requestReview.enter()
+ .append('p')
+ .attr('class', 'request-review')
+ .text(t('commit.request_review'))
+ .merge(requestReview);
+
+ var requestReviewField = requestReview.selectAll('input')
+ .data([0]);
+
+ requestReviewField = requestReviewField.enter()
+ .append('input')
+ .attr('type', 'checkbox')
+ .merge(requestReviewField);
+
+ requestReviewField
+ .property('checked', isReviewRequested(changeset.tags))
+ .on('change', toggleRequestReview);
+
+
// Buttons
- var buttonSection = saveSection
+ var buttonSection = saveSection.selectAll('.buttons')
+ .data([0]);
+
+ // enter
+ var buttonEnter = buttonSection.enter()
.append('div')
.attr('class', 'buttons fillL cf');
- var cancelButton = buttonSection
+ buttonEnter
.append('button')
.attr('class', 'secondary-action col5 button cancel-button')
- .on('click.cancel', function() {
- dispatch.call('cancel');
- });
-
- cancelButton
.append('span')
.attr('class', 'label')
.text(t('commit.cancel'));
- var saveButton = buttonSection
+ buttonEnter
.append('button')
.attr('class', 'action col5 button save-button')
+ .append('span')
+ .attr('class', 'label')
+ .text(t('commit.save'));
+
+ // update
+ buttonSection = buttonSection
+ .merge(buttonEnter);
+
+ buttonSection.selectAll('.cancel-button')
+ .on('click.cancel', function() {
+ dispatch.call('cancel');
+ });
+
+ buttonSection.selectAll('.save-button')
.attr('disabled', function() {
- var n = d3.select('.commit-form textarea').node();
+ var n = d3.select('#preset-input-comment').node();
return (n && n.value.length) ? null : true;
})
.on('click.save', function() {
dispatch.call('save', this, changeset);
});
- saveButton
- .append('span')
- .attr('class', 'label')
- .text(t('commit.save'));
-
// Raw Tag Editor
- var tagSection = body
- .append('div')
- .attr('class', 'modal-section tag-section raw-tag-editor');
+ var tagSection = body.selectAll('.tag-section.raw-tag-editor')
+ .data([0]);
-
- // Changes
- var changeSection = body
+ tagSection = tagSection.enter()
.append('div')
- .attr('class', 'commit-section modal-section fillL2');
-
- changeSection.append('h3')
- .text(t('commit.changes', { count: summary.length }));
-
- var li = changeSection
- .append('ul')
- .attr('class', 'changeset-list')
- .selectAll('li')
- .data(summary);
-
- li = li.enter()
- .append('li')
- .on('mouseover', mouseover)
- .on('mouseout', mouseout)
- .on('click', zoomToEntity)
- .merge(li);
-
- li.each(function(d) {
- d3.select(this)
- .call(svgIcon('#icon-' + d.entity.geometry(d.graph), 'pre-text ' + d.changeType));
- });
+ .attr('class', 'modal-section tag-section raw-tag-editor')
+ .merge(tagSection);
+
+ var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();
+ tagSection
+ .call(rawTagEditor
+ .expanded(expanded)
+ .readOnlyTags(readOnlyTags)
+ .tags(_.clone(changeset.tags))
+ );
- li.append('span')
- .attr('class', 'change-type')
- .text(function(d) { return t('commit.' + d.changeType) + ' '; });
- li.append('strong')
- .attr('class', 'entity-type')
- .text(function(d) {
- var matched = context.presets().match(d.entity, d.graph);
- return (matched && matched.name()) || utilDisplayType(d.entity.id);
- });
+ // Change summary
+ body.call(commitChanges);
- li.append('span')
- .attr('class', 'entity-name')
- .text(function(d) {
- var name = utilDisplayName(d.entity) || '',
- string = '';
- if (name !== '') string += ':';
- return string += ' ' + name;
- });
- li.style('opacity', 0)
- .transition()
- .style('opacity', 1);
+ function toggleRequestReview() {
+ var rr = requestReviewField.property('checked');
+ updateChangeset({ review_requested: (rr ? 'yes' : undefined) });
+ var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();
- // Call change() off the bat, in case a changeset
- // comment is recovered from localStorage
- utilTriggerEvent(commentField, 'input');
+ tagSection
+ .call(rawTagEditor
+ .expanded(expanded)
+ .readOnlyTags(readOnlyTags)
+ .tags(_.clone(changeset.tags))
+ );
+ }
+ }
- function mouseover(d) {
- if (d.entity) {
- context.surface().selectAll(
- utilEntityOrMemberSelector([d.entity.id], context.graph())
- ).classed('hover', true);
+ function changeTags(changed, onInput) {
+ if (changed.hasOwnProperty('comment')) {
+ if (changed.comment === undefined) {
+ changed.comment = '';
+ }
+ if (!onInput) {
+ context.storage('comment', changed.comment);
+ context.storage('commentDate', Date.now());
}
}
+ updateChangeset(changed, onInput);
- function mouseout() {
- context.surface().selectAll('.hover')
- .classed('hover', false);
+ if (_selection) {
+ _selection.call(commit);
}
+ }
- function warningClick(d) {
- if (d.entity) {
- context.map().zoomTo(d.entity);
- context.enter(modeSelect(context, [d.entity.id]));
- }
- }
-
+ function findHashtags(tags) {
+ return _.unionBy(commentTags(), hashTags(), function (s) {
+ return s.toLowerCase();
+ });
- function zoomToEntity(change) {
- var entity = change.entity;
- if (change.changeType !== 'deleted' &&
- context.graph().entity(entity.id).geometry(context.graph()) !== 'vertex') {
- context.map().zoomTo(entity);
- context.surface().selectAll(utilEntityOrMemberSelector([entity.id], context.graph()))
- .classed('hover', true);
- }
+ // Extract hashtags from `comment`
+ function commentTags() {
+ return tags.comment.match(/#[^\s\#]+/g);
}
-
- function checkComment(comment) {
- // Save button disabled if there is no comment..
- d3.selectAll('.save-section .save-button')
- .attr('disabled', (comment.length ? null : true));
-
- // Warn if comment mentions Google..
- var googleWarning = clippyArea
- .html('')
- .selectAll('a')
- .data(comment.match(/google/i) ? [true] : []);
-
- googleWarning.exit()
- .remove();
-
- googleWarning.enter()
- .append('a')
- .attr('target', '_blank')
- .attr('tabindex', -1)
- .call(svgIcon('#icon-alert', 'inline'))
- .attr('href', t('commit.google_warning_link'))
- .append('span')
- .text(t('commit.google_warning'));
+ // Extract and clean hashtags from `hashtags`
+ function hashTags() {
+ var t = tags.hashtags || '';
+ return t
+ .split(/[,;\s]+/)
+ .map(function (s) {
+ if (s[0] !== '#') { s = '#' + s; } // prepend '#'
+ var matched = s.match(/#[^\s\#]+/g); // match valid hashtags
+ return matched && matched[0];
+ }).filter(Boolean); // exclude falsey
}
+ }
- function change(onInput) {
- return function() {
- var comment = commentField.property('value').trim();
- if (!onInput) {
- commentField.property('value', comment);
- }
-
- checkComment(comment);
+ function isReviewRequested(tags) {
+ var rr = tags.review_requested;
+ if (rr === undefined) return false;
+ rr = rr.trim().toLowerCase();
+ return !(rr === '' || rr === 'no');
+ }
- var changeset = updateChangeset({ comment: comment });
- var expanded = !tagSection.selectAll('a.hide-toggle.expanded').empty();
- tagSection
- .call(rawTagEditor
- .expanded(expanded)
- .readOnlyTags(readOnlyTags)
- .tags(_.clone(changeset.tags))
- );
- };
- }
+ function updateChangeset(changed, onInput) {
+ var tags = _.clone(changeset.tags);
+ _.forEach(changed, function(v, k) {
+ k = k.trim().substr(0, 255);
+ if (readOnlyTags.indexOf(k) !== -1) return;
- function changeTags(changed) {
- if (changed.hasOwnProperty('comment')) {
- if (changed.comment === undefined) {
- changed.comment = '';
+ if (k !== '' && v !== undefined) {
+ if (onInput) {
+ tags[k] = v;
+ } else {
+ tags[k] = v.trim().substr(0, 255);
}
- changed.comment = changed.comment.trim();
- commentField.property('value', changed.comment);
+ } else {
+ delete tags[k];
}
- updateChangeset(changed);
- utilTriggerEvent(commentField, 'input');
- }
+ });
+ if (!onInput) {
+ var arr = findHashtags(tags);
+ if (arr.length) {
+ tags.hashtags = arr.join(';').substr(0, 255);
+ context.storage('hashtags', tags.hashtags);
+ } else {
+ delete tags.hashtags;
+ context.storage('hashtags', null);
+ }
+ }
- function updateChangeset(changed) {
- var tags = _.clone(changeset.tags);
+ // always update userdetails, just in case user reauthenticates as someone else
+ if (userDetails && userDetails.changesets_count !== undefined) {
+ tags.changesets_count = String(userDetails.changesets_count);
- _.forEach(changed, function(v, k) {
- k = k.trim().substr(0, 255);
- if (readOnlyTags.indexOf(k) !== -1) return;
+ // first 100 edits - new user
+ if (parseInt(tags.changesets_count, 10) < 100) {
+ var s;
+ s = context.storage('walkthrough_completed');
+ if (s) {
+ tags['ideditor:walkthrough_completed'] = s;
+ }
- if (k !== '' && v !== undefined) {
- tags[k] = v.trim().substr(0, 255);
- } else {
- delete tags[k];
+ s = context.storage('walkthrough_progress');
+ if (s) {
+ tags['ideditor:walkthrough_progress'] = s;
}
- });
- if (!_.isEqual(changeset.tags, tags)) {
- changeset = changeset.update({ tags: tags });
+ s = context.storage('walkthrough_started');
+ if (s) {
+ tags['ideditor:walkthrough_started'] = s;
+ }
}
-
- return changeset;
+ } else {
+ delete tags.changesets_count;
}
+ if (!_.isEqual(changeset.tags, tags)) {
+ changeset = changeset.update({ tags: tags });
+ }
}
diff --git a/modules/ui/commit_changes.js b/modules/ui/commit_changes.js
new file mode 100644
index 0000000000..e47fd81b2e
--- /dev/null
+++ b/modules/ui/commit_changes.js
@@ -0,0 +1,116 @@
+import * as d3 from 'd3';
+import { t } from '../util/locale';
+import { svgIcon } from '../svg';
+import {
+ utilDisplayName,
+ utilDisplayType,
+ utilEntityOrMemberSelector
+} from '../util';
+
+
+export function uiCommitChanges(context) {
+
+ function commitChanges(selection) {
+
+ var summary = context.history().difference().summary();
+
+ var container = selection.selectAll('.modal-section.commit-section')
+ .data([0]);
+
+ var containerEnter = container.enter()
+ .append('div')
+ .attr('class', 'commit-section modal-section fillL2');
+
+ containerEnter
+ .append('h3')
+ .text(t('commit.changes', { count: summary.length }));
+
+ containerEnter
+ .append('ul')
+ .attr('class', 'changeset-list');
+
+ container = containerEnter
+ .merge(container);
+
+
+ var items = container.select('ul').selectAll('li')
+ .data(summary);
+
+ var itemsEnter = items.enter()
+ .append('li')
+ .attr('class', 'change-item');
+
+ itemsEnter
+ .each(function(d) {
+ d3.select(this)
+ .call(svgIcon('#icon-' + d.entity.geometry(d.graph), 'pre-text ' + d.changeType));
+ });
+
+ itemsEnter
+ .append('span')
+ .attr('class', 'change-type')
+ .text(function(d) { return t('commit.' + d.changeType) + ' '; });
+
+ itemsEnter
+ .append('strong')
+ .attr('class', 'entity-type')
+ .text(function(d) {
+ var matched = context.presets().match(d.entity, d.graph);
+ return (matched && matched.name()) || utilDisplayType(d.entity.id);
+ });
+
+ itemsEnter
+ .append('span')
+ .attr('class', 'entity-name')
+ .text(function(d) {
+ var name = utilDisplayName(d.entity) || '',
+ string = '';
+ if (name !== '') {
+ string += ':';
+ }
+ return string += ' ' + name;
+ });
+
+ itemsEnter
+ .style('opacity', 0)
+ .transition()
+ .style('opacity', 1);
+
+ items = itemsEnter
+ .merge(items);
+
+ items
+ .on('mouseover', mouseover)
+ .on('mouseout', mouseout)
+ .on('click', zoomToEntity);
+
+
+ function mouseover(d) {
+ if (d.entity) {
+ context.surface().selectAll(
+ utilEntityOrMemberSelector([d.entity.id], context.graph())
+ ).classed('hover', true);
+ }
+ }
+
+
+ function mouseout() {
+ context.surface().selectAll('.hover')
+ .classed('hover', false);
+ }
+
+
+ function zoomToEntity(change) {
+ var entity = change.entity;
+ if (change.changeType !== 'deleted' &&
+ context.graph().entity(entity.id).geometry(context.graph()) !== 'vertex') {
+ context.map().zoomTo(entity);
+ context.surface().selectAll(utilEntityOrMemberSelector([entity.id], context.graph()))
+ .classed('hover', true);
+ }
+ }
+ }
+
+
+ return commitChanges;
+}
diff --git a/modules/ui/commit_warnings.js b/modules/ui/commit_warnings.js
new file mode 100644
index 0000000000..1dfba52cb7
--- /dev/null
+++ b/modules/ui/commit_warnings.js
@@ -0,0 +1,95 @@
+import { t } from '../util/locale';
+import { modeSelect } from '../modes';
+import { svgIcon } from '../svg';
+import { tooltip } from '../util/tooltip';
+import { utilEntityOrMemberSelector } from '../util';
+
+
+export function uiCommitWarnings(context) {
+
+ function commitWarnings(selection) {
+
+ var changes = context.history().changes();
+ var warnings = context.history().validate(changes);
+
+ var container = selection.selectAll('.warning-section')
+ .data(warnings.length ? [0] : []);
+
+ container.exit()
+ .remove();
+
+ var containerEnter = container.enter()
+ .append('div')
+ .attr('class', 'modal-section warning-section fillL2');
+
+ containerEnter
+ .append('h3')
+ .text(t('commit.warnings'));
+
+ containerEnter
+ .append('ul')
+ .attr('class', 'changeset-list');
+
+ container = containerEnter
+ .merge(container);
+
+
+ var items = container.select('ul').selectAll('li')
+ .data(warnings);
+
+ items.exit()
+ .remove();
+
+ var itemsEnter = items.enter()
+ .append('li')
+ .attr('class', 'warning-item');
+
+ itemsEnter
+ .call(svgIcon('#icon-alert', 'pre-text'));
+
+ itemsEnter
+ .append('strong')
+ .text(function(d) { return d.message; });
+
+ itemsEnter.filter(function(d) { return d.tooltip; })
+ .call(tooltip()
+ .title(function(d) { return d.tooltip; })
+ .placement('top')
+ );
+
+ items = itemsEnter
+ .merge(items);
+
+ items
+ .on('mouseover', mouseover)
+ .on('mouseout', mouseout)
+ .on('click', warningClick);
+
+
+ function mouseover(d) {
+ if (d.entity) {
+ context.surface().selectAll(
+ utilEntityOrMemberSelector([d.entity.id], context.graph())
+ ).classed('hover', true);
+ }
+ }
+
+
+ function mouseout() {
+ context.surface().selectAll('.hover')
+ .classed('hover', false);
+ }
+
+
+ function warningClick(d) {
+ if (d.entity) {
+ context.map().zoomTo(d.entity);
+ context.enter(modeSelect(context, [d.entity.id]));
+ }
+ }
+
+ }
+
+
+ return commitWarnings;
+}
diff --git a/modules/ui/field.js b/modules/ui/field.js
index 148369f1d0..ee70f98c55 100644
--- a/modules/ui/field.js
+++ b/modules/ui/field.js
@@ -10,7 +10,10 @@ import { utilRebind } from '../util';
export function uiField(context, presetField, entity, options) {
options = _.extend({
show: true,
- wrap: true
+ wrap: true,
+ remove: true,
+ revert: true,
+ info: true
}, options);
var dispatch = d3.dispatch('change'),
@@ -24,7 +27,7 @@ export function uiField(context, presetField, entity, options) {
dispatch.call('change', field, t, onInput);
});
- if (field.impl.entity) {
+ if (entity && field.impl.entity) {
field.impl.entity(entity);
}
@@ -34,6 +37,7 @@ export function uiField(context, presetField, entity, options) {
function isModified() {
+ if (!entity) return false;
var original = context.graph().base().entities[entity.id];
return _.some(field.keys, function(key) {
return original ? tags[key] !== original.tags[key] : tags[key];
@@ -51,6 +55,7 @@ export function uiField(context, presetField, entity, options) {
function revert(d) {
d3.event.stopPropagation();
d3.event.preventDefault();
+ if (!entity) return false;
var original = context.graph().base().entities[entity.id],
t = {};
@@ -96,19 +101,23 @@ export function uiField(context, presetField, entity, options) {
.append('div')
.attr('class', 'form-label-button-wrap');
- wrap
- .append('button')
- .attr('class', 'remove-icon')
- .attr('tabindex', -1)
- .call(svgIcon('#operation-delete'));
-
- wrap
- .append('button')
- .attr('class', 'modified-icon')
- .attr('tabindex', -1)
- .call(
- (textDirection === 'rtl') ? svgIcon('#icon-redo') : svgIcon('#icon-undo')
- );
+ if (options.remove) {
+ wrap
+ .append('button')
+ .attr('class', 'remove-icon')
+ .attr('tabindex', -1)
+ .call(svgIcon('#operation-delete'));
+ }
+
+ if (options.revert) {
+ wrap
+ .append('button')
+ .attr('class', 'modified-icon')
+ .attr('tabindex', -1)
+ .call(
+ (textDirection === 'rtl') ? svgIcon('#icon-redo') : svgIcon('#icon-undo')
+ );
+ }
}
@@ -126,7 +135,7 @@ export function uiField(context, presetField, entity, options) {
.classed('modified', isModified())
.classed('present', isPresent())
.each(function(d) {
- if (options.wrap) {
+ if (options.wrap && options.info) {
var referenceKey = d.key;
if (d.type === 'multiCombo') { // lookup key without the trailing ':'
referenceKey = referenceKey.replace(/:$/, '');
@@ -141,7 +150,7 @@ export function uiField(context, presetField, entity, options) {
d3.select(this)
.call(d.impl);
- if (options.wrap) {
+ if (options.wrap && options.info) {
d3.select(this)
.call(reference.body)
.select('.form-label-button-wrap')
diff --git a/modules/ui/fields/combo.js b/modules/ui/fields/combo.js
index 1f61bf0bd0..baef52aa07 100644
--- a/modules/ui/fields/combo.js
+++ b/modules/ui/fields/combo.js
@@ -161,12 +161,17 @@ export function uiFieldCombo(field, context) {
query = country + ':';
}
- taginfo[fn]({
+ var params = {
debounce: (q !== ''),
key: field.key,
- geometry: context.geometry(entity.id),
query: query
- }, function(err, data) {
+ };
+
+ if (entity) {
+ params.geometry = context.geometry(entity.id);
+ }
+
+ taginfo[fn](params, function(err, data) {
if (err) return;
if (hasCountryPrefix) {
data = _.filter(data, function(d) {
diff --git a/modules/ui/form_fields.js b/modules/ui/form_fields.js
new file mode 100644
index 0000000000..bcb2345580
--- /dev/null
+++ b/modules/ui/form_fields.js
@@ -0,0 +1,122 @@
+import * as d3 from 'd3';
+import { d3combobox } from '../lib/d3.combobox.js';
+import { t } from '../util/locale';
+import { utilGetSetValue, utilNoAuto } from '../util';
+
+
+export function uiFormFields(context) {
+ var fieldsArr;
+
+
+ function formFields(selection, klass) {
+ render(selection, klass);
+ }
+
+
+ function render(selection, klass) {
+
+ var shown = fieldsArr.filter(function(field) { return field.isShown(); }),
+ notShown = fieldsArr.filter(function(field) { return !field.isShown(); });
+
+ var container = selection.selectAll('.form-fields-container')
+ .data([0]);
+
+ container = container.enter()
+ .append('div')
+ .attr('class', 'form-fields-container ' + (klass || ''))
+ .merge(container);
+
+
+ var fields = container.selectAll('.wrap-form-field')
+ .data(shown, function(d) { return d.id; });
+
+ fields.exit()
+ .remove();
+
+ // Enter
+ var enter = fields.enter()
+ .append('div')
+ .attr('class', function(d) { return 'wrap-form-field wrap-form-field-' + d.id; });
+
+ // Update
+ fields = fields
+ .merge(enter);
+
+ fields
+ .order()
+ .each(function(d) {
+ d3.select(this)
+ .call(d.render);
+ });
+
+
+ notShown = notShown.map(function(field) {
+ return {
+ title: field.label(),
+ value: field.label(),
+ field: field
+ };
+ });
+
+
+ var more = selection.selectAll('.more-fields')
+ .data((notShown.length > 0) ? [0] : []);
+
+ more.exit()
+ .remove();
+
+ more = more.enter()
+ .append('div')
+ .attr('class', 'more-fields')
+ .append('label')
+ .text(t('inspector.add_fields'))
+ .merge(more);
+
+
+ var input = more.selectAll('.value')
+ .data([0]);
+
+ input.exit()
+ .remove();
+
+ input = input.enter()
+ .append('input')
+ .attr('class', 'value')
+ .attr('type', 'text')
+ .call(utilNoAuto)
+ .merge(input);
+
+ input
+ .call(utilGetSetValue, '')
+ .attr('placeholder', function() {
+ var placeholder = [];
+ for (var field in notShown) {
+ placeholder.push(notShown[field].title);
+ }
+ return placeholder.slice(0,3).join(', ') + ((placeholder.length > 3) ? '…' : '');
+ })
+ .call(d3combobox()
+ .container(context.container())
+ .data(notShown)
+ .minItems(1)
+ .on('accept', function (d) {
+ var field = d.field;
+ field.show = true;
+ render(selection);
+ if (field.type !== 'semiCombo' && field.type !== 'multiCombo') {
+ field.focus();
+ }
+ })
+ );
+ }
+
+
+ formFields.fieldsArr = function(_) {
+ if (!arguments.length) return fieldsArr;
+ fieldsArr = _;
+ return formFields;
+ };
+
+
+ return formFields;
+}
diff --git a/modules/ui/index.js b/modules/ui/index.js
index 3abe1ebb27..742aa7b1ca 100644
--- a/modules/ui/index.js
+++ b/modules/ui/index.js
@@ -2,8 +2,11 @@ export { uiInit } from './init';
export { uiAccount } from './account';
export { uiAttribution } from './attribution';
export { uiBackground } from './background';
+export { uiChangesetEditor } from './changeset_editor';
export { uiCmd } from './cmd';
export { uiCommit } from './commit';
+export { uiCommitChanges } from './commit_changes';
+export { uiCommitWarnings } from './commit_warnings';
export { uiConfirm } from './confirm';
export { uiConflicts } from './conflicts';
export { uiContributors } from './contributors';
@@ -15,6 +18,7 @@ export { uiFeatureInfo } from './feature_info';
export { uiFeatureList } from './feature_list';
export { uiField } from './field';
export { uiFlash } from './flash';
+export { uiFormFields } from './form_fields';
export { uiFullScreen } from './full_screen';
export { uiGeolocate } from './geolocate';
export { uiHelp } from './help';
diff --git a/modules/ui/intro/intro.js b/modules/ui/intro/intro.js
index bb351c452a..f87bb9b162 100644
--- a/modules/ui/intro/intro.js
+++ b/modules/ui/intro/intro.js
@@ -1,4 +1,5 @@
import * as d3 from 'd3';
+import _ from 'lodash';
import { t, textDirection } from '../../util/locale';
import { localize } from './helper';
@@ -88,6 +89,13 @@ export function uiIntro(context) {
var curtain = uiCurtain();
selection.call(curtain);
+ // store that the user started the walkthrough..
+ context.storage('walkthrough_started', 'yes');
+
+ // restore previous walkthrough progress..
+ var storedProgress = context.storage('walkthrough_progress') || '';
+ var progress = storedProgress.split(';').filter(Boolean);
+
var chapters = chapterFlow.map(function(chapter, i) {
var s = chapterUi[chapter](context, curtain.reveal)
.on('done', function() {
@@ -102,11 +110,25 @@ export function uiIntro(context) {
d3.select('button.chapter-' + next)
.classed('next', true);
}
+
+ // store walkthrough progress..
+ progress.push(chapter);
+ context.storage('walkthrough_progress', _.uniq(progress).join(';'));
});
return s;
});
chapters[chapters.length - 1].on('startEditing', function() {
+ // store walkthrough progress..
+ progress.push('startEditing');
+ context.storage('walkthrough_progress', _.uniq(progress).join(';'));
+
+ // store if walkthrough is completed..
+ var incomplete = _.difference(chapterFlow, progress);
+ if (!incomplete.length) {
+ context.storage('walkthrough_completed', 'yes');
+ }
+
curtain.remove();
navwrap.remove();
d3.selectAll('#map .layer-background').style('opacity', opacity);
diff --git a/modules/ui/preset_editor.js b/modules/ui/preset_editor.js
index beeb67cc5d..c597256c56 100644
--- a/modules/ui/preset_editor.js
+++ b/modules/ui/preset_editor.js
@@ -1,18 +1,15 @@
import * as d3 from 'd3';
-import { d3combobox } from '../lib/d3.combobox.js';
import { t } from '../util/locale';
import { modeBrowse } from '../modes';
import { uiDisclosure } from './disclosure';
import { uiField } from './field';
-import {
- utilGetSetValue,
- utilNoAuto,
- utilRebind
-} from '../util';
+import { uiFormFields } from './form_fields';
+import { utilRebind } from '../util';
export function uiPresetEditor(context) {
var dispatch = d3.dispatch('change'),
+ formFields = uiFormFields(context),
expandedPreference = (context.storage('preset_fields.expanded') !== 'false'),
state,
fieldsArr,
@@ -80,106 +77,18 @@ export function uiPresetEditor(context) {
.tags(tags);
});
- var shown = fieldsArr.filter(function(field) { return field.isShown(); }),
- notShown = fieldsArr.filter(function(field) { return !field.isShown(); });
+ selection
+ .call(formFields.fieldsArr(fieldsArr), 'inspector-inner fillL3');
- var form = selection.selectAll('.preset-form')
- .data([0]);
- form = form.enter()
- .append('div')
- .attr('class', 'preset-form inspector-inner fillL3')
- .merge(form);
-
-
- var fields = form.selectAll('.wrap-form-field')
- .data(shown, function(d) { return d.id; });
-
- fields.exit()
- .remove();
-
- // Enter
- var enter = fields.enter()
- .append('div')
- .attr('class', function(d) { return 'wrap-form-field wrap-form-field-' + d.id; });
-
- // Update
- fields = fields
- .merge(enter);
-
- fields
- .order()
- .each(function(d) {
- d3.select(this)
- .call(d.render)
- .selectAll('input')
- .on('keydown', function() {
- // if user presses enter, and combobox is not active, accept edits..
- if (d3.event.keyCode === 13 && d3.select('.combobox').empty()) {
- context.enter(modeBrowse(context));
- }
- });
- });
-
-
- notShown = notShown.map(function(field) {
- return {
- title: field.label(),
- value: field.label(),
- field: field
- };
- });
-
-
- var more = selection.selectAll('.more-fields')
- .data((notShown.length > 0) ? [0] : []);
-
- more.exit()
- .remove();
-
- more = more.enter()
- .append('div')
- .attr('class', 'more-fields')
- .append('label')
- .text(t('inspector.add_fields'))
- .merge(more);
-
-
- var input = more.selectAll('.value')
- .data([0]);
-
- input.exit()
- .remove();
-
- input = input.enter()
- .append('input')
- .attr('class', 'value')
- .attr('type', 'text')
- .call(utilNoAuto)
- .merge(input);
-
- input
- .call(utilGetSetValue, '')
- .attr('placeholder', function() {
- var placeholder = [];
- for (var field in notShown) {
- placeholder.push(notShown[field].title);
+ selection.selectAll('.wrap-form-field input')
+ .on('keydown', function() {
+ // if user presses enter, and combobox is not active, accept edits..
+ if (d3.event.keyCode === 13 && d3.select('.combobox').empty()) {
+ context.enter(modeBrowse(context));
}
- return placeholder.slice(0,3).join(', ') + ((placeholder.length > 3) ? '…' : '');
- })
- .call(d3combobox()
- .container(context.container())
- .data(notShown)
- .minItems(1)
- .on('accept', function (d) {
- var field = d.field;
- field.show = true;
- render(selection);
- field.focus();
- })
- );
-
+ });
}
diff --git a/modules/ui/raw_tag_editor.js b/modules/ui/raw_tag_editor.js
index c3bbb4f8a1..2991895d6e 100644
--- a/modules/ui/raw_tag_editor.js
+++ b/modules/ui/raw_tag_editor.js
@@ -175,7 +175,12 @@ export function uiRawTagEditor(context) {
function isReadOnly(d) {
- return readOnlyTags.indexOf(d.key) !== -1;
+ for (var i = 0; i < readOnlyTags.length; i++) {
+ if (d.key.match(readOnlyTags[i]) !== null) {
+ return true;
+ }
+ }
+ return false;
}
diff --git a/modules/ui/tag_reference.js b/modules/ui/tag_reference.js
index 77531839fc..b5e5e5ec87 100644
--- a/modules/ui/tag_reference.js
+++ b/modules/ui/tag_reference.js
@@ -9,8 +9,8 @@ import { svgIcon } from '../svg/index';
export function uiTagReference(tag) {
var taginfo = services.taginfo,
tagReference = {},
- button,
- body,
+ button = d3.select(null),
+ body = d3.select(null),
loaded,
showing;
@@ -46,7 +46,8 @@ export function uiTagReference(tag) {
function load(param) {
if (!taginfo) return;
- button.classed('tag-reference-loading', true);
+ button
+ .classed('tag-reference-loading', true);
taginfo.docs(param, function show(err, data) {
var docs;
@@ -56,22 +57,23 @@ export function uiTagReference(tag) {
body.html('');
-
if (!docs || !docs.title) {
if (param.hasOwnProperty('value')) {
load(_.omit(param, 'value')); // retry with key only
} else {
- body.append('p').text(t('inspector.no_documentation_key'));
+ body
+ .append('p')
+ .attr('class', 'tag-reference-description')
+ .text(t('inspector.no_documentation_key'));
done();
}
return;
}
-
if (docs.image && docs.image.thumb_url_prefix) {
body
.append('img')
- .attr('class', 'wiki-image')
+ .attr('class', 'tag-reference-wiki-image')
.attr('src', docs.image.thumb_url_prefix + '100' + docs.image.thumb_url_suffix)
.on('load', function() { done(); })
.on('error', function() { d3.select(this).remove(); done(); });
@@ -81,16 +83,31 @@ export function uiTagReference(tag) {
body
.append('p')
+ .attr('class', 'tag-reference-description')
.text(docs.description || t('inspector.documentation_redirect'));
body
.append('a')
+ .attr('class', 'tag-reference-link')
.attr('target', '_blank')
.attr('tabindex', -1)
.attr('href', 'https://wiki.openstreetmap.org/wiki/' + docs.title)
.call(svgIcon('#icon-out-link', 'inline'))
.append('span')
.text(t('inspector.reference'));
+
+ // Add link to info about "good changeset comments" - #2923
+ if (param.key === 'comment') {
+ body
+ .append('a')
+ .attr('class', 'tag-reference-comment-link')
+ .attr('target', '_blank')
+ .attr('tabindex', -1)
+ .call(svgIcon('#icon-out-link', 'inline'))
+ .attr('href', t('commit.about_changeset_comments_link'))
+ .append('span')
+ .text(t('commit.about_changeset_comments'));
+ }
});
}
@@ -98,9 +115,12 @@ export function uiTagReference(tag) {
function done() {
loaded = true;
- button.classed('tag-reference-loading', false);
+ button
+ .classed('tag-reference-loading', false);
- body.transition()
+ body
+ .classed('expanded', true)
+ .transition()
.duration(200)
.style('max-height', '200px')
.style('opacity', '1');
@@ -109,12 +129,15 @@ export function uiTagReference(tag) {
}
- function hide(selection) {
- selection = selection || body.transition().duration(200);
-
- selection
+ function hide() {
+ body
+ .transition()
+ .duration(200)
.style('max-height', '0px')
- .style('opacity', '0');
+ .style('opacity', '0')
+ .on('end', function () {
+ body.classed('expanded', false);
+ });
showing = false;
}
@@ -158,7 +181,7 @@ export function uiTagReference(tag) {
.merge(body);
if (showing === false) {
- hide(body);
+ hide();
}
};