User:Techwizzie/newInterface.js

From Wikidata
Jump to navigation Jump to search

Note: After publishing, 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)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
// Function to initialize the edit properties functionality
const instanceOfItemID = "P31";
const propertiesForTypeID = "P1963";
var statementsMap = {};
var guidToStatementMap = {};
var claimGUIDsToRemove = [];
var claimsToSet = [];
var propertiesForClasses = {};

function initializeEditProperties() {
  var menuList = $("#right-navigation").find(".vector-menu-content-list");
  var editEntityDiv = $("<li>").attr({
    id: "ca-edit-entity",
    class: "vector-tab-noicon mw-list-item",
  });

  var editFieldSpan = $("<span>").text("Edit Properties");
  var anchor = $("<a>").append(editFieldSpan);
  anchor.on("click", function (e) {
    menuList.find("li").each(function () {
      $(this).removeClass("selected");
    });
    $("#ca-edit-entity").addClass("selected");
    handleEditPropertiesClick();
  });

  if (mw.config.get("wgPageContentModel") === "wikibase-item") {
    editEntityDiv.append(anchor);
    menuList.append(editEntityDiv);
  }
}

// Function to generate guid
function generateGUID(pageId) {
  var chars = "0123456789abcdefghijklmnopqrstuvwxyz";
  var guid = pageId + "$";
  for (var i = 0; i < 36; i++) {
    if (i === 8 || i === 13 || i === 18 || i === 23) {
      guid += "-";
    } else if (i === 14) {
      guid += "4";
    } else if (i === 19) {
      guid += chars.charAt(Math.floor(Math.random() * chars.length));
    } else {
      guid += chars.charAt(Math.floor(Math.random() * chars.length));
    }
  }
  return guid;
}

function handleEditPropertiesClick() {
  $("#mw-content-text").empty();
  var pageName = mw.config.get("wgPageName");
  if (mw.config.get("wgPageContentModel") === "wikibase-item") {
    var itemID = pageName;
    retrieveEntityProperties(itemID);
  }
}

function retrieveEntityProperties(itemID) {
  var api = new mw.Api();
  var requestParams = {
    action: "wbgetentities",
    format: "json",
    ids: itemID,
  };

  var result = api.get(requestParams);
  result.done(function (res) {
    var entity = res.entities[itemID];
    var instancesOf = getInstanceOfProperties(entity);
    retrieveTypeProperties(api, requestParams, instancesOf, entity);
  });
}

// Function to retrieve instance of properties
function getInstanceOfProperties(entity) {
  var instancesOf = [];
  if (entity.claims[instanceOfItemID]) {
    entity.claims[instanceOfItemID].forEach(function (val) {
      instancesOf.push(val.mainsnak.datavalue.value.id);
    });
  }
  return instancesOf;
}

// Function to retrieve type properties from Wikidata API
function retrieveTypeProperties(api, requestParams, instancesOf, entity) {
  var typesResponse = api.get(
    $.extend({}, requestParams, { ids: instancesOf })
  );
  typesResponse.done(function (res) {
    var entities = res.entities;
    var propertiesForType = {};
    var typePropertyMap = {};

    instancesOf.forEach(function (instance) {
      var et = entities[instance];
      propertiesForClasses[instance] = [];
      if (et.claims[propertiesForTypeID]) {
        et.claims[propertiesForTypeID].forEach(function (val) {
          if (!propertiesForType[instance]) {
            propertiesForType[instance] = [];
          }
          propertiesForType[instance].push(val.mainsnak.datavalue.value.id);
          propertiesForClasses[instance].push(val.mainsnak.datavalue.value.id);
          typePropertyMap[val.mainsnak.datavalue.value.id] = instance;
        });
      }
    });
    var propertyIDs = Object.keys(entity.claims);
    propertyIDs.splice(propertyIDs.indexOf(instanceOfItemID), 1);
    var valuesMap = getValuesMap(entity, propertyIDs);
    retrieveLabels(
      api,
      requestParams,
      propertyIDs,
      instancesOf,
      valuesMap,
      typePropertyMap,
      propertiesForType
    );
  });
}

// Function to retrieve values map
function getValuesMap(entity, propertyIDs) {
  var valuesMap = {};
  propertyIDs.forEach(function (propertyID) {
    valuesMap[propertyID] = [];
    if (entity.claims[propertyID]) {
      statementsMap[propertyID] = entity.claims[propertyID];
      statementsMap[propertyID].map(function (statement) {
        guidToStatementMap[statement.id] = statement;
      });
      entity.claims[propertyID].forEach(function (statement) {
        if (statement.mainsnak.datavalue) {
          valuesMap[propertyID].push(statement.mainsnak.datavalue.value);
        }
      });
    }
  });
  return valuesMap;
}

function newTextInput(value, datatype, statementID, guid) {
  const pageId = mw.config.get("wbEntityId");
  var textInput = new OO.ui.TextInputWidget({});
  textInput.setValue(value);
  const newGUID = guid ? guid : generateGUID(pageId);
  textInput.$element.attr("data-guid", newGUID);
  var field = new OO.ui.FieldLayout(textInput, {});
  var deleteIcon = new OO.ui.IconWidget({
    icon: "trash",
    flags: "destructive",
  });
  var horizontalLayout = new OO.ui.HorizontalLayout({
    items: [field, deleteIcon],
  });
  deleteIcon.$element.on("click", function (e) {
    if (statementID) claimGUIDsToRemove.push(statementID);
    horizontalLayout.$element.remove();
  });
  textInput.on("change", function (newValue) {
    var modifiedStatement = {};
    if (guidToStatementMap[newGUID]) {
      modifiedStatement = guidToStatementMap[statementID];
    } else {
      modifiedStatement = {};
    }
    if (datatype === "quantity") {
      modifiedStatement.mainsnak.datavalue.value.amount = newValue;
    } else if (
      datatype === "string" ||
      datatype === "commonsMedia" ||
      datatype === "globecoordinate"
    ) {
      modifiedStatement.mainsnak.datavalue.value = newValue;
    } else if (datatype === "time") {
      modifiedStatement.mainsnak.datavalue.value.time = newValue;
    }
    console.log("Modified statement => ", modifiedStatement);
  });
  return horizontalLayout;
}

function newComboboxInput(value, statementID, guid) {
  const pageId = mw.config.get("wbEntityId");
  const lang = mw.config.get("wgContentLanguage");
  var api = new mw.Api();
  var allValues = [value];
  var comboboxInput = new OO.ui.ComboBoxInputWidget({
    autocomplete: true,
    menu: {
      filterFromInput: true,
    },
  });
  const newGUID = guid ? guid : generateGUID(pageId);
  comboboxInput.$element.attr("data-guid", newGUID);
  comboboxInput.setOptions(
    allValues.map(function (item) {
      return new OO.ui.MenuOptionWidget({
        data: item,
        label: item,
      });
    })
  );
  comboboxInput.setValue(value);
  comboboxInput.on("change", function (inputText) {
    var requestParams = {
      action: "wbsearchentities",
      format: "json",
      ids: [],
      search: inputText,
      language: lang,
      type: "item",
      limit: 20,
    };
    var values = api.get($.extend({}, requestParams, {}));
    values.done(function (items) {
      comboboxInput.setOptions(
        items.search.map(function (item) {
          if (item.display.label) {
            return new OO.ui.MenuOptionWidget({
              data: item.display.label.value + " (" + item.description + ")",
              label: item.display.label.value + " (" + item.description + ")",
            });
          }
        })
      );
    });
    var modifiedStatement = {};
    if (guidToStatementMap[newGUID]) {
      modifiedStatement = guidToStatementMap[statementID];
    } else {
      modifiedStatement = {};
    }
    modifiedStatement.mainsnak.datavalue.value = inputText;
    console.log("Modified statement => ", modifiedStatement);
  });
  var field = new OO.ui.FieldLayout(comboboxInput, {
    align: "left",
  });
  var deleteIcon = new OO.ui.IconWidget({
    icon: "trash",
    flags: "destructive",
  });
  var horizontalLayout = new OO.ui.HorizontalLayout({
    items: [field, deleteIcon],
  });
  deleteIcon.$element.on("click", function (e) {
    if (statementID) claimGUIDsToRemove.push(statementID);
    horizontalLayout.$element.remove();
  });
  return horizontalLayout;
}

// Function to retrieve labels from Wikidata API
function retrieveLabels(
  api,
  requestParams,
  propertyIDs,
  instancesOf,
  valuesMap,
  typePropertyMap,
  propertiesForType,
  shouldReturn = false
) {
  var lang = mw.config.get("wgPageContentLanguage");
  var batchSize = 50; // Maximum number of IDs per request
  var chunks = [];
  var i, j;

  // Split propertyIDs and instancesOf into chunks of batchSize
  for (i = 0, j = propertyIDs.length; i < j; i += batchSize) {
    chunks.push(propertyIDs.slice(i, i + batchSize));
  }
  for (i = 0, j = instancesOf.length; i < j; i += batchSize) {
    chunks.push(instancesOf.slice(i, i + batchSize));
  }

  // Create promises for each chunk and make asynchronous requests
  var promises = chunks.map(function (chunk) {
    var labelsResponse = api.get(
      $.extend({}, requestParams, { props: "labels", ids: chunk })
    );
    return labelsResponse.then(function (res) {
      var props = res.entities;
      var properties = [];
      var classLabels = {};
      Object.keys(props).forEach(function (idx) {
        var prop = props[idx];
        if (instancesOf.indexOf(prop.id) === -1) {
          properties.push({
            id: prop.id,
            datatype: prop.datatype,
            label: prop.labels[lang] ? prop.labels[lang].value : "",
          });
        } else {
          classLabels[prop.id] = prop.labels[lang]
            ? prop.labels[lang].value
            : "";
        }
      });

      return { properties: properties, classLabels: classLabels };
    });
  });

  // Wait for all promises to resolve and process the results
  return Promise.all(promises)
    .then(function (results) {
      var allProperties = [];
      var allClassLabels = {};

      // Merge the results
      results.forEach(function (result) {
        allProperties = allProperties.concat(result.properties);
        Object.assign(allClassLabels, result.classLabels);
      });

      if (shouldReturn) {
        return allClassLabels;
      } else {
        createPropertyDivs(
          api,
          allProperties,
          allClassLabels,
          valuesMap,
          typePropertyMap,
          instancesOf,
          propertiesForType
        );
      }
    })
    .catch(function (error) {
      console.error("Error fetching labels:", error);
    });
}

// Function to create property divs
function createPropertyDivs(
  api,
  properties,
  classLabels,
  valuesMap,
  typePropertyMap,
  instancesOf,
  propertiesForType
) {
  var propsByTypeStyle =
    "border-style:solid; width: max-content; border-width:0.5px; padding: 0 1rem 1rem 1rem; margin-top: 1rem;background: aliceblue;";
  var lang = mw.config.get("wgPageContentLanguage");
  var propertyDivs = {};
  var requestParams = {
    action: "wbgetentities",
    format: "json",
  };
  Object.keys(propertiesForType).forEach(function (id) {
    propertyDivs[id] = $("<div></div>").attr("style", propsByTypeStyle);
  });

  // Group properties into batches of 50 or fewer
  var propertyBatches = chunkArray(properties, 50);
  // Create promises for each batch and make asynchronous requests
  var promises = propertyBatches.map(function (propertyBatch) {
    var propertyBatchIDs = [];
    propertyBatch.map(function (prop) {
      propertyBatchIDs.push(prop.id);
    });
    // Fetch labels for the current batch of properties
    var labelsResponse = api.get(
      $.extend({}, requestParams, { props: "labels", ids: propertyBatchIDs })
    );
    return labelsResponse.then(function (res) {
      var props = res.entities;
      var properties = [];
      var classLabelsBatch = {};
      // Process each property in the batch
      propertyBatchIDs.forEach(function (propID) {
        var prop = props[propID];
        if (prop) {
          if (instancesOf.indexOf(prop.id) === -1) {
            properties.push({
              id: prop.id,
              datatype: prop.datatype,
              label: prop.labels[lang] ? prop.labels[lang].value : "",
            });
          } else {
            classLabelsBatch[prop.id] = prop.labels[lang]
              ? prop.labels[lang].value
              : "";
          }
        }
      });

      return { properties: properties, classLabelsBatch: classLabelsBatch };
    });
  });

  // Wait for all promises to resolve and process the results
  Promise.all(promises)
    .then(function (results) {
      var allProperties = [];

      // Merge the results
      results.forEach(function (result) {
        allProperties = allProperties.concat(result.properties);
      });

      createPropertyDivElements(
        api,
        allProperties,
        classLabels,
        valuesMap,
        typePropertyMap,
        propertyDivs,
        propertiesForType
      );
    })
    .catch(function (error) {
      console.error("Error fetching labels:", error);
    });
}

// Helper function to chunk an array into smaller arrays
function chunkArray(array, size) {
  return Array.from(
    { length: Math.ceil(array.length / size) },
    function (_, index) {
      return array.slice(index * size, index * size + size);
    }
  );
}

// Function to create property div elements
function createPropertyDivElements(
  api,
  properties,
  classLabels,
  valuesMap,
  typePropertyMap,
  propertyDivs,
  propertiesForType
) {
  const lang = mw.config.get("wgContentLanguage");
  var propsByTypeStyle =
    "border-style:solid; width: max-content; border-width:0.5px; padding: 0 1rem 1rem 1rem; margin-top: 1rem;background: aliceblue;";
  var otherFieldsDiv = $("<div></div>").attr("style", propsByTypeStyle);
  const generalProperties = properties.filter(function (item) {
    return item.datatype !== "external-id";
  });
  generalProperties.forEach(function (prop) {
    var fieldset = new OO.ui.FieldsetLayout({
      classes: ["container"],
    });
    var labelWidget = new OO.ui.LabelWidget({
      label: prop.label,
    });
    labelWidget.$element.css("font-weight", "bold");
    var addIcon = new OO.ui.IconWidget({
      icon: "add",
      flags: "progressive",
      label: "add",
    });
    var hLayout = new OO.ui.HorizontalLayout({
      items: [labelWidget, addIcon],
    });
    fieldset.addItems(hLayout);
    var fields = [];
    if (prop.datatype === "string") {
      for (var i = 0; i < statementsMap[prop.id].length; i++) {
        const statementObject = statementsMap[prop.id][i];
        if (statementObject.mainsnak.datavalue === undefined) {
          continue;
        }
        const value = statementObject.mainsnak.datavalue.value;
        var horizontalLayout = newComboboxInput(
          value,
          statementObject.id,
          statementObject.id
        );
        fields.push(horizontalLayout);
      }
      fieldset.addItems(fields);
      addIcon.$element.on("click", function (e) {
        fields = [];
        var horizontalLayout = newComboboxInput("");
        fields.push(horizontalLayout);
        fieldset.addItems(fields);
      });
      if (typePropertyMap[prop.id] !== undefined) {
        propertyDivs[typePropertyMap[prop.id]].append(fieldset.$element);
      } else {
        otherFieldsDiv.append(fieldset.$element);
      }
    } else if (prop.datatype === "wikibase-item") {
      for (var i = 0; i < statementsMap[prop.id].length; i++) {
        const statementObject = statementsMap[prop.id][i];
        if (statementObject.mainsnak.datavalue === undefined) {
          continue;
        }
        const statementQID = statementObject.mainsnak.datavalue.value.id;
        var searchParams = {
          action: "wbsearchentities",
          format: "json",
          search: statementQID,
          language: lang,
          type: "item",
          limit: 20,
        };
        var resp = api.get(searchParams);
        resp
          .done(function (res) {
            const label = res.search[0].display.label
              ? res.search[0].display.label.value
              : statementQID;
            var horizontalLayout = newComboboxInput(
              label,
              statementObject.id,
              statementObject.id
            );
            fields.push(horizontalLayout);
            fieldset.addItems(fields);
            if (typePropertyMap[prop.id] !== undefined) {
              propertyDivs[typePropertyMap[prop.id]].append(fieldset.$element);
            } else {
              otherFieldsDiv.append(fieldset.$element);
            }
          })
          .catch(function (ex) {
            console.warn(ex);
          });
        addIcon.$element.on("click", function (e) {
          fields = [];
          var horizontalLayout = newComboboxInput("");
          fields.push(horizontalLayout);
          fieldset.addItems(fields);
        });
      }
    } else {
      if (prop.datatype === "quantity") {
        for (var i = 0; i < statementsMap[prop.id].length; i++) {
          const statementObject = statementsMap[prop.id][i];
          if (statementObject.mainsnak.datavalue === undefined) {
            continue;
          }
          const value = statementObject.mainsnak.datavalue.value;
          var horizontalLayout = newTextInput(
            value.amount,
            prop.datatype,
            statementObject.id,
            statementObject.id
          );
          fields.push(horizontalLayout);
        }
      } else if (
        prop.datatype === "commonsMedia" ||
        prop.datatype === "globecoordinate"
      ) {
        if (prop.datatype === "globecoordinate")
          console.log(statementsMap[prop.id]);
        statementsMap[prop.id].forEach(function (statementObject) {
          const value = statementObject.mainsnak.datavalue.value;
          var horizontalLayout = newTextInput(
            value,
            prop.datatype,
            statementObject.id,
            statementObject.id
          );
          fields.push(horizontalLayout);
        });
      } else if (prop.datatype === "time") {
        statementsMap[prop.id].forEach(function (statementObject) {
          const value = statementObject.mainsnak.datavalue.value.time;
          var horizontalLayout = newTextInput(
            value,
            prop.datatype,
            statementObject.id,
            statementObject.id
          );
          fields.push(horizontalLayout);
        });
      }
      fieldset.addItems(fields);
      addIcon.$element.on("click", function (e) {
        fields = [];
        var horizontalLayout = newTextInput("", prop.datatype);
        fields.push(horizontalLayout);
        fieldset.addItems(fields);
      });
      if (typePropertyMap[prop.id] !== undefined) {
        propertyDivs[typePropertyMap[prop.id]].append(fieldset.$element);
      } else {
        otherFieldsDiv.append(fieldset.$element);
      }
    }
  });
  idProperties = properties.filter(function (item) {
    return item.datatype === "external-id";
  });
  console.log(idProperties);

  idProperties.forEach(function (prop) {
    var fieldset = new OO.ui.FieldsetLayout({
      classes: ["container"],
    });
    var labelWidget = new OO.ui.LabelWidget({
      label: prop.label,
    });
    labelWidget.$element.css("font-weight", "bold");
    var addIcon = new OO.ui.IconWidget({
      icon: "add",
      flags: "progressive",
      label: "add",
    });
    var horizontalLayout = new OO.ui.HorizontalLayout({
      items: [labelWidget, addIcon],
    });
    fieldset.addItems(horizontalLayout);
    var fields = [];
    statementsMap[prop.id].forEach(function (statementObject) {
      const value = statementObject.mainsnak.datavalue.value;
      var horizontalLayout = newTextInput(
        value,
        prop.datatype,
        statementObject.id
      );
      fields.push(horizontalLayout);
    });
    fieldset.addItems(fields);
    var externalIDsDiv = $("<div></div>");
    var title = $("<h3>").text("External ID(s)");
    externalIDsDiv.append(title);
    externalIDsDiv.append(fieldset.$element);
    externalIDsDiv.css("background", "white");
    // if (typePropertyMap[prop.id] !== undefined) {
    //   propertyDivs[typePropertyMap[prop.id]].append(externalIDsDiv.$element);
    // } else {
    //   otherFieldsDiv.append(externalIDsDiv.$element);
    // }
    $("#mw-context-text").append(externalIDsDiv.$element);
    console.log("It reaches here");
  });

  Object.keys(propertyDivs).forEach(function (propID) {
    var sectionHeader = $("<h2></h2>");
    sectionHeader.html(
      'Fields for class <a href="https://www.wikidata.org/wiki/' +
        propID +
        '">' +
        classLabels[propID] +
        "</span>"
    );
    propertyDivs[propID].prepend(sectionHeader);
    $("#mw-content-text").prepend(propertyDivs[propID]);
    if (propertiesForClasses[propID].length === 0) {
      propertyDivs[propID].$element.remove();
    }
  });

  var sectionHeader = $("<h2></h2>");
  sectionHeader.text("Other Fields");
  otherFieldsDiv.prepend(sectionHeader);
  $("#mw-content-text").append(otherFieldsDiv);

  var title = $("<h2>").text("Edit Properties");
  $("#mw-content-text").prepend(title);
  var submitButton = new OO.ui.ButtonWidget({
    framed: true,
    flags: ["primary", "progressive"],
    label: "Save changes",
  });
  submitButton.$element.css("width", "100%");
  submitButton.on("click", function (e) {
    console.log("Claims to remove: ", claimGUIDsToRemove);
    console.log("GUID to statement map: ", guidToStatementMap);
    // var api = new mw.Api();
    // var requestParams = {
    //   action: 'wbremoveclaims',
    //   format: 'json',
    //   claim: claimGUIDsToRemove
    // };
    // var resp = api.get(requestParams);
    // resp.done(function(){
    //   location.reload();
    // });
  });
  $("#mw-content-text").append("<br>");
  $("#mw-content-text").append(submitButton.$element);
}

$(document).ready(function () {
  mw.loader.using("oojs-ui-core").done(function () {
    initializeEditProperties();
  });
});