MediaWiki:Gadget-moveClaim.js

From wikibase-docker
Revision as of 18:55, 6 April 2022 by WikibaseAdmin (talk | contribs) (Created page with "/** * Enable a quick tool to move (or copy) a claim from one entity to another. * @author Matěj Suchánek (original author) * @author Melderick (property change gadget) *...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Note: After saving, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Go to Menu → Settings (Opera → Preferences on a Mac) and then to Privacy & security → Clear browsing data → Cached images and files.
/**
 * Enable a quick tool to move (or copy) a claim from one entity to another.
 * @author Matěj Suchánek (original author)
 * @author Melderick (property change gadget)
 * @todo MVC, OOUI/Vue
 */
(function (mw, $) {
  var wikibaseHost = "eltedata.elte-dh.hu";

  var itemNamespace = 120;
  var propertyNamespace = 122;

  if (
    [itemNamespace, propertyNamespace].indexOf(
      mw.config.get("wgNamespaceNumber")
    ) === -1 ||
    !mw.config.exists("wbEntityId") ||
    !mw.config.get("wbIsEditView")
  ) {
    return;
  }

  mw.messages.set({
    "another-entity": "To another entity",
    "same-entity": "To the same entity",
    close: "Close",
    "copy-claim": "Copy claim",
    "move-claim": "Move claim",
    newentity: "The id of the new entity:",
    newproperty: "The id of the new property:",
    "force-label": "Allow type mismatch:",
    "move-claim-new": "NEW",
    "move-claim-intro":
      "You can move a claim to another entity. Please give a valid entity id.",
    "move-claim-intro-hint":
      'If you write "$1", you will copy/move the claim to a new item.',
    "move-claim-intro-same-entity":
      "You can move a claim to another property of the current entity.",
    "move-claim-intro-hint-same-entity":
      "Please give a valid property id and of the same type as the current property.",
    "move-claim-copied": "The claim $1 was copied to [[$2|$3]].",
    "move-claim-moved": "The claim $1 was moved to [[$2|$3]].",
    "error-sameid":
      "The new entity's id has to be different to the current one's.",
    "error-sameproperty":
      "The new property's id has to be different to the current one's.",
    "error-differenttype":
      "The current entity $1 and the new entity [[$2|$3]] has to be of the same type. Use the above checkbox to override this control.",
    "error-invalidid": "The given id is not a valid entity identifier.",
    "error-invalidproperty": "The given id is not a valid property identifier.",
    "error-invaliddatatype":
      "The new property is incompatible with the current property datatype.",
    "error-notexisting": "The entity with the id [[$1|$2]] does not exist.",
    "error-api": "There was an error editing the entity: $1",
  });
  switch (mw.config.get("wgUserLanguage")) {
    case "be-tarask":
      mw.messages.set({
        close: "Зачыніць",
        "copy-claim": "Скапіяваць сьцьверджаньне",
        "move-claim": "Перанесьці сьцьверджаньне",
        newentity: "Ідэнтыфікатар новай існасьці:",
        "move-claim-new": "НОВЫ",
        "move-claim-intro":
          "Сьцьверджаньне можна перанесьці ў іншую існасьць. Калі ласка, пазначце правільны ідэнтыфікатар існасьці.",
        "move-claim-intro-hint":
          "Увёўшы «$1», вы скапіюеце/перанесяцё сьцьверджаньне ў новы элемэнт.",
        "move-claim-copied":
          "Сьцьверджаньне $1 пасьпяхова скапіяванае ў [[$2|$3]].",
        "move-claim-moved":
          "Сьцьверджаньне $1 пасьпяхова перанесенае ў [[$2|$3]].",
        "error-sameid":
          "Ідэнтыфікатар новай існасьці мусіць адрозьнівацца ад цяперашняга.",
        "error-invalidid":
          "Уведзены ідэнтыфікатар — няслушны ідэнтыфікатар існасьці.",
        "error-notexisting": "Існасьці з ідэнтыфікатарам $1 не існуе.",
        "error-api": "Пры рэдагаваньні існасьці адбылася памылка: $1",
      });
      break;
    case "el":
      mw.messages.set({
        "another-entity": "Σε μια άλλη οντότητα",
        "same-entity": "Στην ίδια οντότητα",
        close: "Κλείσιμο",
        "copy-claim": "Αντιγραφή ισχυρισμού",
        "move-claim": "Μετακίνηση ισχυρισμού",
        newentity: "Το αναγνωριστικό (id) της νέας οντότητας:",
        newproperty: "Το αναγνωριστικό (id) της νέας οντότητας:",
        "move-claim-new": "ΝΕΟ",
        "move-claim-intro":
          "Μπορείτε να μετακινήσετε ένα ισχυρισμό σε μια άλλη οντότητα. Παρακαλώ δώστε έγκυρο αναγνωριστικό (id) οντότητας.",
        "move-claim-intro-hint":
          'Αν γράψετε "$1", θα αντιγράψει/μετακινήσει τον ισχυρισμό σε ένα νέο αντικείμενο.',
        "move-claim-intro-same-entity":
          "Μπορείτε να μετακινήσετε ένα ισχυρισμό σε μια άλλη ιδιότητα της παρούσας οντότητας.",
        "move-claim-intro-hint-same-entity":
          "Παρακαλώ δώστε ένα έγκυρο αναγνωριστικό (id) οντότητας και του ίδιου τύπου της παρούσας ιδιότητας.",
        "move-claim-copied":
          "Ο ισχυρισμός $1 αντιγράφτηκε στο αντικείμενο [[$2|$3]].",
        "move-claim-moved":
          "Ο ισχυρισμός $1 μετακινήθηκε στο αντικείμενο [[$2|$3]].",
        "error-sameid":
          "Το/τα αναγνωριστικό/ά (id) της νέας οντότητα/των νέων οντοτήτων πρέπει να είναι διαφορετικό/ά από το/τα τρέχον/τρέχοντα.",
        "error-sameproperty":
          "Η νέα ιδιότητα/οι νέες ιδιότητες πρέπει να είναι διαφορετική/διαφορετικές από την παρούσα/τις παρούσες.",
        "error-invalidid":
          "Το δοθέν αναγνωριστικό (id) δεν είναι ένα έγκυρο αναγνωριστικό οντότητας.",
        "error-invalidproperty":
          "Το δοθέν αναγνωριστικό (id) δεν είναι ένα έγκυρο αναγνωριστικό ιδιότητας.",
        "error-invaliddatatype":
          "Η νέα ιδιότητα είναι ασύμβατη με το είδος των δεδομένων της παρούσας ιδιότητας.",
        "error-notexisting":
          "Η οντότητα με αναγνωριστικό (id) [[$1|$2]] δεν υπάρχει.",
        "error-api":
          "Παρουσιάστηκε ένα σφάλμα στην επεξεργασία της οντότητας: $1",
      });
      break;
    case "fr":
      mw.messages.set({
        "another-entity": "Vers un autre élément",
        "same-entity": "Vers le même élément",
        close: "Fermer",
        "copy-claim": "Copier l’affirmation",
        "move-claim": "Déplacer l’affirmation",
        newentity: "Identifiant du nouvel élément :",
        newproperty: "Identifiant de la nouvelle propriété :",
        "force-label": "Autoriser un différent type d’élément:",
        "move-claim-new": "NOUVEAU",
        "move-claim-intro":
          "Vous pouvez déplacer cette affirmation vers un autre élément. Veuillez donner un identifiant d’élément valide.",
        "move-claim-intro-hint":
          'Si vous rentrez "$1", vous copierez/déplacerez l’affirmation vers un nouvel élément.',
        "move-claim-intro-same-entity":
          "Vous pouvez déplacer cette affirmation vers une autre propriété de l’élément courant.",
        "move-claim-intro-hint-same-entity":
          "Veuillez donner un identifiant de propriété valide et de même type que la propriété courante.",
        "move-claim-copied":
          "L’affirmation $1 a été copiée vers [[$2|$3]] avec succès.",
        "move-claim-moved":
          "L’affirmation $1 a été déplacée vers [[$2|$3]] avec succès.",
        "error-sameid":
          "L’identifiant du nouvel élément doit être différent de celui de l’élément actuel.",
        "error-sameproperty":
          "L’identifiant de la nouvelle propriété doit être différent de celui de la propriété actuelle.",
        "error-differenttype":
          "L’élément courant $1 et le nouvel élément [[$2|$3]] doivent être du même type. Cocher la case ci-dessous pour passer outre ce contrôle.",
        "error-invalidid":
          "L’identifiant donné n’est pas un identifiant d’élément valide.",
        "error-invalidproperty":
          "L’identifiant donné n’est pas un identifiant de propriété valide.",
        "error-invaliddatatype":
          "La nouvelle propriété est incompatible avec le datatype de la propriété actuelle.",
        "error-notexisting":
          "L’élément avec l’identifiant [[$1|$2]] n’existe pas.",
        "error-api":
          "Une erreur est survenue lors de l’édition de l’élément : $1",
      });
      break;
    case "it":
      mw.messages.set({
        close: "Chiudi",
        "copy-claim": "Copia dichiarazione",
        "move-claim": "Sposta dichiarazione",
        newentity: "L'ID della nuova entità:",
        "move-claim-new": "NUOVO",
        "move-claim-intro":
          "Puoi spostare una dichiarazione in un'altra entità. Inserisci un valido ID dell'entità.",
        "move-claim-intro-hint":
          'Se scrivi "$1", copierai/sposterai la dichiarazione in un nuovo elemento.',
        "move-claim-copied":
          "La dichiarazione $1 è stata copiata con successo in [[$2|$3]].",
        "move-claim-moved":
          "La dichiarazione $1 è stata spostata con successo in [[$2|$3]].",
        "error-sameid":
          "L'ID della nuova entità deve essere differente da quello attuale.",
        "error-invalidid": "L'ID inserito non è quello di una valida entità.",
        "error-notexisting": "L'entità con ID $1 non esiste.",
        "error-api": "C'è stato un errore nella modifica dell'entità: $1",
      });
      break;
    case "sv":
      mw.messages.set({
        "another-entity": "Till en annan entitet",
        "same-entity": "Till samma entitet",
        close: "Stäng",
        "copy-claim": "Kopiera påstående",
        "move-claim": "Flytta påstående",
        newentity: "Den nya entitetens ID:",
        newproperty: "Den nya egenskapens ID:",
        "force-label": "Tillåt typmatchningsfel:",
        "move-claim-new": "NY",
        "move-claim-intro":
          "Du kan flytta ett påstående till en annan entitet. Ange ett giltigt entitets-ID.",
        "move-claim-intro-hint":
          'Om du skriver "$1" kommer du flytta/kopiera påståendet till ett nytt objekt.',
        "move-claim-intro-same-entity":
          "Du kan flytta ett påstående till en annan egenskap hos den nuvarande entiteten.",
        "move-claim-intro-hint-same-entity":
          "Ange ett giltigt egenskaps-ID som är av samma typ som den nuvarande egenskapen.",
        "move-claim-copied": "Påståendet $1 kopierades till [[$2|$3]].",
        "move-claim-moved": "Påståendet $1 flyttades till [[$2|$3]].",
        "error-sameid":
          "Den nya entitetens ID måste skilja sig från den aktuella.",
        "error-sameproperty":
          "Den nya egenskapens ID måste skilja sig från den aktuella.",
        "error-differenttype":
          "Den aktuella entiteten $1 och den nya entiteten [[$2|$3]] måste vara av samma typ. Använd kryssrutan ovan för att åsidosätta denna kontroll.",
        "error-invalidid":
          "Det angivna ID:t är inte en giltig entitetsidentifierare.",
        "error-invalidproperty":
          "Det angivna ID:t är inte en giltig egenskapsidentifierare.",
        "error-invaliddatatype":
          "Den nya egenskapen är inte kompatibel med den aktuella egenskapens datatyp.",
        "error-notexisting": "Det finns ingen entitet med ID:t [[$1|$2]].",
        "error-api": "Det uppstod ett fel när entiteten redigerades: $1",
      });
      break;
  }

  var canEditThis = mw.config.get("wgIsProbablyEditable"),
    TO_ANOTHER_ENTITY = 0,
    TO_THE_SAME_ENTITY = 1,
    COPY_CLAIM = false,
    MOVE_CLAIM = true;

  var api,
    repoApi,
    claimid,
    oldentity,
    oldproperty,
    oldtitle,
    olddata = null,
    newentity,
    newproperty,
    newtitle,
    newdata,
    newdatatype,
    lastMode = canEditThis ? MOVE_CLAIM : COPY_CLAIM,
    lastTab = TO_ANOTHER_ENTITY,
    credit = "using [[MediaWiki:Gadget-moveClaim.js|moveClaim.js]]";

  function createSpinner() {
    $("#move-claim-result").html(
      $.createSpinner({
        size: "large",
        type: "block",
      })
    );
  }

  function showError(error) {
    var parameters = Array.prototype.slice.call(arguments, 1);
    $("#move-claim-result").html(
      $("<p>")
        .attr("class", "error")
        .html(
          mw
            .message("error-" + error)
            .params(parameters)
            .parse()
        )
    );
  }

  function onError(error, result) {
    showError("api", (result && result.error && result.error.info) || error);
  }

  function getFragmentedTitle(title, entity) {
    if (entity.indexOf("-") !== -1) {
      title = title.replace(/:.+$/, ":" + entity.replace("-", "#"));
    }
    return title;
  }

  function successProperty(removed) {
    var $statement = $(".wikibase-statement-" + $.escapeSelector(claimid)),
      message = mw.message(
        removed ? "move-claim-moved" : "move-claim-copied",
        oldproperty,
        "Property:" + newproperty,
        newproperty
      );

    $("#move-claim").dialog("close");

    // Todo: Hide moved claim
    if (removed) {
      $statement.find(".move-button-container").remove();
    }

    mw.notify(message, {
      autoHide: false,
      title: removed ? mw.msg("move-claim") : mw.msg("copy-claim"),
    });
  }

  function success(removed) {
    var $statement = $(".wikibase-statement-" + $.escapeSelector(claimid)),
      message = mw.message(
        removed ? "move-claim-moved" : "move-claim-copied",
        oldproperty,
        newtitle,
        newentity
      );

    $("#move-claim").dialog("close");
    $("#move-newentity").val(newentity); // update for new items

    // Todo: Hide moved claim
    if (removed) {
      $statement.find(".move-button-container").remove();
    }

    mw.notify(message, {
      autoHide: false,
      title: removed ? mw.msg("move-claim") : mw.msg("copy-claim"),
    });
  }

  function loadEntity() {
    if (olddata !== null) {
      return $.Deferred().resolve().promise();
    }
    return repoApi
      .getEntities(oldentity, ["info", "claims"])
      .then(function (data) {
        olddata = data.entities[oldentity];
        oldtitle = getFragmentedTitle(olddata.title, oldentity);
      });
  }

  function clone(data) {
    return JSON.parse(JSON.stringify(data));
  }

  function getQualifiersHash(claim) {
    var hashes = [];
    Object.values(claim.qualifiers || {}).forEach(function (qualifiers) {
      Array.prototype.push.apply(
        hashes,
        qualifiers.map(function (q) {
          return q.hash;
        })
      );
    });
    return hashes.sort().join("|");
  }

  function getHash(claim) {
    return claim.mainsnak.hash + "$" + getQualifiersHash(claim);
  }

  function getMainsnakForCmp(claim) {
    var mainsnak = clone(claim.mainsnak);
    delete mainsnak.hash;
    delete mainsnak.property;
    return JSON.stringify(mainsnak);
  }

  function mergePropertyClaim(claim, removing) {
    var data = {},
      mainsnak = getMainsnakForCmp(claim),
      hash = getQualifiersHash(claim),
      same = false;

    $.each(olddata.claims[newproperty] || [], function (i, _claim) {
      if (
        mainsnak === getMainsnakForCmp(_claim) &&
        hash === getQualifiersHash(_claim)
      ) {
        same = _claim;
        return false;
      }
    });

    if (same !== false) {
      if (!claim.references) {
        return $.Deferred().resolve().promise();
      }
      data = clone(same);
      var hashes = (same.references || []).map(function (ref) {
        return ref.hash;
      });
      claim.references.forEach(function (ref) {
        if (hashes.indexOf(ref.hash) === -1) {
          if (data.references === undefined) {
            data.references = [];
          }
          data.references.push(ref);
        }
      });
    } else {
      data = clone(claim);
      data.mainsnak.property = newproperty;
      delete data.mainsnak.hash;
      delete data.id;
    }

    var post = { claims: [data] };
    if (removing) {
      post.claims.push({ id: claimid, remove: 1 });
    }

    return api.postWithEditToken({
      formatversion: 2,
      action: "wbeditentity",
      id: oldentity,
      data: JSON.stringify(post),
      baserevid: olddata.lastrevid,
      summary: removing
        ? "Moving [[Property:" +
          oldproperty +
          "]] to [[Property:" +
          newproperty +
          "]], " +
          credit
        : "Copying [[Property:" +
          oldproperty +
          "]] to [[Property:" +
          newproperty +
          "]], " +
          credit,
    });
  }

  function mergeClaim(claim, removing) {
    var property = claim.mainsnak.property;
    var data = {},
      hash = getHash(claim),
      same = false;
    $.each(newdata.claims[property] || [], function (_, _claim) {
      if (hash === getHash(_claim)) {
        same = _claim;
        return false;
      }
    });

    if (same !== false) {
      if (!claim.references) {
        return $.Deferred().resolve().promise();
      }
      data = clone(same);
      var hashes = (same.references || []).map(function (ref) {
        return ref.hash;
      });
      claim.references.forEach(function (ref) {
        if (hashes.indexOf(ref.hash) === -1) {
          if (data.references === undefined) {
            data.references = [];
          }
          data.references.push(ref);
        }
      });
    } else {
      data = clone(claim);
      delete data.id;
    }

    return api.postWithEditToken({
      formatversion: 2,
      action: "wbeditentity",
      id: newentity,
      data: JSON.stringify({ claims: [data] }),
      baserevid: newdata.lastrevid,
      summary: removing
        ? "Moving [[Property:" +
          property +
          "]] from [[" +
          oldtitle +
          "]], " +
          credit
        : "Copying [[Property:" +
          property +
          "]] from [[" +
          oldtitle +
          "]], " +
          credit,
    });
  }

  function getAllClaims() {
    var claims = [];
    Object.values(olddata.claims || {}).forEach(function (p_claims) {
      Array.prototype.push.apply(claims, p_claims);
    });
    return claims;
  }

  function moveProperty(remove) {
    var claim = false;
    $.each(getAllClaims(), function (_, _claim) {
      if (_claim.id === claimid) {
        claim = _claim;
        return false;
      }
    });

    if (!claim) {
      return false;
    }

    if (newdatatype !== claim.mainsnak.datatype) {
      showError("invaliddatatype");
      return false;
    }

    return mergePropertyClaim(claim, remove);
  }

  function move(remove) {
    var claim = false;
    $.each(getAllClaims(), function (_, _claim) {
      if (_claim.id === claimid) {
        claim = _claim;
        return false;
      }
    });
    if (!claim) return false;

    return mergeClaim(claim, remove).then(function () {
      if (!remove) {
        return;
      }
      return api.postWithEditToken({
        action: "wbremoveclaims",
        baserevid: olddata.lastrevid,
        claim: claimid,
        formatversion: 2,
        summary: "Moving claim to [[" + newtitle + "]], " + credit,
      });
    });
  }

  function performPropertyChange(remove) {
    createSpinner();
    newproperty = $("#move-newproperty").val().toUpperCase();

    if (oldproperty === newproperty) {
      showError("sameproperty");
      return;
    }
    if (newproperty.match(/^P[1-9]\d*$/) === null) {
      showError("invalidproperty");
      return;
    }

    repoApi
      .getEntities([oldentity, newproperty], ["info", "claims"])
      .then(function (data) {
        var entity = data.entities[newproperty];
        if (entity.hasOwnProperty("missing")) {
          showError("notexisting", "Property:" + newproperty, newproperty);
          return false;
        }

        olddata = data.entities[oldentity];
        newdatatype = entity.datatype;

        return moveProperty(remove);
      })
      .then(function (ok) {
        if (ok === false) {
          return;
        }
        successProperty(remove);
      }, onError);
  }

  function performMove(remove) {
    lastMode = remove;
    lastTab = $("#move-claim").tabs("option", "active");
    if (lastTab === TO_THE_SAME_ENTITY) {
      performPropertyChange(remove);
      return;
    }
    createSpinner();
    newentity = $("#move-newentity").val().toUpperCase();
    if (oldentity === newentity) {
      showError("sameid");
      return;
    }

    var promise;
    if (["NEW", mw.msg("move-claim-new")].indexOf(newentity) !== -1) {
      promise = $.when(
        repoApi
          .createEntity("item") // todo: lexemes (upstream)
          .then(function (data) {
            newdata = data.entity;
            newentity = newdata.id;
            newtitle = newentity; // todo: getFragmentedTitle( newdata.title, newentity );

            return true;
          }),
        loadEntity()
      );
    } else {
      if (
        newentity.match(/^([PQ][1-9]\d*|L[1-9]\d*(-[FS][1-9]\d*)?)$/) === null
      ) {
        if (oldentity.charAt(0) === "L" && newentity.match(/^[FS][1-9]\d*$/)) {
          newentity = oldentity + "-" + newentity;
        } else {
          showError("invalidid");
          return;
        }
      }
      if (
        $("#force-checkbox").is(":checked") === false &&
        newentity.charAt(0) === "P" &&
        oldentity.charAt(0) !== "P"
      ) {
        $("#force-group").show();
        showError(
          "differenttype",
          oldentity,
          "Property:" + newentity,
          newentity
        );
        return;
      }
      promise = repoApi
        .getEntities([oldentity, newentity], ["info", "claims"])
        .then(function (data) {
          var entity = data.entities[newentity];
          if (entity.hasOwnProperty("missing")) {
            showError("notexisting", newentity, newentity);
            return false;
          }

          if (entity.hasOwnProperty("redirects")) {
            newentity = entity.redirects.to;
          }

          olddata = data.entities[oldentity];
          newdata = entity;

          oldtitle = getFragmentedTitle(olddata.title, oldentity);
          newtitle = getFragmentedTitle(newdata.title, newentity);

          return true;
        });
    }

    promise.then(function (ok) {
      if (ok === false) return;
      ok = move(remove);
      if (ok === false) return;
      return ok.then(function () {
        success(remove);
      });
    }, onError);
  }

  function openDialog() {
    $("#move-claim-result").empty();
    $("#force-group").hide();
    $("#force-checkbox").prop("checked", false);
    $("#move-claim").dialog("open");
    $("#move-claim").tabs("option", "active", lastTab);
    if (
      (lastTab === TO_ANOTHER_ENTITY && $("#move-newentity").val() !== "") ||
      (lastTab === TO_THE_SAME_ENTITY && $("#move-newproperty").val() !== "")
    ) {
      if (lastMode === MOVE_CLAIM) {
        $("#move-claim-button-move").focus();
      } else {
        $("#move-claim-button-copy").focus();
      }
    }
  }

  function addButton($statement) {
    $statement.prepend(
      $("<div>")
        .attr("class", "move-button-container")
        .css({
          float: "right",
          position: "relative",
          "z-index": 1,
        })
        .append(
          $("<a>")
            .attr({
              href: "#",
              class: "move-button",
            })
            .on("click", function (event) {
              event.preventDefault();
              claimid = $statement.attr("id");
              oldentity = claimid.split("$")[0].toUpperCase();
              oldproperty = $statement
                .closest(".wikibase-statementgroupview")
                .attr("id");
              openDialog();
            })
        )
    );
  }

  function removeIf(buttons, idx, remove) {
    if (remove) {
      buttons.shift(idx);
    }
    return buttons;
  }

  function init() {
    // Add click listener
    $(".wikibase-statementview").each(function () {
      addButton($(this));
    });

    // Create dialog
    $("<div>")
      .attr("id", "move-claim")
      .append(
        $("<ul>").append(
          $("<li>").append(
            $("<a>")
              .attr("href", canEditThis ? "#another-entity" : "#")
              .append($("<span>").text(mw.msg("another-entity")))
          ),
          $("<li>").append(
            $("<a>")
              .attr("href", "#same-entity")
              .append($("<span>").text(mw.msg("same-entity")))
          )
        ),
        $("<div>")
          .attr("id", "another-entity")
          .append(
            $("<form>")
              .submit(function (event) {
                event.preventDefault();
                performMove(lastMode);
              })
              .append(
                $("<fieldset>").append(
                  $("<legend>").text(mw.msg("move-claim")),
                  // </legend>
                  $("<p>")
                    .attr("id", "claim-intro")
                    .text(mw.msg("move-claim-intro")),
                  // </p>
                  $("<p>")
                    .attr("id", "claim-intro-hint")
                    .text(
                      mw
                        .msg("move-claim-intro-hint")
                        .replace("$1", mw.msg("move-claim-new"))
                    ),
                  // </p>
                  $("<p>").append(
                    $("<label>")
                      .attr({
                        for: "move-newentity",
                        class: "move-label",
                      })
                      .text(mw.msg("newentity")),
                    // </label>
                    " ",
                    $("<input>")
                      .attr({
                        type: "text",
                        id: "move-newentity",
                        class: "move-input",
                      })
                      .on("keyup", function () {
                        this.value = this.value.replace(
                          /.*([PQ][1-9]\d*).*/,
                          "$1"
                        ); // TODO: lexemes etc.
                      })
                    // </label>
                  ),
                  // </p>
                  $("<p>")
                    .attr({
                      id: "force-group",
                    })
                    .append(
                      $("<label>")
                        .attr({
                          for: "move-newentity",
                          class: "move-label",
                        })
                        .text(mw.msg("force-label")),
                      " ",
                      // </label>
                      $("<input>").attr({
                        type: "checkbox",
                        id: "force-checkbox",
                      })
                    )
                ) // </p>
              ) // </fieldset>
            // </form>
          ),
        $("<div>")
          .attr("id", "same-entity")
          .append(
            $("<form>")
              .submit(function (event) {
                event.preventDefault();
                performMove(lastMode);
              })
              .append(
                $("<fieldset>").append(
                  $("<legend>").text(mw.msg("move-claim")),
                  // </legend>
                  $("<p>")
                    .attr("id", "claim-intro-same-entity")
                    .text(mw.msg("move-claim-intro-same-entity")),
                  // </p>
                  $("<p>")
                    .attr("id", "claim-intro-hint-same-entity")
                    .text(mw.msg("move-claim-intro-hint-same-entity")),
                  // </p>
                  $("<p>").append(
                    $("<label>")
                      .attr({
                        for: "move-newproperty",
                        class: "move-label",
                      })
                      .text(mw.msg("newproperty")),
                    " ",
                    // </label>
                    $("<input>")
                      .attr({
                        type: "text",
                        id: "move-newproperty",
                        class: "move-input",
                      })
                      .on("keyup", function () {
                        this.value = this.value.replace(
                          /.*(P[1-9]\d*).*/,
                          "$1"
                        );
                      })
                  )
                ) // </p>
              ) // </fieldset>
            // </form>
          ),
        $("<p>").attr("id", "move-claim-result")
      )
      .dialog({
        dialogClass: "move-dialog",
        title: mw.message("move-claim").escaped(),
        autoOpen: false,
        modal: true,
        width: 500,
        buttons: removeIf(
          [
            {
              id: "move-claim-button-move",
              text: mw.msg("move-claim"),
              click: function (event) {
                event.preventDefault();
                performMove(MOVE_CLAIM);
              },
            },
            {
              id: "move-claim-button-copy",
              text: mw.msg("copy-claim"),
              click: function (event) {
                event.preventDefault();
                performMove(COPY_CLAIM);
              },
            },
            {
              id: "move-claim-button-close",
              text: mw.msg("close"),
              click: function (event) {
                event.preventDefault();
                $("#move-claim").dialog("close");
              },
            },
          ],
          0,
          !canEditThis
        ),
      });
    $("#move-claim").tabs();
  }

  if (mw.loader.getState("ext.gadget.Move") !== "ready") {
    // don't load CSS twice
    mw.loader.load(
      "//" +
        wikibaseHost +
        "/w/index.php?title=MediaWiki:Gadget-Move.css&action=raw&ctype=text/css",
      "text/css"
    );
  }

  $.when(
    mw.loader
      .using([
        "jquery.spinner",
        "jquery.ui",
        "mediawiki.api",
        "mediawiki.util",
        "wikibase.api.RepoApi",
        "wikibase.Site",
      ])
      .then(function () {
        api = new mw.Api();
        repoApi = new wikibase.api.RepoApi(api);
      }),
    $.ready
  ).then(init);

  mw.hook("wikibase.statement.saved").add(function (_, guid) {
    olddata = null;
    //var $block = $( '#' + $.escapeSelector( guid ) );
    var $block = $(".wikibase-statement-" + $.escapeSelector(guid));
    if ($block.find(".move-button-container").length === 0) {
      addButton($block);
    }
  });
})(mediaWiki, jQuery);