Repository: maproulette/maproulette3
Pull request #2029 (2023-06-01 03:29:46)

support osm types extraction from osm identifiers for editor URI
add capability to parse osm types from osm id properties```"id", "@id", "osmIdentifier", "osmid"``` when constructing editor uri.
fixes https://github.com/maproulette/maproulette3/issues/2017

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/maproulette/maproulette3/pull/2029?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=maproulette) Report
> Merging [#2029](https://app.codecov.io/gh/maproulette/maproulette3/pull/2029?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=maproulette) (1cb8f9c) into [develop](https://app.codecov.io/gh/maproulette/maproulette3/commit/a720171108ca2280a110ecbffdcc22b0726c6ba0?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=maproulette) (a720171) will **increase** coverage by `0.01%`.
> The diff coverage is `72.72%`.

:exclamation: Your organization is not using the GitHub App Integration. As a result you may experience degraded service beginning May 15th. Please [install the Github App Integration](https://github.com/apps/codecov) for your organization. [Read more](https://about.codecov.io/blog/codecov-is-updating-its-github-integration/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=maproulette).

```diff
@@             Coverage Diff             @@
##           develop    #2029      +/-   ##
===========================================
+ Coverage    25.04%   25.05%   +0.01%     
===========================================
  Files          631      631              
  Lines        21160    21171      +11     
  Branches      6405     6413       +8     
===========================================
+ Hits          5299     5305       +6     
- Misses       13317    13322       +5     
  Partials      2544     2544              
```


| [Impacted Files](https://app.codecov.io/gh/maproulette/maproulette3/pull/2029?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=maproulette) | Coverage Δ | |
|---|---|---|
| [src/services/Editor/Editor.js](https://app.codecov.io/gh/maproulette/maproulette3/pull/2029?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=maproulette#diff-c3JjL3NlcnZpY2VzL0VkaXRvci9FZGl0b3IuanM=) | `47.53% <72.72%> (-0.46%)` | :arrow_down: |

... and [1 file with indirect coverage changes](https://app.codecov.io/gh/maproulette/maproulette3/pull/2029/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=maproulette)

:mega: We’re building smart automated test selection to slash your CI/CD build times. [Learn more](https://about.codecov.io/iterative-testing/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=maproulette)


Patch:
diff --git a/src/components/AutosuggestTextBox/AutosuggestMentionTextArea.js b/src/components/AutosuggestTextBox/AutosuggestMentionTextArea.js
index c1f13a81..50377da6 100644
--- a/src/components/AutosuggestTextBox/AutosuggestMentionTextArea.js
+++ b/src/components/AutosuggestTextBox/AutosuggestMentionTextArea.js
@@ -65,6 +65,11 @@ export default class AutosuggestMentionTextArea extends Component {
   }
 
   handleKeyDown = (e, dropdown, downshift) => {
+    // Ignore if modifier keys were pressed
+    if (e.metaKey || e.altKey || e.ctrlKey) {
+      return
+    }
+    
     if (dropdown.isDropdownVisible) {
       if (e.key === "ArrowUp" ||
           e.key === "ArrowDown" ||
diff --git a/src/components/AutosuggestTextBox/AutosuggestTextBox.js b/src/components/AutosuggestTextBox/AutosuggestTextBox.js
index 202e6d37..ac62099b 100644
--- a/src/components/AutosuggestTextBox/AutosuggestTextBox.js
+++ b/src/components/AutosuggestTextBox/AutosuggestTextBox.js
@@ -56,6 +56,11 @@ export default class AutosuggestTextBox extends Component {
   }
 
   handleKeyDown = (e, dropdown, downshift) => {
+    // Ignore if modifier keys were pressed
+    if (e.metaKey || e.altKey || e.ctrlKey) {
+      return
+    }
+
     if (e.key === "Enter") {
       // Don't let enter key potentially submit a form
       e.preventDefault()
diff --git a/src/components/ChallengeDetail/ChallengeDetail.js b/src/components/ChallengeDetail/ChallengeDetail.js
index 8952496d..834c0c9b 100644
--- a/src/components/ChallengeDetail/ChallengeDetail.js
+++ b/src/components/ChallengeDetail/ChallengeDetail.js
@@ -251,7 +251,7 @@ export class ChallengeDetail extends Component {
               <Link
                 to={{}}
                 onClick={() =>
-                  this.props.unsaveChallenge(this.props.user.id, challenge.id)
+                  this.props.unsaveChallengeForUser(this.props.user.id, challenge.id)
                 }
                 className="mr-button"
                 title={this.props.intl.formatMessage(messages.removeFromFavorites)}
@@ -264,7 +264,7 @@ export class ChallengeDetail extends Component {
               <Link
                 to={{}}
                 onClick={() =>
-                  this.props.saveChallenge(this.props.user.id, challenge.id)
+                  this.props.saveChallengeForUser(this.props.user.id, challenge.id)
                 }
                 className="mr-button"
                 title={this.props.intl.formatMessage(messages.saveToFavorites)}
diff --git a/src/components/ChallengeDetail/FlagModal.js b/src/components/ChallengeDetail/FlagModal.js
index 9cef4d61..65e5bb0e 100644
--- a/src/components/ChallengeDetail/FlagModal.js
+++ b/src/components/ChallengeDetail/FlagModal.js
@@ -3,6 +3,7 @@ import External from "../External/External";
 import Modal from "../Modal/Modal";
 import FlagCommentInput from "./FlagCommentInput";
 import messages from "./Messages";
+import MarkdownContent from "../MarkdownContent/MarkdownContent";
 import { FormattedMessage } from "react-intl";
 
 const FlagModel = (props) => {
@@ -18,7 +19,7 @@ const FlagModel = (props) => {
           <FormattedMessage {...messages.email} />
         </h2>
         <div className="mr-text-base mr-mt-4 mr-text-yellow">
-         <FormattedMessage {...messages.modalSubtitle} />
+         <MarkdownContent markdown={props.intl.formatMessage(messages.modalSubtitle)} />
         </div>
         <FlagCommentInput challenge={props.challenge} {...props} />
       </Modal>
diff --git a/src/components/ChallengeDetail/Messages.js b/src/components/ChallengeDetail/Messages.js
index f2ce75df..6d392ee5 100644
--- a/src/components/ChallengeDetail/Messages.js
+++ b/src/components/ChallengeDetail/Messages.js
@@ -101,7 +101,7 @@ export default defineMessages({
 
   modalSubtitle: {
     id: "ChallengeDetails.controls.modal.subtitle",
-    defaultMessage:  "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets."
+    defaultMessage:  "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   },
 
   submitFlag: {
diff --git a/src/components/Custom/RJSFFormFieldAdapter/Messages.js b/src/components/Custom/RJSFFormFieldAdapter/Messages.js
index c2868d9b..b3c58a73 100644
--- a/src/components/Custom/RJSFFormFieldAdapter/Messages.js
+++ b/src/components/Custom/RJSFFormFieldAdapter/Messages.js
@@ -28,4 +28,9 @@ export default defineMessages({
     id: 'Form.controls.markdownField.preview.label',
     defaultMessage: "Preview",
   },
+
+  uploadErrorText: {
+    id: 'Form.controls.markdownField.uploadErrorText.label',
+    defaultMessage: "Upload Failed!",
+  }
 })
diff --git a/src/components/Custom/RJSFFormFieldAdapter/RJSFFormFieldAdapter.js b/src/components/Custom/RJSFFormFieldAdapter/RJSFFormFieldAdapter.js
index dc274aa7..b034b89e 100644
--- a/src/components/Custom/RJSFFormFieldAdapter/RJSFFormFieldAdapter.js
+++ b/src/components/Custom/RJSFFormFieldAdapter/RJSFFormFieldAdapter.js
@@ -335,10 +335,16 @@ export const DropzoneTextUpload = ({id, onChange, readonly, formContext, dropAre
       disablePreview
       onDrop={files => {
         formContext[id] = {file: files[0]}
-        onChange(files[0].name)
+        onChange(files[0])
       }}
     >
       {({acceptedFiles, getRootProps, getInputProps}) => {
+        const [uploadErrorText, setUploadErrorText] = useState('')
+
+        if(acceptedFiles[0] !== undefined && !acceptedFiles[0].name.endsWith('.geojson')){
+          acceptedFiles.pop() 
+          setUploadErrorText(<span className="mr-mr-4 mr-text-red-light mr-ml-1"><FormattedMessage {...messages.uploadErrorText} /></span>)
+        }
         const body = acceptedFiles.length > 0 ? <p>{acceptedFiles[0].name}</p> : (
           <span className="mr-flex mr-items-center">
             <SvgSymbol
@@ -346,6 +352,7 @@ export const DropzoneTextUpload = ({id, onChange, readonly, formContext, dropAre
               sym="upload-icon"
               className="mr-fill-current mr-w-3 mr-h-3 mr-mr-4"
             />
+            {uploadErrorText}
             <FormattedMessage {...messages.uploadFilePrompt} />
             <input {...getInputProps()} />
           </span>
diff --git a/src/components/EnhancedMap/SearchControl/LocationSearchBox.js b/src/components/EnhancedMap/SearchControl/LocationSearchBox.js
index 8ccc7654..75da807c 100644
--- a/src/components/EnhancedMap/SearchControl/LocationSearchBox.js
+++ b/src/components/EnhancedMap/SearchControl/LocationSearchBox.js
@@ -24,7 +24,11 @@ export class LocationSearchBox extends Component {
    * @private
    */
   checkForSpecialKeys = (e) => {
-    if (e.key === "Escape") {
+    // Ignore if modifier keys were pressed
+    if (e.metaKey || e.altKey || e.ctrlKey) {
+      return
+    }
+    else if (e.key === "Escape") {
       this.props.clearNominatimSearch()
     }
     else if (e.key === "Enter") {
diff --git a/src/components/EnhancedMap/SearchControl/SearchContent.js b/src/components/EnhancedMap/SearchControl/SearchContent.js
index 3c71f628..8a74e930 100644
--- a/src/components/EnhancedMap/SearchControl/SearchContent.js
+++ b/src/components/EnhancedMap/SearchControl/SearchContent.js
@@ -9,8 +9,12 @@ import messages from './Messages'
 
 const SearchContent = props => {
   const checkForSpecialKeys = e => {
+    // Ignore if modifier keys were pressed
+    if (e.metaKey || e.altKey || e.ctrlKey) {
+      return
+    }
     // Esc clears search, Enter signals completion
-    if (e.key === "Escape") {
+    else if (e.key === "Escape") {
       props.clearNominatimSearch()
     }
     else if (e.key === "Enter") {
diff --git a/src/components/Footer/Footer.js b/src/components/Footer/Footer.js
index 29c0ee99..a3832e0f 100644
--- a/src/components/Footer/Footer.js
+++ b/src/components/Footer/Footer.js
@@ -51,10 +51,23 @@ class Footer extends Component {
             </div>
 
             <div className="md:mr-px-4 md:mr-flex-1 md:mr-flex md:mr-justify-end">
-              <div>
+              <div className="mr-flex mr-flex-col">
                 <h3 className="mr-mb-2 mr-text-xl mr-text-green-lighter">
                   <FormattedMessage {...messages.followUs} />
                 </h3>
+                <a
+                  href="https://en.osm.town/@MapRoulette"
+                  target="_blank"
+                  rel="noopener noreferrer"
+                  className="mr-inline-flex mr-items-center"
+                >
+                  <SvgSymbol
+                    sym="icon-mastodon"
+                    viewBox="0 0 30 24"
+                    className="mr-w-12 mr-h-auto mr-fill-mastodon"
+                  />
+                  <span className="mr-absolute mr-ml-8">@Maproulette</span>
+                </a>
                 <a
                   href="https://twitter.com/maproulette"
                   target="_blank"
diff --git a/src/components/HOCs/WithCurrentUser/WithCurrentUser.js b/src/components/HOCs/WithCurrentUser/WithCurrentUser.js
index de65beba..7ee58ad3 100644
--- a/src/components/HOCs/WithCurrentUser/WithCurrentUser.js
+++ b/src/components/HOCs/WithCurrentUser/WithCurrentUser.js
@@ -8,8 +8,8 @@ import {
   fetchUser,
   fetchBasicUser,
   loadCompleteUser,
-  saveChallenge,
-  unsaveChallenge,
+  saveChallengeForUser,
+  unsaveChallengeForUser,
   saveTask,
   unsaveTask,
   fetchTopChallenges,
@@ -69,8 +69,8 @@ export const mapDispatchToProps = (dispatch, ownProps) => {
       loadCompleteUser,
       logoutUser,
       fetchSavedChallenges,
-      saveChallenge,
-      unsaveChallenge,
+      saveChallengeForUser,
+      unsaveChallengeForUser,
       fetchSavedTasks,
       saveTask,
       unsaveTask,
diff --git a/src/components/InspectTaskControls/InspectTaskControls.js b/src/components/InspectTaskControls/InspectTaskControls.js
index e5082048..45e0722f 100644
--- a/src/components/InspectTaskControls/InspectTaskControls.js
+++ b/src/components/InspectTaskControls/InspectTaskControls.js
@@ -54,6 +54,11 @@ export class InspectTaskControls extends Component {
       return
     }
 
+    // Ignore if modifier keys were pressed
+    if (event.metaKey || event.altKey || event.ctrlKey) {
+      return
+    }
+    
     const inspectShortcuts = this.props.keyboardShortcutGroups[shortcutGroup]
     if (event.key === inspectShortcuts.prevTask.key) {
       this.prevTask()
diff --git a/src/components/QuickTextBox/QuickTextBox.js b/src/components/QuickTextBox/QuickTextBox.js
index 1d6a717d..21371ec9 100644
--- a/src/components/QuickTextBox/QuickTextBox.js
+++ b/src/components/QuickTextBox/QuickTextBox.js
@@ -24,7 +24,11 @@ export default class QuickTextBox extends Component {
    * @private
    */
   checkForSpecialKeys = (e) => {
-    if (e.key === "Escape") {
+    // Ignore if modifier keys were pressed
+    if (e.metaKey || e.altKey || e.ctrlKey) {
+      return
+    }
+    else if (e.key === "Escape") {
       this.props.cancel()
     }
     else if (e.key === "Enter") {
diff --git a/src/components/SearchBox/SearchBox.js b/src/components/SearchBox/SearchBox.js
index 7beba6c6..92b10658 100644
--- a/src/components/SearchBox/SearchBox.js
+++ b/src/components/SearchBox/SearchBox.js
@@ -25,7 +25,11 @@ export default class SearchBox extends Component {
    * @private
    */
   checkForSpecialKeys = (e) => {
-    if (e.key === "Escape") {
+    // Ignore if modifier keys were pressed
+    if (e.metaKey || e.altKey || e.ctrlKey) {
+      return
+    }
+    else if (e.key === "Escape") {
       this.props.clearSearch()
     }
     else if (e.key === "Enter" && _isFunction(this.props.deactivate)) {
diff --git a/src/components/Sprites/Sprites.js b/src/components/Sprites/Sprites.js
index be2a47e0..50055f16 100644
--- a/src/components/Sprites/Sprites.js
+++ b/src/components/Sprites/Sprites.js
@@ -331,6 +331,13 @@ export default function () {
           />
         </symbol>
 
+        <symbol id="icon-mastodon" viewBox="0 0 30 24">
+          <path 
+            d="M11.19 12.195c2.016-.24 3.77-1.475 3.99-2.603.348-1.778.32-4.339.32-4.339 0-3.47-2.286-4.488-2.286-4.488C12.062.238 10.083.017 8.027 0h-.05C5.92.017 3.942.238 2.79.765c0 0-2.285 1.017-2.285 4.488l-.002.662c-.004.64-.007 1.35.011 2.091.083 3.394.626 6.74 3.78 7.57 1.454.383 2.703.463 3.709.408 1.823-.1 2.847-.647 2.847-.647l-.06-1.317s-1.303.41-2.767.36c-1.45-.05-2.98-.156-3.215-1.928a3.614 3.614 0 0 1-.033-.496s1.424.346 3.228.428c1.103.05 2.137-.064 3.188-.189zm1.613-2.47H11.13v-4.08c0-.859-.364-1.295-1.091-1.295-.804 0-1.207.517-1.207 1.541v2.233H7.168V5.89c0-1.024-.403-1.541-1.207-1.541-.727 0-1.091.436-1.091 1.296v4.079H3.197V5.522c0-.859.22-1.541.66-2.046.456-.505 1.052-.764 1.793-.764.856 0 1.504.328 1.933.983L8 4.39l.417-.695c.429-.655 1.077-.983 1.934-.983.74 0 1.336.259 1.791.764.442.505.661 1.187.661 2.046v4.203z"
+            transform="translate(0 5)"
+          />          
+        </symbol>
+
         <symbol id="mr-logo" viewBox="0 0 174 40">
           <path d="M55.6074219,11.359375 L59.625,22.703125 L63.6210938,11.359375 L67.8535156,11.359375 L67.8535156,27 L64.6201172,27 L64.6201172,22.7246094 L64.9423828,15.3447266 L60.7207031,27 L58.5078125,27 L54.296875,15.3554688 L54.6191406,22.7246094 L54.6191406,27 L51.3964844,27 L51.3964844,11.359375 L55.6074219,11.359375 Z M77.1992188,27 C77.0559889,26.7207017 76.9521488,26.3733745 76.8876953,25.9580078 C76.1357384,26.7959026 75.1582091,27.2148438 73.9550781,27.2148438 C72.8164006,27.2148438 71.8728879,26.88542 71.1245117,26.2265625 C70.3761356,25.567705 70.0019531,24.7369842 70.0019531,23.734375 C70.0019531,22.502598 70.4584915,21.557295 71.371582,20.8984375 C72.2846725,20.23958 73.604158,19.9065756 75.3300781,19.8994141 L76.7587891,19.8994141 L76.7587891,19.2333984 C76.7587891,18.6962864 76.6209324,18.2666032 76.3452148,17.9443359 C76.0694973,17.6220687 75.6344431,17.4609375 75.0400391,17.4609375 C74.51725,17.4609375 74.1072606,17.5862618 73.8100586,17.8369141 C73.5128566,18.0875664 73.3642578,18.4313129 73.3642578,18.8681641 L70.2597656,18.8681641 C70.2597656,18.1949836 70.4674458,17.571943 70.8828125,16.9990234 C71.2981792,16.4261039 71.8854129,15.9767269 72.6445312,15.6508789 C73.4036496,15.3250309 74.2558546,15.1621094 75.2011719,15.1621094 C76.6334707,15.1621094 77.7703408,15.5219691 78.6118164,16.2416992 C79.453292,16.9614294 79.8740234,17.9729753 79.8740234,19.2763672 L79.8740234,24.3144531 C79.8811849,25.4173232 80.0351547,26.2516248 80.3359375,26.8173828 L80.3359375,27 L77.1992188,27 Z M74.6318359,24.8408203 C75.0901716,24.8408203 75.5126934,24.7387706 75.8994141,24.534668 C76.2861347,24.3305654 76.5725902,24.0566423 76.7587891,23.7128906 L76.7587891,21.7148438 L75.5986328,21.7148438 C74.0445886,21.7148438 73.2174484,22.2519478 73.1171875,23.3261719 L73.1064453,23.5087891 C73.1064453,23.8955097 73.2425117,24.2141915 73.5146484,24.4648438 C73.7867852,24.715496 74.1591773,24.8408203 74.6318359,24.8408203 Z M92.7216797,21.2958984 C92.7216797,23.086272 92.315271,24.5203397 91.5024414,25.5981445 C90.6896118,26.6759494 89.5921293,27.2148438 88.2099609,27.2148438 C87.0354759,27.2148438 86.0865922,26.8066447 85.3632812,25.9902344 L85.3632812,31.46875 L82.2587891,31.46875 L82.2587891,15.3769531 L85.1376953,15.3769531 L85.2451172,16.515625 C85.9970741,15.6132767 86.9781841,15.1621094 88.1884766,15.1621094 C89.6207754,15.1621094 90.734371,15.692052 91.5292969,16.7519531 C92.3242227,17.8118543 92.7216797,19.2727771 92.7216797,21.1347656 L92.7216797,21.2958984 Z M89.6171875,21.0703125 C89.6171875,19.9889269 89.4256204,19.1546253 89.0424805,18.5673828 C88.6593405,17.9801403 88.1025427,17.6865234 87.3720703,17.6865234 C86.3981071,17.6865234 85.7285175,18.0589155 85.3632812,18.8037109 L85.3632812,23.5625 C85.7428404,24.3287799 86.4195915,24.7119141 87.3935547,24.7119141 C88.875984,24.7119141 89.6171875,23.498059 89.6171875,21.0703125 Z M101.003906,20.6728516 L97.3300781,20.6728516 L97.3300781,27 L95.2568359,27 L95.2568359,11.359375 L100.43457,11.359375 C102.196298,11.359375 103.55159,11.7604127 104.500488,12.5625 C105.449386,13.3645873 105.923828,14.5318934 105.923828,16.0644531 C105.923828,17.0384163 105.660647,17.8870407 105.134277,18.6103516 C104.607908,19.3336625 103.875656,19.8743472 102.9375,20.2324219 L106.611328,26.8710938 L106.611328,27 L104.398438,27 L101.003906,20.6728516 Z M97.3300781,18.9863281 L100.499023,18.9863281 C101.523117,18.9863281 102.337725,18.7213568 102.942871,18.1914062 C103.548017,17.6614557 103.850586,16.9524784 103.850586,16.0644531 C103.850586,15.0976514 103.56234,14.3564479 102.98584,13.8408203 C102.40934,13.3251927 101.576828,13.0638021 100.488281,13.0566406 L97.3300781,13.0566406 L97.3300781,18.9863281 Z M107.975586,21.0810547 C107.975586,19.9423771 108.199379,18.9182988 108.646973,18.0087891 C109.094566,17.0992793 109.717607,16.3974634 110.516113,15.9033203 C111.31462,15.4091772 112.225906,15.1621094 113.25,15.1621094 C114.83269,15.1621094 116.112788,15.7099555 117.090332,16.8056641 C118.067876,17.9013727 118.556641,19.3587149 118.556641,21.1777344 L118.556641,21.3173828 C118.556641,22.4488989 118.340009,23.4640255 117.906738,24.362793 C117.473468,25.2615605 116.854008,25.961586 116.04834,26.4628906 C115.242672,26.9641952 114.317063,27.2148438 113.271484,27.2148438 C111.695956,27.2148438 110.419438,26.6669977 109.441895,25.5712891 C108.464351,24.4755805 107.975586,23.0253996 107.975586,21.2207031 L107.975586,21.0810547 Z M109.973633,21.3173828 C109.973633,22.6064518 110.272621,23.6412721 110.870605,24.421875 C111.46859,25.2024779 112.268875,25.5927734 113.271484,25.5927734 C114.281255,25.5927734 115.08333,25.1971068 115.677734,24.4057617 C116.272138,23.6144166 116.569336,22.506192 116.569336,21.0810547 C116.569336,19.8063087 116.266767,18.7732787 115.661621,17.9819336 C115.056475,17.1905885 114.252609,16.7949219 113.25,16.7949219 C112.268875,16.7949219 111.477542,17.1852175 110.875977,17.9658203 C110.274411,18.7464232 109.973633,19.8635995 109.973633,21.3173828 Z M128.224609,25.8505859 C127.451168,26.7600957 126.316088,27.2148438 124.819336,27.2148438 C123.580397,27.2148438 122.636885,26.8549841 121.98877,26.1352539 C121.340654,25.4155237 121.013021,24.3502675 121.005859,22.9394531 L121.005859,15.3769531 L122.993164,15.3769531 L122.993164,22.8857422 C122.993164,24.6474697 123.709303,25.5283203 125.141602,25.5283203 C126.659838,25.5283203 127.669594,24.9625708 128.170898,23.8310547 L128.170898,15.3769531 L130.158203,15.3769531 L130.158203,27 L128.267578,27 L128.224609,25.8505859 Z M135.335938,27 L133.348633,27 L133.348633,10.5 L135.335938,10.5 L135.335938,27 Z M143.338867,27.2148438 C141.763338,27.2148438 140.48145,26.6974336 139.493164,25.6625977 C138.504878,24.6277618 138.010742,23.2438238 138.010742,21.5107422 L138.010742,21.1455078 C138.010742,19.9925073 138.230955,18.9630579 138.671387,18.0571289 C139.111819,17.1511999 139.727698,16.4422226 140.519043,15.9301758 C141.310388,15.418129 142.167964,15.1621094 143.091797,15.1621094 C144.602872,15.1621094 145.77734,15.6598258 146.615234,16.6552734 C147.453129,17.6507211 147.87207,19.0758371 147.87207,20.9306641 L147.87207,21.7578125 L139.998047,21.7578125 C140.026693,22.9036516 140.361488,23.8292608 141.002441,24.534668 C141.643395,25.2400751 142.458003,25.5927734 143.446289,25.5927734 C144.148115,25.5927734 144.742511,25.4495457 145.229492,25.1630859 C145.716474,24.8766262 146.142576,24.4970727 146.507812,24.0244141 L147.72168,24.9697266 C146.747716,26.4664788 145.286794,27.2148438 143.338867,27.2148438 Z M143.091797,16.7949219 C142.28971,16.7949219 141.616539,17.0867484 141.072266,17.6704102 C140.527992,18.2540719 140.191407,19.0722604 140.0625,20.125 L145.884766,20.125 L145.884766,19.9746094 C145.827474,18.9648387 145.555341,18.1824572 145.068359,17.6274414 C144.581378,17.0724256 143.92253,16.7949219 143.091797,16.7949219 Z M152.867188,12.5625 L152.867188,15.3769531 L155.037109,15.3769531 L155.037109,16.9130859 L152.867188,16.9130859 L152.867188,24.1210938 C152.867188,24.5865909 152.963866,24.9357085 153.157227,25.168457 C153.350587,25.4012056 153.680011,25.5175781 154.145508,25.5175781 C154.374676,25.5175781 154.689777,25.4746098 155.09082,25.3886719 L155.09082,27 C154.568031,27.1432299 154.059573,27.2148438 153.56543,27.2148438 C152.677404,27.2148438 152.007815,26.9462917 151.556641,26.4091797 C151.105466,25.8720676 150.879883,25.1093799 150.879883,24.1210938 L150.879883,16.9130859 L148.763672,16.9130859 L148.763672,15.3769531 L150.879883,15.3769531 L150.879883,12.5625 L152.867188,12.5625 Z M160.053711,12.5625 L160.053711,15.3769531 L162.223633,15.3769531 L162.223633,16.9130859 L160.053711,16.9130859 L160.053711,24.1210938 C160.053711,24.5865909 160.15039,24.9357085 160.34375,25.168457 C160.53711,25.4012056 160.866534,25.5175781 161.332031,25.5175781 C161.561199,25.5175781 161.8763,25.4746098 162.277344,25.3886719 L162.277344,27 C161.754555,27.1432299 161.246096,27.2148438 160.751953,27.2148438 C159.863928,27.2148438 159.194338,26.9462917 158.743164,26.4091797 C158.29199,25.8720676 158.066406,25.1093799 158.066406,24.1210938 L158.066406,16.9130859 L155.950195,16.9130859 L155.950195,15.3769531 L158.066406,15.3769531 L158.066406,12.5625 L160.053711,12.5625 Z M169.367188,27.2148438 C167.791659,27.2148438 166.509771,26.6974336 165.521484,25.6625977 C164.533198,24.6277618 164.039062,23.2438238 164.039062,21.5107422 L164.039062,21.1455078 C164.039062,19.9925073 164.259275,18.9630579 164.699707,18.0571289 C165.140139,17.1511999 165.756018,16.4422226 166.547363,15.9301758 C167.338708,15.418129 168.196284,15.1621094 169.120117,15.1621094 C170.631192,15.1621094 171.80566,15.6598258 172.643555,16.6552734 C173.48145,17.6507211 173.900391,19.0758371 173.900391,20.9306641 L173.900391,21.7578125 L166.026367,21.7578125 C166.055013,22.9036516 166.389808,23.8292608 167.030762,24.534668 C167.671715,25.2400751 168.486323,25.5927734 169.474609,25.5927734 C170.176436,25.5927734 170.770831,25.4495457 171.257812,25.1630859 C171.744794,24.8766262 172.170897,24.4970727 172.536133,24.0244141 L173.75,24.9697266 C172.776037,26.4664788 171.315114,27.2148438 169.367188,27.2148438 Z M169.120117,16.7949219 C168.31803,16.7949219 167.644859,17.0867484 167.100586,17.6704102 C166.556312,18.2540719 166.219727,19.0722604 166.09082,20.125 L171.913086,20.125 L171.913086,19.9746094 C171.855794,18.9648387 171.583661,18.1824572 171.09668,17.6274414 C170.609698,17.0724256 169.950851,16.7949219 169.120117,16.7949219 Z" />
           <path d="M19.8753324,25.4652697 C16.7880954,25.4652697 14.2853952,22.9625695 14.2853952,19.8753324 C14.2853952,16.7880954 16.7880954,14.2853952 19.8753324,14.2853952 C22.9625695,14.2853952 25.4652697,16.7880954 25.4652697,19.8753324 C25.4652697,22.9625695 22.9625695,25.4652697 19.8753324,25.4652697 Z M19.8753324,24.2230614 C22.2765169,24.2230614 24.2230614,22.2765169 24.2230614,19.8753324 C24.2230614,17.474148 22.2765169,15.5276035 19.8753324,15.5276035 C17.474148,15.5276035 15.5276035,17.474148 15.5276035,19.8753324 C15.5276035,22.2765169 17.474148,24.2230614 19.8753324,24.2230614 Z" />
diff --git a/src/components/TagDiffVisualization/TagDiffVisualization.js b/src/components/TagDiffVisualization/TagDiffVisualization.js
index 99bf8e6a..266b5308 100644
--- a/src/components/TagDiffVisualization/TagDiffVisualization.js
+++ b/src/components/TagDiffVisualization/TagDiffVisualization.js
@@ -468,7 +468,11 @@ export const AddTagControl = props => {
         onChange={e => props.setNewTagName(e.target.value)}
         placeholder={props.intl.formatMessage(messages.tagNamePlaceholder)}
         onKeyDown={e => { // Support Enter and ESC keys
-          if (e.key === "Escape") {
+          // Ignore if modifier keys were pressed
+          if (e.metaKey || e.altKey || e.ctrlKey) {
+            return
+          }
+          else if (e.key === "Escape") {
             props.cancelNewTag()
           }
           else if (props.newTagValid && e.key === "Enter") {
diff --git a/src/components/TaskConfirmationModal/TaskConfirmationModal.js b/src/components/TaskConfirmationModal/TaskConfirmationModal.js
index 77c7648a..ccc350da 100644
--- a/src/components/TaskConfirmationModal/TaskConfirmationModal.js
+++ b/src/components/TaskConfirmationModal/TaskConfirmationModal.js
@@ -54,6 +54,11 @@ export class TaskConfirmationModal extends Component {
       return
     }
 
+    // Ignore if modifier keys were pressed
+    if (event.metaKey || event.altKey || event.ctrlKey) {
+      return
+    }
+    
     if (event.key ===
         this.props.keyboardShortcutGroups.taskConfirmation.confirmSubmit.key &&
         event.shiftKey) {
diff --git a/src/components/TaskPane/ActiveTaskDetails/ActiveTaskControls/TaskCancelEditingControl/TaskCancelEditingControl.js b/src/components/TaskPane/ActiveTaskDetails/ActiveTaskControls/TaskCancelEditingControl/TaskCancelEditingControl.js
index ab32929b..f6aa30aa 100644
--- a/src/components/TaskPane/ActiveTaskDetails/ActiveTaskControls/TaskCancelEditingControl/TaskCancelEditingControl.js
+++ b/src/components/TaskPane/ActiveTaskDetails/ActiveTaskControls/TaskCancelEditingControl/TaskCancelEditingControl.js
@@ -26,6 +26,11 @@ export default class TaskCancelEditingControl extends Component {
       return
     }
 
+    // Ignore if modifier keys were pressed
+    if (event.metaKey || event.altKey || event.ctrlKey) {
+      return
+    }
+    
     const shortcuts = this.props.keyboardShortcutGroups.taskEditing
     if (event.key === shortcuts.cancel.key) {
       this.props.cancelEditing()
diff --git a/src/components/TaskPane/TaskMap/TaskMap.js b/src/components/TaskPane/TaskMap/TaskMap.js
index 2dadd020..cd2388be 100644
--- a/src/components/TaskPane/TaskMap/TaskMap.js
+++ b/src/components/TaskPane/TaskMap/TaskMap.js
@@ -96,6 +96,11 @@ export class TaskMap extends Component {
       return
     }
 
+    // Ignore if modifier keys were pressed
+    if (event.metaKey || event.altKey || event.ctrlKey) {
+      return
+    }
+    
     const layerShortcuts = this.props.keyboardShortcutGroups[shortcutGroup]
     switch(event.key) {
       case layerShortcuts.layerOSMData.key:
diff --git a/src/components/TaskPane/TaskPane.js b/src/components/TaskPane/TaskPane.js
index 5f680115..2e898111 100644
--- a/src/components/TaskPane/TaskPane.js
+++ b/src/components/TaskPane/TaskPane.js
@@ -227,7 +227,7 @@ export class TaskPane extends Component {
         <li>
           <a
             className="mr-normal-case mr-flex"
-            onClick={() => (isFavorited ? this.props.unsaveChallenge : this.props.saveChallenge)(
+            onClick={() => (isFavorited ? this.props.unsaveChallengeForUser : this.props.saveChallengeForUser)(
               this.props.user.id,
               challenge.id
             )}
diff --git a/src/components/Widgets/TaskBundleWidget/TaskBundleWidget.js b/src/components/Widgets/TaskBundleWidget/TaskBundleWidget.js
index 08df42d2..35db722b 100644
--- a/src/components/Widgets/TaskBundleWidget/TaskBundleWidget.js
+++ b/src/components/Widgets/TaskBundleWidget/TaskBundleWidget.js
@@ -90,6 +90,11 @@ export default class TaskBundleWidget extends Component {
       return
     }
 
+    // Ignore if modifier keys were pressed
+    if (event.metaKey || event.altKey || event.ctrlKey) {
+      return
+    }
+    
     const shortcuts = this.props.keyboardShortcutGroups.taskEditing
     if (event.key === shortcuts.completeTogether.key) {
       this.bundleTasks()
@@ -163,7 +168,7 @@ export default class TaskBundleWidget extends Component {
 
     this.props.activateKeyboardShortcut(
       shortcutGroup,
-      _pick(this.props.keyboardShortcutGroups.taskEditing, 'c'),
+      _pick(this.props.keyboardShortcutGroups.taskEditing, 'completeTogether'),
       this.handleKeyboardShortcuts)
   }
 
@@ -189,7 +194,7 @@ export default class TaskBundleWidget extends Component {
       this.props.unsubscribeFromChallengeTaskMessages(challengeId)
     }
 
-    this.props.deactivateKeyboardShortcut(shortcutGroup, 'c',
+    this.props.deactivateKeyboardShortcut(shortcutGroup, 'completeTogether',
                                           this.handleKeyboardShortcuts)
   }
 
@@ -400,7 +405,7 @@ registerWidgetType(
               WithBoundedTasks(
                 WithBrowsedChallenge(
                   WithWebSocketSubscriptions(
-                    WithKeyboardShortcuts(TaskBundleWidget )
+                    WithKeyboardShortcuts(TaskBundleWidget)
                   )
                 ),
                 'filteredClusteredTasks',
diff --git a/src/interactions/TaskFeature/AsIdentifiableFeature.js b/src/interactions/TaskFeature/AsIdentifiableFeature.js
index a8389c23..4ea84f95 100644
--- a/src/interactions/TaskFeature/AsIdentifiableFeature.js
+++ b/src/interactions/TaskFeature/AsIdentifiableFeature.js
@@ -129,6 +129,44 @@ export class AsIdentifiableFeature {
     const re = new RegExp(/^(node|way|relation|n|r|w)?\/?\d+$/)
     return !!re.exec(id)
   }
+
+  // check whether osm types are consistent if there are multiple Ids
+  checkValidTypeMultipleIds(type) {
+    const typeRe = /^(node|way|relation|n|r|w)/
+    for (let i = 0; i < featureIdFields.length; i++) {
+      const match = typeRe.exec(this.properties[featureIdFields[i]])
+      let currentType
+
+      if (match) {
+        switch (match[0]) {
+          case 'node':
+          case 'n':
+            currentType = 'node'
+            break
+          case 'way':
+          case 'w':
+            currentType = 'way'
+            break
+          case 'relation':
+          case 'r':
+            currentType = 'relation'
+            break
+          default:
+            currentType = null
+        }
+      } else {
+        currentType = null
+      }
+
+      if (!currentType) {
+        continue
+      } else if (currentType != type) {
+        return false
+      }
+    }
+
+    return true
+  }
 }
 
 export default feature => new AsIdentifiableFeature(feature)
diff --git a/src/lang/af.json b/src/lang/af.json
index ae24d2c7..226a1cdd 100644
--- a/src/lang/af.json
+++ b/src/lang/af.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Save to favorites",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Go Back",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Start",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Skip",
   "KeyMapping.taskCompletion.tooHard": "Too difficult / Couldn’t see",
   "KeyMapping.taskEditing.cancel": "Cancel Editing",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Fit Map to Task Features",
   "KeyMapping.taskInspect.nextTask": "Next Task",
diff --git a/src/lang/cs_CZ.json b/src/lang/cs_CZ.json
index 033010cd..703ce369 100644
--- a/src/lang/cs_CZ.json
+++ b/src/lang/cs_CZ.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Save to favorites",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Go Back",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Start",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Přeskočit",
   "KeyMapping.taskCompletion.tooHard": "Příliš obtížné / Neviděl jsem",
   "KeyMapping.taskEditing.cancel": "Cancel Editing",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Fit Map to Task Features",
   "KeyMapping.taskInspect.nextTask": "Next Task",
diff --git a/src/lang/de.json b/src/lang/de.json
index f9fa2bfe..e2e13c5c 100644
--- a/src/lang/de.json
+++ b/src/lang/de.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "In Favoritenliste speichern",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Zurück",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Start",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Überspringen",
   "KeyMapping.taskCompletion.tooHard": "Zu schwierig",
   "KeyMapping.taskEditing.cancel": "Bearbeiten abbrechen",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Skaliere Karte auf die Aufgabe",
   "KeyMapping.taskInspect.nextTask": "Nächste",
diff --git a/src/lang/en-US.json b/src/lang/en-US.json
index d333e697..5a35a880 100644
--- a/src/lang/en-US.json
+++ b/src/lang/en-US.json
@@ -527,7 +527,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Save to favorites",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Go Back",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Start",
@@ -999,7 +999,7 @@
   "KeyMapping.taskCompletion.skip": "Skip",
   "KeyMapping.taskCompletion.tooHard": "Too difficult / Couldn’t see",
   "KeyMapping.taskEditing.cancel": "Cancel Editing",
-  "KeyMapping.taskEditing.completeTogether": "Complete tasks together",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Fit Map to Task Features",
   "KeyMapping.taskInspect.nextTask": "Next Task",
diff --git a/src/lang/es.json b/src/lang/es.json
index 867299bd..1eeff4ff 100644
--- a/src/lang/es.json
+++ b/src/lang/es.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Guardar en favoritos",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Regresar",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Comenzar",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Omitir",
   "KeyMapping.taskCompletion.tooHard": "Demasiado difícil / No pude verlo bien",
   "KeyMapping.taskEditing.cancel": "Cancelar edición",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Ajustar mapa a los elementos de la tarea",
   "KeyMapping.taskInspect.nextTask": "Siguiente tarea",
diff --git a/src/lang/fa_IR.json b/src/lang/fa_IR.json
index 71bdc145..08b9c66b 100644
--- a/src/lang/fa_IR.json
+++ b/src/lang/fa_IR.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Save to favorites",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Go Back",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Start",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Skip",
   "KeyMapping.taskCompletion.tooHard": "Too difficult / Couldn’t see",
   "KeyMapping.taskEditing.cancel": "Cancel Editing",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Fit Map to Task Features",
   "KeyMapping.taskInspect.nextTask": "Next Task",
diff --git a/src/lang/fr.json b/src/lang/fr.json
index 67a29f38..04e4012d 100644
--- a/src/lang/fr.json
+++ b/src/lang/fr.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Enregistrer en favoris",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Retour",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Démarrer",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Passer",
   "KeyMapping.taskCompletion.tooHard": "Trop difficile / Pas visible",
   "KeyMapping.taskEditing.cancel": "Annuler",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Adapter la carte aux caractéristiques de la tâche",
   "KeyMapping.taskInspect.nextTask": "Tâche suivante",
diff --git a/src/lang/it_IT.json b/src/lang/it_IT.json
index f7a5d600..cd354c9e 100644
--- a/src/lang/it_IT.json
+++ b/src/lang/it_IT.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Salva nei preferiti",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Torna indietro",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Inizia",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Salta",
   "KeyMapping.taskCompletion.tooHard": "Troppo difficile / Non riuscivo a vedere",
   "KeyMapping.taskEditing.cancel": "Annulla modifica",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "Esci",
   "KeyMapping.taskEditing.fitBounds": "Adatta la mappa alle funzionalità del compito",
   "KeyMapping.taskInspect.nextTask": "Compito successivo",
diff --git a/src/lang/ja.json b/src/lang/ja.json
index eda19581..71778fff 100644
--- a/src/lang/ja.json
+++ b/src/lang/ja.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "お気に入りに保存",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "戻る",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "始める",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "スキップ",
   "KeyMapping.taskCompletion.tooHard": "Too difficult / Couldn’t see",
   "KeyMapping.taskEditing.cancel": "編集をキャンセル",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "タスクの内容にマップを合わせる",
   "KeyMapping.taskInspect.nextTask": "次のタスク",
diff --git a/src/lang/ko.json b/src/lang/ko.json
index e453d270..7fff5a0b 100644
--- a/src/lang/ko.json
+++ b/src/lang/ko.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "즐겨찾기에 저장",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "뒤로가기",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "시작",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "넘기기",
   "KeyMapping.taskCompletion.tooHard": "너무 어려움 / 볼 수 없음",
   "KeyMapping.taskEditing.cancel": "편집 취소",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "지도를 작업할 지물에 맞추기",
   "KeyMapping.taskInspect.nextTask": "다음 작업",
diff --git a/src/lang/nl.json b/src/lang/nl.json
index 0f72821f..dfcd914d 100644
--- a/src/lang/nl.json
+++ b/src/lang/nl.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Opslaan naar Favorieten",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Ga terug",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Beginnen",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Overslaan",
   "KeyMapping.taskCompletion.tooHard": "Te moeilijk / Onduidelijk",
   "KeyMapping.taskEditing.cancel": "Bewerken annuleren",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Kaart passend aan objecten van taak maken",
   "KeyMapping.taskInspect.nextTask": "Volgende taak",
diff --git a/src/lang/pl.json b/src/lang/pl.json
index c724d0f7..804198ed 100644
--- a/src/lang/pl.json
+++ b/src/lang/pl.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Dodaj do ulubionych",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Wróć",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Rozpocznij",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Pomiń",
   "KeyMapping.taskCompletion.tooHard": "Zbyt trudne / Nie mogłem zobaczyć",
   "KeyMapping.taskEditing.cancel": "Anuluj edycję",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Fit Map to Task Features",
   "KeyMapping.taskInspect.nextTask": "Następne zadanie",
diff --git a/src/lang/pt_BR.json b/src/lang/pt_BR.json
index ae631b7d..c2553616 100644
--- a/src/lang/pt_BR.json
+++ b/src/lang/pt_BR.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Save to favorites",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Go Back",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Start",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Ignorar",
   "KeyMapping.taskCompletion.tooHard": "Too difficult / Couldn’t see",
   "KeyMapping.taskEditing.cancel": "Cancelar edição",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Ajustar Mapa aos Recursos da Tarefa",
   "KeyMapping.taskInspect.nextTask": "Próxima tarefa",
diff --git a/src/lang/ru_RU.json b/src/lang/ru_RU.json
index 3a8e3022..69dab15f 100644
--- a/src/lang/ru_RU.json
+++ b/src/lang/ru_RU.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Сохранить в избранное",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Вернуться",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Начать",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Пропустить",
   "KeyMapping.taskCompletion.tooHard": "Слишком сложно / Не вижу",
   "KeyMapping.taskEditing.cancel": "Отменить изменения",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "Выйти",
   "KeyMapping.taskEditing.fitBounds": "Подогнать карту под требования задачи",
   "KeyMapping.taskInspect.nextTask": "Следующая задача",
diff --git a/src/lang/tr.json b/src/lang/tr.json
index 7eae6a18..50c119cd 100644
--- a/src/lang/tr.json
+++ b/src/lang/tr.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Favorilere kaydet",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Geri Dön",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Başla",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Geç",
   "KeyMapping.taskCompletion.tooHard": "Çok zor / Görülemedi",
   "KeyMapping.taskEditing.cancel": "Düzenlemeyi İptal Et",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Haritayı Görev Özelliklerine Sığdır",
   "KeyMapping.taskInspect.nextTask": "Sonraki Görev",
diff --git a/src/lang/uk.json b/src/lang/uk.json
index 013dafba..54d6d872 100644
--- a/src/lang/uk.json
+++ b/src/lang/uk.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Додати в закладки",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Повернутись",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Розпочати",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Пропустити",
   "KeyMapping.taskCompletion.tooHard": "Дуже важко/Не бачу",
   "KeyMapping.taskEditing.cancel": "Скасувати редагування",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Підігнати масштаб під елементи завдання",
   "KeyMapping.taskInspect.nextTask": "Наступне Завдання",
diff --git a/src/lang/vi.json b/src/lang/vi.json
index 66ddfe36..69002541 100644
--- a/src/lang/vi.json
+++ b/src/lang/vi.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Lưu vào ưa thích",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "Quay lại",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "Bắt đầu",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "Skip",
   "KeyMapping.taskCompletion.tooHard": "Too difficult / Couldn’t see",
   "KeyMapping.taskEditing.cancel": "Cancel Editing",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Fit Map to Task Features",
   "KeyMapping.taskInspect.nextTask": "Next Task",
diff --git a/src/lang/zh_TW.json b/src/lang/zh_TW.json
index 86fd1f94..832addfe 100644
--- a/src/lang/zh_TW.json
+++ b/src/lang/zh_TW.json
@@ -522,7 +522,7 @@
   "ChallengeDetails.controls.favorite.tooltip": "Save to favorites",
   "ChallengeDetails.controls.flagged_text": "This challenge has been flagged",
   "ChallengeDetails.controls.goBack.label": "返回",
-  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created here and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
+  "ChallengeDetails.controls.modal.subtitle": "You are about to flag a Challenge. An issue will be created in this [public github repository](https://github.com/maproulette/challenge-reports/issues) and the Challenge creator will be notified by email. Any follow-up discussion should take place there. Flagging a Challenge does not disable it immediately. Please explain in detail what your issue is with this challenge, if possible linking to specific OSM changesets.",
   "ChallengeDetails.controls.preview.label": "Preview",
   "ChallengeDetails.controls.review.label": "I have attempted to contact the Challenge creator",
   "ChallengeDetails.controls.start.label": "開始",
@@ -994,6 +994,7 @@
   "KeyMapping.taskCompletion.skip": "跳過",
   "KeyMapping.taskCompletion.tooHard": "Too difficult / Couldn’t see",
   "KeyMapping.taskEditing.cancel": "取消編輯",
+  "KeyMapping.taskEditing.completeTogether": "Complete Tasks Together",
   "KeyMapping.taskEditing.escapeLabel": "ESC",
   "KeyMapping.taskEditing.fitBounds": "Fit Map to Task Features",
   "KeyMapping.taskInspect.nextTask": "下一個任務",
diff --git a/src/services/Editor/Editor.js b/src/services/Editor/Editor.js
index 22305234..8756d23c 100644
--- a/src/services/Editor/Editor.js
+++ b/src/services/Editor/Editor.js
@@ -391,11 +391,15 @@ export const constructLevel0URI = function (
 };
 
 /**
- * Extracts osm identifiers from the given task's (or array of tasks) features
+ * Extracts numerical osm identifiers and osm types from the given task's (or array of tasks) features
  * and returns them as a comma-separated string by default. Features with
  * missing osm ids are skipped, and an empty string is returned if the task has
  * no features or none of its features have osm ids
  *
+ * Osm types will be extracted from osm id properties("@id", "osmid", "osmIdentifier", "id") if defined 
+ * to allow for customization from user 
+ * if there is no osm type defined in osm id properties, it will be generated based on geometry type
+ * 
  * To support varying formats required by different editors, the output string
  * can optionally be customized with options that control whether the entity
  * type is abbreviated or not, a separator character to be used between
@@ -415,11 +419,33 @@ export const osmObjectParams = function (
       objects = objects.concat(
         _compact(
           task.geometries.features.map((feature) => {
-            const osmId = AsIdentifiableFeature(feature).osmId();
+            const currentFeature = AsIdentifiableFeature(feature)
+            const osmId = currentFeature.osmId();
+            const osmType = currentFeature.osmType();
+            let areAllTypesValid;
             if (!osmId) {
               return null;
             }
 
+            if (osmType) {
+              areAllTypesValid = currentFeature.checkValidTypeMultipleIds(osmType);
+            }
+            // We will use osm types defined by user if they exist and are consistent, if not fall back to geometry type 
+            if (osmType && areAllTypesValid) {
+              switch(osmType) {
+                case "node":
+                  return `${
+                    abbreviated ? "n" : "node"
+                  }${entitySeparator}${osmId}`;
+                case "way":
+                  return `${abbreviated ? "w" : "way"}${entitySeparator}${osmId}`;
+                case "relation":
+                  return `${
+                    abbreviated ? "r" : "relation"
+                  }${entitySeparator}${osmId}`;
+              }
+            }
+
             switch (feature.geometry.type) {
               case "Point":
                 return `${
diff --git a/src/services/KeyboardShortcuts/Messages.js b/src/services/KeyboardShortcuts/Messages.js
index b6d3498d..4d32056d 100644
--- a/src/services/KeyboardShortcuts/Messages.js
+++ b/src/services/KeyboardShortcuts/Messages.js
@@ -61,7 +61,7 @@ export default defineMessages({
 
   completeTogether: {
     id: "KeyMapping.taskEditing.completeTogether",
-    defaultMessage: "Complete tasks together"
+    defaultMessage: "Complete Tasks Together"
   },
 
   escapeLabel: {
diff --git a/src/services/User/User.js b/src/services/User/User.js
index f031ab44..49ee8583 100644
--- a/src/services/User/User.js
+++ b/src/services/User/User.js
@@ -754,7 +754,7 @@ export const resetAPIKey = function(userId) {
 /**
  * Add the given challenge to the given user's list of saved challenges.
  */
-export const saveChallenge = function(userId, challengeId) {
+export const saveChallengeForUser = function(userId, challengeId) {
   return updateUser(userId, (dispatch) => {
     // Optimistically assume it will succeed and update the local store.
     // If it doesn't, it'll get updated properly by the server response.
@@ -770,7 +770,7 @@ export const saveChallenge = function(userId, challengeId) {
  * Remove the given challenge from the given user's list of saved
  * challenges.
  */
-export const unsaveChallenge = function(userId, challengeId) {
+export const unsaveChallengeForUser = function(userId, challengeId) {
   return updateUser(userId, (dispatch) => {
     // Optimistically assume it will succeed and update the local store.
     // If it doesn't, it'll get updated by the server response.
diff --git a/src/tailwind.config.js b/src/tailwind.config.js
index 3ba22f52..88b7d53e 100644
--- a/src/tailwind.config.js
+++ b/src/tailwind.config.js
@@ -65,6 +65,7 @@ module.exports = {
       'pink-light-10': 'rgba(255,178,240,0.1)',
       'pink-light-50': 'rgba(255,178,240,0.5)',
       twitter: '#1DA1F2',
+      mastodon: '#595AFF',
       orange: '#f7bb59',
       'orange-jaffa': '#EA8433',
       red: '#CA484C',


Test patch:
diff --git a/src/components/HOCs/WithCurrentUser/WithCurrentUser.test.js b/src/components/HOCs/WithCurrentUser/WithCurrentUser.test.js
index 0580d5c7..3f5d8fb4 100644
--- a/src/components/HOCs/WithCurrentUser/WithCurrentUser.test.js
+++ b/src/components/HOCs/WithCurrentUser/WithCurrentUser.test.js
@@ -3,7 +3,7 @@ import { mapStateToProps, mapDispatchToProps } from './WithCurrentUser'
 import AsEndUser from '../../../interactions/User/AsEndUser'
 import { denormalize } from 'normalizr'
 import { logoutUser,
-         saveChallenge, unsaveChallenge,
+         saveChallengeForUser, unsaveChallengeForUser,
          saveTask, unsaveTask,
          userDenormalizationSchema } from '../../../services/User/User'
 
@@ -90,28 +90,28 @@ test("mapDispatchToProps makes the logoutUser() function available", () => {
   expect(logoutUser).toBeCalled()
 })
 
-test("mapDispatchToProps makes the saveChallenge() function available", () => {
+test("mapDispatchToProps makes the saveChallengeForUser() function available", () => {
   const dispatch = jest.fn()
   const mappedProps = mapDispatchToProps(dispatch)
 
   const userId = 123
   const challengeId = 987
 
-  mappedProps.saveChallenge(userId, challengeId)
+  mappedProps.saveChallengeForUser(userId, challengeId)
   expect(dispatch).toBeCalled()
-  expect(saveChallenge).toBeCalledWith(userId, challengeId)
+  expect(saveChallengeForUser).toBeCalledWith(userId, challengeId)
 })
 
-test("mapDispatchToProps makes the unsaveChallenge() function available", () => {
+test("mapDispatchToProps makes the unsaveChallengeForUser() function available", () => {
   const dispatch = jest.fn()
   const mappedProps = mapDispatchToProps(dispatch)
 
   const userId = 123
   const challengeId = 987
 
-  mappedProps.unsaveChallenge(userId, challengeId)
+  mappedProps.unsaveChallengeForUser(userId, challengeId)
   expect(dispatch).toBeCalled()
-  expect(unsaveChallenge).toBeCalledWith(userId, challengeId)
+  expect(unsaveChallengeForUser).toBeCalledWith(userId, challengeId)
 })
 
 test("mapDispatchToProps makes the saveTask() function available", () => {