Commit 3831a78f authored by Ben Mullins's avatar Ben Mullins
Browse files

Issue #3270395 by murilohp, nod_, mradcliffe, xjm, Wim Leers: Remove use of...

Issue #3270395 by murilohp, nod_, mradcliffe, xjm, Wim Leers: Remove use of underscore from editor.admin.js and filter.filter_html.admin.js
parent dafde755
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -6,7 +6,6 @@ drupal.editor.admin:
    - core/jquery
    - core/once
    - core/drupal
    - core/internal.underscore

drupal.editor:
  version: VERSION
+35 −24
Original line number Diff line number Diff line
@@ -7,7 +7,7 @@
 * to automatically adjust their settings based on the editor configuration.
 */

(function ($, _, Drupal, document) {
(function ($, Drupal, document) {
  /**
   * Editor configuration namespace.
   *
@@ -226,7 +226,7 @@
      ) {
        // If the tag does not exist in the universe, then it definitely can't
        // have this specific property value.
        if (!_.has(universe, tag)) {
        if (!universe.hasOwnProperty(tag)) {
          return false;
        }

@@ -240,8 +240,11 @@
        }

        // The simple case: no wildcard in property value.
        if (_.indexOf(propertyValue, '*') === -1) {
          if (_.has(universe, tag) && _.has(universe[tag], key)) {
        if (propertyValue.indexOf('*') === -1) {
          if (
            universe.hasOwnProperty(tag) &&
            universe[tag].hasOwnProperty(key)
          ) {
            if (allowing) {
              universe[tag][key] = true;
            }
@@ -253,7 +256,7 @@

        let atLeastOneFound = false;
        const regex = key.replace(/\*/g, '[^ ]*');
        _.each(_.keys(universe[tag]), (key) => {
        Object.keys(universe[tag]).forEach((key) => {
          if (key.match(regex)) {
            atLeastOneFound = true;
            if (allowing) {
@@ -286,7 +289,7 @@
        allowing,
      ) {
        let atLeastOneFound = false;
        _.each(_.keys(universe), (tag) => {
        Object.keys(universe).forEach((tag) => {
          if (
            // eslint-disable-next-line no-use-before-define
            findPropertyValuesOnTag(
@@ -340,7 +343,7 @@
        }

        let atLeastOneFound = false;
        _.each(propertyValues, (propertyValue) => {
        propertyValues.forEach((propertyValue) => {
          if (
            findPropertyValueOnTag(
              universe,
@@ -367,7 +370,7 @@
       */
      function deleteAllTagsFromUniverseIfAllowed(universe) {
        let atLeastOneDeleted = false;
        _.each(_.keys(universe), (tag) => {
        Object.keys(universe).forEach((tag) => {
          // eslint-disable-next-line no-use-before-define
          if (deleteFromUniverseIfAllowed(universe, tag)) {
            atLeastOneDeleted = true;
@@ -394,8 +397,10 @@
          return deleteAllTagsFromUniverseIfAllowed(universe);
        }
        if (
          _.has(universe, tag) &&
          _.every(_.omit(universe[tag], 'touchedByAllowedPropertyRule'))
          universe.hasOwnProperty(tag) &&
          Object.keys(universe[tag])
            .filter((key) => key !== 'touchedByAllowedPropertyRule')
            .every((key) => universe[tag][key])
        ) {
          delete universe[tag];
          return true;
@@ -419,12 +424,15 @@
        const properties = ['attributes', 'styles', 'classes'];

        // Check if a tag in the universe is forbidden.
        const allRequiredTags = _.keys(universe);
        const allRequiredTags = Object.keys(universe);
        let filterRule;
        for (let i = 0; i < filterStatus.rules.length; i++) {
          filterRule = filterStatus.rules[i];
          if (filterRule.allow === false) {
            if (_.intersection(allRequiredTags, filterRule.tags).length > 0) {
            const intersection = filterRule.tags.filter((tag) =>
              allRequiredTags.includes(tag),
            );
            if (intersection.length > 0) {
              return true;
            }
          }
@@ -485,18 +493,18 @@
        let tag;
        for (
          let l = 0;
          !_.isEmpty(universe) && l < filterStatus.rules.length;
          Object.keys(universe).length > 0 && l < filterStatus.rules.length;
          l++
        ) {
          filterRule = filterStatus.rules[l];
          if (filterRule.allow === true) {
            for (
              let m = 0;
              !_.isEmpty(universe) && m < filterRule.tags.length;
              Object.keys(universe).length > 0 && m < filterRule.tags.length;
              m++
            ) {
              tag = filterRule.tags[m];
              if (_.has(universe, tag)) {
              if (universe.hasOwnProperty(tag)) {
                universe[tag].tag = true;
                deleteFromUniverseIfAllowed(universe, tag);
              }
@@ -508,7 +516,7 @@
        // For all filter rules…
        for (
          let i = 0;
          !_.isEmpty(universe) && i < filterStatus.rules.length;
          Object.keys(universe).length > 0 && i < filterStatus.rules.length;
          i++
        ) {
          filterRule = filterStatus.rules[i];
@@ -520,7 +528,8 @@
            // … for all those tags …
            for (
              let j = 0;
              !_.isEmpty(universe) && j < filterRule.restrictedTags.tags.length;
              Object.keys(universe).length > 0 &&
              j < filterRule.restrictedTags.tags.length;
              j++
            ) {
              tag = filterRule.restrictedTags.tags[j];
@@ -604,17 +613,19 @@
        // values and/or rules for forbidding tag property values. For details:
        // see the comments below.
        // @see generateUniverseFromFeatureRequirements()
        if (_.some(_.pluck(filterStatus.rules, 'allow'))) {
        if (filterStatus.rules.some(({ allow }) => allow)) {
          // If the universe is empty, then everything was explicitly allowed
          // and our job is done: this filter allows this feature!
          if (_.isEmpty(universe)) {
          if (Object.keys(universe).length === 0) {
            return true;
          }
          // Otherwise, it is still possible that this feature is allowed.

          // Every tag must be explicitly allowed if there are filter rules
          // doing tag whitelisting.
          if (!_.every(_.pluck(universe, 'tag'))) {
          if (
            !Object.keys(universe).every((tagName) => universe[tagName].tag)
          ) {
            return false;
          }
          // Every tag was explicitly allowed, but since the universe is not
@@ -626,18 +637,18 @@
          // matter that the properties: this could never have happened
          // anyway. It's only this late that we can know this for certain.

          const tags = _.keys(universe);
          const tags = Object.keys(universe);
          // Figure out if there was any rule applying whitelisting tag
          // restrictions to each of the remaining tags.
          for (let i = 0; i < tags.length; i++) {
            const tag = tags[i];
            if (_.has(universe, tag)) {
            if (universe.hasOwnProperty(tag)) {
              if (universe[tag].touchedByAllowedPropertyRule === false) {
                delete universe[tag];
              }
            }
          }
          return _.isEmpty(universe);
          return Object.keys(universe).length === 0;
        }
        // Otherwise, if all filter rules were doing blacklisting, then the sole
        // fact that we got to this point indicates that this filter allows for
@@ -1023,4 +1034,4 @@
      });
    },
  };
})(jQuery, _, Drupal, document);
})(jQuery, Drupal, document);
+30 −33
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@
* @preserve
**/

(function ($, _, Drupal, document) {
(function ($, Drupal, document) {
  Drupal.editorConfiguration = {
    addedFeature(feature) {
      $(document).trigger('drupalEditorFeatureAdded', feature);
@@ -57,7 +57,7 @@
      }

      function findPropertyValueOnTag(universe, tag, property, propertyValue, allowing) {
        if (!_.has(universe, tag)) {
        if (!universe.hasOwnProperty(tag)) {
          return false;
        }

@@ -67,8 +67,8 @@
          universe[tag].touchedByAllowedPropertyRule = true;
        }

        if (_.indexOf(propertyValue, '*') === -1) {
          if (_.has(universe, tag) && _.has(universe[tag], key)) {
        if (propertyValue.indexOf('*') === -1) {
          if (universe.hasOwnProperty(tag) && universe[tag].hasOwnProperty(key)) {
            if (allowing) {
              universe[tag][key] = true;
            }
@@ -81,8 +81,7 @@

        let atLeastOneFound = false;
        const regex = key.replace(/\*/g, '[^ ]*');

        _.each(_.keys(universe[tag]), key => {
        Object.keys(universe[tag]).forEach(key => {
          if (key.match(regex)) {
            atLeastOneFound = true;

@@ -91,19 +90,16 @@
            }
          }
        });

        return atLeastOneFound;
      }

      function findPropertyValuesOnAllTags(universe, property, propertyValues, allowing) {
        let atLeastOneFound = false;

        _.each(_.keys(universe), tag => {
        Object.keys(universe).forEach(tag => {
          if (findPropertyValuesOnTag(universe, tag, property, propertyValues, allowing)) {
            atLeastOneFound = true;
          }
        });

        return atLeastOneFound;
      }

@@ -113,25 +109,21 @@
        }

        let atLeastOneFound = false;

        _.each(propertyValues, propertyValue => {
        propertyValues.forEach(propertyValue => {
          if (findPropertyValueOnTag(universe, tag, property, propertyValue, allowing)) {
            atLeastOneFound = true;
          }
        });

        return atLeastOneFound;
      }

      function deleteAllTagsFromUniverseIfAllowed(universe) {
        let atLeastOneDeleted = false;

        _.each(_.keys(universe), tag => {
        Object.keys(universe).forEach(tag => {
          if (deleteFromUniverseIfAllowed(universe, tag)) {
            atLeastOneDeleted = true;
          }
        });

        return atLeastOneDeleted;
      }

@@ -140,7 +132,7 @@
          return deleteAllTagsFromUniverseIfAllowed(universe);
        }

        if (_.has(universe, tag) && _.every(_.omit(universe[tag], 'touchedByAllowedPropertyRule'))) {
        if (universe.hasOwnProperty(tag) && Object.keys(universe[tag]).filter(key => key !== 'touchedByAllowedPropertyRule').every(key => universe[tag][key])) {
          delete universe[tag];
          return true;
        }
@@ -150,16 +142,16 @@

      function anyForbiddenFilterRuleMatches(universe, filterStatus) {
        const properties = ['attributes', 'styles', 'classes'];

        const allRequiredTags = _.keys(universe);

        const allRequiredTags = Object.keys(universe);
        let filterRule;

        for (let i = 0; i < filterStatus.rules.length; i++) {
          filterRule = filterStatus.rules[i];

          if (filterRule.allow === false) {
            if (_.intersection(allRequiredTags, filterRule.tags).length > 0) {
            const intersection = filterRule.tags.filter(tag => allRequiredTags.includes(tag));

            if (intersection.length > 0) {
              return true;
            }
          }
@@ -191,14 +183,14 @@
        let filterRule;
        let tag;

        for (let l = 0; !_.isEmpty(universe) && l < filterStatus.rules.length; l++) {
        for (let l = 0; Object.keys(universe).length > 0 && l < filterStatus.rules.length; l++) {
          filterRule = filterStatus.rules[l];

          if (filterRule.allow === true) {
            for (let m = 0; !_.isEmpty(universe) && m < filterRule.tags.length; m++) {
            for (let m = 0; Object.keys(universe).length > 0 && m < filterRule.tags.length; m++) {
              tag = filterRule.tags[m];

              if (_.has(universe, tag)) {
              if (universe.hasOwnProperty(tag)) {
                universe[tag].tag = true;
                deleteFromUniverseIfAllowed(universe, tag);
              }
@@ -206,11 +198,11 @@
          }
        }

        for (let i = 0; !_.isEmpty(universe) && i < filterStatus.rules.length; i++) {
        for (let i = 0; Object.keys(universe).length > 0 && i < filterStatus.rules.length; i++) {
          filterRule = filterStatus.rules[i];

          if (filterRule.restrictedTags.tags.length && !emptyProperties(filterRule.restrictedTags.allowed)) {
            for (let j = 0; !_.isEmpty(universe) && j < filterRule.restrictedTags.tags.length; j++) {
            for (let j = 0; Object.keys(universe).length > 0 && j < filterRule.restrictedTags.tags.length; j++) {
              tag = filterRule.restrictedTags.tags[j];

              for (let k = 0; k < properties.length; k++) {
@@ -246,28 +238,33 @@

        markAllowedTagsAndPropertyValues(universe, filterStatus);

        if (_.some(_.pluck(filterStatus.rules, 'allow'))) {
          if (_.isEmpty(universe)) {
        if (filterStatus.rules.some(_ref => {
          let {
            allow
          } = _ref;
          return allow;
        })) {
          if (Object.keys(universe).length === 0) {
            return true;
          }

          if (!_.every(_.pluck(universe, 'tag'))) {
          if (!Object.keys(universe).every(tagName => universe[tagName].tag)) {
            return false;
          }

          const tags = _.keys(universe);
          const tags = Object.keys(universe);

          for (let i = 0; i < tags.length; i++) {
            const tag = tags[i];

            if (_.has(universe, tag)) {
            if (universe.hasOwnProperty(tag)) {
              if (universe[tag].touchedByAllowedPropertyRule === false) {
                delete universe[tag];
              }
            }
          }

          return _.isEmpty(universe);
          return Object.keys(universe).length === 0;
        }

        return true;
@@ -373,4 +370,4 @@
    }

  };
})(jQuery, _, Drupal, document);
 No newline at end of file
})(jQuery, Drupal, document);
 No newline at end of file
+72 −55
Original line number Diff line number Diff line
@@ -3,7 +3,7 @@
 * Attaches behavior for updating filter_html's settings automatically.
 */

(function ($, Drupal, _, document) {
(function ($, Drupal, document) {
  if (Drupal.filterConfiguration) {
    /**
     * Implement a live setting parser to prevent text editors from automatically
@@ -35,6 +35,21 @@
    };
  }

  /**
   * Gets the values that are present in one array but not another.
   *
   * @param {Array[]} args
   *   The list of arrays to process.
   *
   * @return {Array}
   *   Returns the first array without the values present in other arrays.
   */
  function difference(...args) {
    return args.reduce((mainData, otherData) =>
      mainData.filter((data) => !otherData.includes(data)),
    );
  }

  /**
   * Displays and updates what HTML tags are allowed to use in a filter.
   *
@@ -99,9 +114,9 @@

        // When the allowed tags list is manually changed, update userTags.
        that.$allowedHTMLFormItem.on('change.updateUserTags', function () {
          that.userTags = _.difference(
            that._parseSetting(this.value),
            that.autoTags,
          that.userTags = difference(
            Object.values(that._parseSetting(this.value)),
            Object.values(that.autoTags),
          );
        });
      });
@@ -121,14 +136,18 @@
      this.$allowedHTMLDescription.find('.editor-update-message').remove();

      // If any auto-created tags: insert message and update form item.
      if (!_.isEmpty(this.autoTags)) {
      if (Object.keys(this.autoTags).length > 0) {
        this.$allowedHTMLDescription.append(
          Drupal.theme('filterFilterHTMLUpdateMessage', this.autoTags),
        );
        const userTagsWithoutOverrides = _.omit(
          this.userTags,
          _.keys(this.autoTags),
        );

        const userTagsWithoutOverrides = {};
        Object.keys(this.userTags)
          .filter((tag) => !this.autoTags.hasOwnProperty(tag))
          .forEach((tag) => {
            userTagsWithoutOverrides[tag] = this.userTags[tag];
          });

        this.$allowedHTMLFormItem.val(
          `${this._generateSetting(
            userTagsWithoutOverrides,
@@ -171,7 +190,7 @@
          featureRule = feature[f];
          for (let t = 0; t < featureRule.required.tags.length; t++) {
            tag = featureRule.required.tags[t];
            if (!_.has(editorRequiredTags, tag)) {
            if (!editorRequiredTags.hasOwnProperty(tag)) {
              filterRule = new Drupal.FilterHTMLRule();
              filterRule.restrictedTags.tags = [tag];
              // @todo Neither Drupal.FilterHtmlRule nor
@@ -191,14 +210,14 @@
            // attributes.
            else {
              filterRule = editorRequiredTags[tag];
              filterRule.restrictedTags.allowed.attributes = _.union(
                filterRule.restrictedTags.allowed.attributes,
                featureRule.required.attributes,
              );
              filterRule.restrictedTags.allowed.classes = _.union(
                filterRule.restrictedTags.allowed.classes,
                featureRule.required.classes,
              );
              filterRule.restrictedTags.allowed.attributes = [
                ...filterRule.restrictedTags.allowed.attributes,
                ...featureRule.required.attributes,
              ];
              filterRule.restrictedTags.allowed.classes = [
                ...filterRule.restrictedTags.allowed.classes,
                ...featureRule.required.classes,
              ];
            }
          }
        }
@@ -214,7 +233,7 @@
      Object.keys(editorRequiredTags).forEach((tag) => {
        // If userAllowedTags does not contain a rule for this editor-required
        // tag, then add it to the list of automatically allowed tags.
        if (!_.has(userAllowedTags, tag)) {
        if (!userAllowedTags.hasOwnProperty(tag)) {
          autoAllowedTags[tag] = editorRequiredTags[tag];
        }
        // Otherwise, if userAllowedTags already allows this tag, then check if
@@ -227,28 +246,28 @@
            userAllowedTags[tag].restrictedTags.allowed.attributes;
          const needsAdditionalAttributes =
            requiredAttributes.length &&
            _.difference(requiredAttributes, allowedAttributes).length;
            difference(requiredAttributes, allowedAttributes).length;
          const requiredClasses =
            editorRequiredTags[tag].restrictedTags.allowed.classes;
          const allowedClasses =
            userAllowedTags[tag].restrictedTags.allowed.classes;
          const needsAdditionalClasses =
            requiredClasses.length &&
            _.difference(requiredClasses, allowedClasses).length;
            difference(requiredClasses, allowedClasses).length;
          if (needsAdditionalAttributes || needsAdditionalClasses) {
            autoAllowedTags[tag] = userAllowedTags[tag].clone();
          }
          if (needsAdditionalAttributes) {
            autoAllowedTags[tag].restrictedTags.allowed.attributes = _.union(
              allowedAttributes,
              requiredAttributes,
            );
            autoAllowedTags[tag].restrictedTags.allowed.attributes = [
              ...allowedAttributes,
              ...requiredAttributes,
            ];
          }
          if (needsAdditionalClasses) {
            autoAllowedTags[tag].restrictedTags.allowed.classes = _.union(
              allowedClasses,
              requiredClasses,
            );
            autoAllowedTags[tag].restrictedTags.allowed.classes = [
              ...allowedClasses,
              ...requiredClasses,
            ];
          }
        }
      });
@@ -322,9 +341,9 @@
     *   The string representation of the setting. e.g. "<p> <br> <a>"
     */
    _generateSetting(tags) {
      return _.reduce(
        tags,
        (setting, rule, tag) => {
      return Object.keys(tags).reduce((setting, tag) => {
        const rule = tags[tag];

        if (setting.length) {
          setting += ' ';
        }
@@ -346,9 +365,7 @@

        setting += '>';
        return setting;
        },
        '',
      );
      }, '');
    },
  };

@@ -373,4 +390,4 @@
    html += '</p>';
    return html;
  };
})(jQuery, Drupal, _, document);
})(jQuery, Drupal, document);
+27 −19
Original line number Diff line number Diff line
@@ -5,7 +5,7 @@
* @preserve
**/

(function ($, Drupal, _, document) {
(function ($, Drupal, document) {
  if (Drupal.filterConfiguration) {
    Drupal.filterConfiguration.liveSettingParsers.filter_html = {
      getRules() {
@@ -23,6 +23,14 @@
    };
  }

  function difference() {
    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }

    return args.reduce((mainData, otherData) => mainData.filter(data => !otherData.includes(data)));
  }

  Drupal.behaviors.filterFilterHtmlUpdating = {
    $allowedHTMLFormItem: null,
    $allowedHTMLDescription: null,
@@ -54,7 +62,7 @@
          }
        });
        that.$allowedHTMLFormItem.on('change.updateUserTags', function () {
          that.userTags = _.difference(that._parseSetting(this.value), that.autoTags);
          that.userTags = difference(Object.values(that._parseSetting(this.value)), Object.values(that.autoTags));
        });
      });
    },
@@ -63,11 +71,12 @@
      this.autoTags = this._calculateAutoAllowedTags(this.userTags, this.newFeatures);
      this.$allowedHTMLDescription.find('.editor-update-message').remove();

      if (!_.isEmpty(this.autoTags)) {
      if (Object.keys(this.autoTags).length > 0) {
        this.$allowedHTMLDescription.append(Drupal.theme('filterFilterHTMLUpdateMessage', this.autoTags));

        const userTagsWithoutOverrides = _.omit(this.userTags, _.keys(this.autoTags));

        const userTagsWithoutOverrides = {};
        Object.keys(this.userTags).filter(tag => !this.autoTags.hasOwnProperty(tag)).forEach(tag => {
          userTagsWithoutOverrides[tag] = this.userTags[tag];
        });
        this.$allowedHTMLFormItem.val(`${this._generateSetting(userTagsWithoutOverrides)} ${this._generateSetting(this.autoTags)}`);
      } else {
        this.$allowedHTMLFormItem.val(this._generateSetting(this.userTags));
@@ -88,7 +97,7 @@
          for (let t = 0; t < featureRule.required.tags.length; t++) {
            tag = featureRule.required.tags[t];

            if (!_.has(editorRequiredTags, tag)) {
            if (!editorRequiredTags.hasOwnProperty(tag)) {
              filterRule = new Drupal.FilterHTMLRule();
              filterRule.restrictedTags.tags = [tag];
              filterRule.restrictedTags.allowed.attributes = featureRule.required.attributes.slice(0);
@@ -96,37 +105,34 @@
              editorRequiredTags[tag] = filterRule;
            } else {
              filterRule = editorRequiredTags[tag];
              filterRule.restrictedTags.allowed.attributes = _.union(filterRule.restrictedTags.allowed.attributes, featureRule.required.attributes);
              filterRule.restrictedTags.allowed.classes = _.union(filterRule.restrictedTags.allowed.classes, featureRule.required.classes);
              filterRule.restrictedTags.allowed.attributes = [...filterRule.restrictedTags.allowed.attributes, ...featureRule.required.attributes];
              filterRule.restrictedTags.allowed.classes = [...filterRule.restrictedTags.allowed.classes, ...featureRule.required.classes];
            }
          }
        }
      });
      const autoAllowedTags = {};
      Object.keys(editorRequiredTags).forEach(tag => {
        if (!_.has(userAllowedTags, tag)) {
        if (!userAllowedTags.hasOwnProperty(tag)) {
          autoAllowedTags[tag] = editorRequiredTags[tag];
        } else {
          const requiredAttributes = editorRequiredTags[tag].restrictedTags.allowed.attributes;
          const allowedAttributes = userAllowedTags[tag].restrictedTags.allowed.attributes;

          const needsAdditionalAttributes = requiredAttributes.length && _.difference(requiredAttributes, allowedAttributes).length;

          const needsAdditionalAttributes = requiredAttributes.length && difference(requiredAttributes, allowedAttributes).length;
          const requiredClasses = editorRequiredTags[tag].restrictedTags.allowed.classes;
          const allowedClasses = userAllowedTags[tag].restrictedTags.allowed.classes;

          const needsAdditionalClasses = requiredClasses.length && _.difference(requiredClasses, allowedClasses).length;
          const needsAdditionalClasses = requiredClasses.length && difference(requiredClasses, allowedClasses).length;

          if (needsAdditionalAttributes || needsAdditionalClasses) {
            autoAllowedTags[tag] = userAllowedTags[tag].clone();
          }

          if (needsAdditionalAttributes) {
            autoAllowedTags[tag].restrictedTags.allowed.attributes = _.union(allowedAttributes, requiredAttributes);
            autoAllowedTags[tag].restrictedTags.allowed.attributes = [...allowedAttributes, ...requiredAttributes];
          }

          if (needsAdditionalClasses) {
            autoAllowedTags[tag].restrictedTags.allowed.classes = _.union(allowedClasses, requiredClasses);
            autoAllowedTags[tag].restrictedTags.allowed.classes = [...allowedClasses, ...requiredClasses];
          }
        }
      });
@@ -167,7 +173,9 @@
    },

    _generateSetting(tags) {
      return _.reduce(tags, (setting, rule, tag) => {
      return Object.keys(tags).reduce((setting, tag) => {
        const rule = tags[tag];

        if (setting.length) {
          setting += ' ';
        }
@@ -201,4 +209,4 @@
    html += '</p>';
    return html;
  };
})(jQuery, Drupal, _, document);
 No newline at end of file
})(jQuery, Drupal, document);
 No newline at end of file
Loading