mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 20:13:46 +00:00 
			
		
		
		
	This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
  n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
  n.Statement.check(node);
for (const file of process.argv.slice(2)) {
  console.log("Parsing", file);
  const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
    parser: path.extname(file) === ".ts" ? tsParser : babelParser,
  });
  let changed = false;
  let inLoop = false;
  let replaceReturn = false;
  const visitLoop = (...args: string[]) =>
    function(this: Context, path: NodePath) {
      for (const arg of args) {
        this.visit(path.get(arg));
      }
      const old = { inLoop };
      inLoop = true;
      this.visit(path.get("body"));
      inLoop = old.inLoop;
      return false;
    };
  recast.visit(ast, {
    visitDoWhileStatement: visitLoop("test"),
    visitExpressionStatement(path) {
      const { expression, comments } = path.node;
      let valueOnly;
      if (
        n.CallExpression.check(expression) &&
        n.MemberExpression.check(expression.callee) &&
        !expression.callee.computed &&
        n.Identifier.check(expression.callee.object) &&
        expression.callee.object.name === "_" &&
        n.Identifier.check(expression.callee.property) &&
        ["each", "forEach"].includes(expression.callee.property.name) &&
        [2, 3].includes(expression.arguments.length) &&
        checkExpression(expression.arguments[0]) &&
        (n.FunctionExpression.check(expression.arguments[1]) ||
          n.ArrowFunctionExpression.check(expression.arguments[1])) &&
        [1, 2].includes(expression.arguments[1].params.length) &&
        n.Identifier.check(expression.arguments[1].params[0]) &&
        ((valueOnly = expression.arguments[1].params[1] === undefined) ||
          n.Identifier.check(expression.arguments[1].params[1])) &&
        (expression.arguments[2] === undefined ||
          n.ThisExpression.check(expression.arguments[2]))
      ) {
        const old = { inLoop, replaceReturn };
        inLoop = false;
        replaceReturn = true;
        this.visit(
          path
            .get("expression")
            .get("arguments")
            .get(1)
            .get("body")
        );
        inLoop = old.inLoop;
        replaceReturn = old.replaceReturn;
        const [right, { body, params }] = expression.arguments;
        const loop = b.forOfStatement(
          b.variableDeclaration("let", [
            b.variableDeclarator(
              valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
            ),
          ]),
          valueOnly
            ? right
            : b.callExpression(
                b.memberExpression(right, b.identifier("entries")),
                []
              ),
          checkStatement(body) ? body : b.expressionStatement(body)
        );
        loop.comments = comments;
        path.replace(loop);
        changed = true;
      }
      this.traverse(path);
    },
    visitForStatement: visitLoop("init", "test", "update"),
    visitForInStatement: visitLoop("left", "right"),
    visitForOfStatement: visitLoop("left", "right"),
    visitFunction(path) {
      this.visit(path.get("params"));
      const old = { replaceReturn };
      replaceReturn = false;
      this.visit(path.get("body"));
      replaceReturn = old.replaceReturn;
      return false;
    },
    visitReturnStatement(path) {
      if (replaceReturn) {
        assert(!inLoop); // could use labeled continue if this ever fires
        const { argument, comments } = path.node;
        if (argument === null) {
          const s = b.continueStatement();
          s.comments = comments;
          path.replace(s);
        } else {
          const s = b.expressionStatement(argument);
          s.comments = comments;
          path.replace(s, b.continueStatement());
        }
        return false;
      }
      this.traverse(path);
    },
    visitWhileStatement: visitLoop("test"),
  });
  if (changed) {
    console.log("Writing", file);
    fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
  }
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
		
	
		
			
				
	
	
		
			258 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			258 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const render_hotspot_overlay = require('../templates/hotspot_overlay.hbs');
 | |
| const render_intro_reply_hotspot = require('../templates/intro_reply_hotspot.hbs');
 | |
| 
 | |
| // popover orientations
 | |
| const TOP = 'top';
 | |
| const LEFT = 'left';
 | |
| const RIGHT = 'right';
 | |
| const BOTTOM = 'bottom';
 | |
| const LEFT_BOTTOM = 'left_bottom';
 | |
| const VIEWPORT_CENTER = 'viewport_center';
 | |
| 
 | |
| // popover orientation can optionally be fixed here (property: popover),
 | |
| // otherwise popovers.compute_placement is used to compute orientation
 | |
| const HOTSPOT_LOCATIONS = {
 | |
|     intro_reply: {
 | |
|         element: '.selected_message .messagebox-content',
 | |
|         offset_x: 0.85,
 | |
|         offset_y: 0.7,
 | |
|         popover: BOTTOM,
 | |
|     },
 | |
|     intro_streams: {
 | |
|         element: '#streams_header .sidebar-title',
 | |
|         offset_x: 1.35,
 | |
|         offset_y: 0.39,
 | |
|     },
 | |
|     intro_topics: {
 | |
|         element: '.topic-name',
 | |
|         offset_x: 0.8,
 | |
|         offset_y: 0.39,
 | |
|     },
 | |
|     intro_gear: {
 | |
|         element: '#settings-dropdown',
 | |
|         offset_x: -0.4,
 | |
|         offset_y: 1.2,
 | |
|         popover: LEFT_BOTTOM,
 | |
|     },
 | |
|     intro_compose: {
 | |
|         element: '#left_bar_compose_stream_button_big',
 | |
|         offset_x: 0,
 | |
|         offset_y: 0,
 | |
|     },
 | |
| };
 | |
| 
 | |
| // popover illustration url(s)
 | |
| const WHALE = '/static/images/hotspots/whale.svg';
 | |
| 
 | |
| exports.post_hotspot_as_read = function (hotspot_name) {
 | |
|     channel.post({
 | |
|         url: '/json/users/me/hotspots',
 | |
|         data: { hotspot: JSON.stringify(hotspot_name) },
 | |
|         error: function (err) {
 | |
|             blueslip.error(err.responseText);
 | |
|         },
 | |
|     });
 | |
| };
 | |
| 
 | |
| function place_icon(hotspot) {
 | |
|     const element = $(hotspot.location.element);
 | |
|     const icon = $('#hotspot_' + hotspot.name + '_icon');
 | |
| 
 | |
|     if (element.length === 0 || element.css('display') === 'none' ||
 | |
|         !element.is(':visible') || element.is(':hidden')) {
 | |
|         icon.css('display', 'none');
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     const offset = {
 | |
|         top: element.outerHeight() * hotspot.location.offset_y,
 | |
|         left: element.outerWidth() * hotspot.location.offset_x,
 | |
|     };
 | |
|     const client_rect = element.get(0).getBoundingClientRect();
 | |
|     const placement = {
 | |
|         top: client_rect.top + offset.top,
 | |
|         left: client_rect.left + offset.left,
 | |
|     };
 | |
|     icon.css('display', 'block');
 | |
|     icon.css(placement);
 | |
|     return true;
 | |
| }
 | |
| 
 | |
| function place_popover(hotspot) {
 | |
|     if (!hotspot.location.element) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     const popover_width = $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover').outerWidth();
 | |
|     const popover_height = $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover').outerHeight();
 | |
|     const el_width = $(hotspot.location.element).outerWidth();
 | |
|     const el_height = $(hotspot.location.element).outerHeight();
 | |
|     const arrow_offset = 20;
 | |
| 
 | |
|     let popover_offset;
 | |
|     let arrow_placement;
 | |
|     const orientation = hotspot.location.popover ||
 | |
|         popovers.compute_placement(
 | |
|             $(hotspot.location.element),
 | |
|             popover_height,
 | |
|             popover_width,
 | |
|             false
 | |
|         );
 | |
| 
 | |
|     switch (orientation) {
 | |
|     case TOP:
 | |
|         popover_offset = {
 | |
|             top: -(popover_height + arrow_offset),
 | |
|             left: el_width / 2 - popover_width / 2,
 | |
|         };
 | |
|         arrow_placement = 'bottom';
 | |
|         break;
 | |
| 
 | |
|     case LEFT:
 | |
|         popover_offset = {
 | |
|             top: el_height / 2 - popover_height / 2,
 | |
|             left: -(popover_width + arrow_offset),
 | |
|         };
 | |
|         arrow_placement = 'right';
 | |
|         break;
 | |
| 
 | |
|     case BOTTOM:
 | |
|         popover_offset = {
 | |
|             top: el_height + arrow_offset,
 | |
|             left: el_width / 2 - popover_width / 2,
 | |
|         };
 | |
|         arrow_placement = 'top';
 | |
|         break;
 | |
| 
 | |
|     case RIGHT:
 | |
|         popover_offset = {
 | |
|             top: el_height / 2 - popover_height / 2,
 | |
|             left: el_width + arrow_offset,
 | |
|         };
 | |
|         arrow_placement = 'left';
 | |
|         break;
 | |
| 
 | |
|     case LEFT_BOTTOM:
 | |
|         popover_offset = {
 | |
|             top: 0,
 | |
|             left: -(popover_width + arrow_offset / 2),
 | |
|         };
 | |
|         arrow_placement = '';
 | |
|         break;
 | |
| 
 | |
|     case VIEWPORT_CENTER:
 | |
|         popover_offset = {
 | |
|             top: el_height / 2,
 | |
|             left: el_width / 2,
 | |
|         };
 | |
|         arrow_placement = '';
 | |
|         break;
 | |
| 
 | |
|     default:
 | |
|         blueslip.error(
 | |
|             'Invalid popover placement value for hotspot \'' +
 | |
|                 hotspot.name + '\''
 | |
|         );
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     // position arrow
 | |
|     arrow_placement = 'arrow-' + arrow_placement;
 | |
|     $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover')
 | |
|         .removeClass('arrow-top arrow-left arrow-bottom arrow-right')
 | |
|         .addClass(arrow_placement);
 | |
| 
 | |
|     // position popover
 | |
|     let popover_placement;
 | |
|     if (orientation === VIEWPORT_CENTER) {
 | |
|         popover_placement = {
 | |
|             top: '45%',
 | |
|             left: '50%',
 | |
|             transform: 'translate(-50%, -50%)',
 | |
|         };
 | |
|     } else {
 | |
|         const client_rect = $(hotspot.location.element).get(0).getBoundingClientRect();
 | |
|         popover_placement = {
 | |
|             top: client_rect.top + popover_offset.top,
 | |
|             left: client_rect.left + popover_offset.left,
 | |
|             transform: '',
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     $('#hotspot_' + hotspot.name + '_overlay .hotspot-popover')
 | |
|         .css(popover_placement);
 | |
| }
 | |
| 
 | |
| function insert_hotspot_into_DOM(hotspot) {
 | |
|     if (hotspot.name === "intro_reply") {
 | |
|         $('#bottom_whitespace').append(render_intro_reply_hotspot({}));
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     const hotspot_overlay_HTML = render_hotspot_overlay({
 | |
|         name: hotspot.name,
 | |
|         title: hotspot.title,
 | |
|         description: hotspot.description,
 | |
|         img: WHALE,
 | |
|     });
 | |
| 
 | |
|     const hotspot_icon_HTML =
 | |
|         '<div class="hotspot-icon" id="hotspot_' + hotspot.name + '_icon">' +
 | |
|             '<span class="dot"></span>' +
 | |
|             '<span class="pulse"></span>' +
 | |
|             '<div class="bounce"><span class="bounce-icon">?</span></div>' +
 | |
|         '</div>';
 | |
| 
 | |
|     setTimeout(function () {
 | |
|         $('body').prepend(hotspot_icon_HTML);
 | |
|         $('body').prepend(hotspot_overlay_HTML);
 | |
|         if (place_icon(hotspot)) {
 | |
|             place_popover(hotspot);
 | |
|         }
 | |
| 
 | |
|         // reposition on any event that might update the UI
 | |
|         ['resize', 'scroll', 'onkeydown', 'click'].forEach(function (event_name) {
 | |
|             window.addEventListener(event_name, _.debounce(function () {
 | |
|                 if (place_icon(hotspot)) {
 | |
|                     place_popover(hotspot);
 | |
|                 }
 | |
|             }, 10), true);
 | |
|         });
 | |
|     }, hotspot.delay * 1000);
 | |
| }
 | |
| 
 | |
| exports.is_open = function () {
 | |
|     return $('.hotspot.overlay').hasClass('show');
 | |
| };
 | |
| 
 | |
| exports.close_hotspot_icon = function (elem) {
 | |
|     $(elem).animate({ opacity: 0 }, {
 | |
|         duration: 300,
 | |
|         done: function () {
 | |
|             $(elem).css({ display: 'none' });
 | |
|         }.bind(elem),
 | |
|     });
 | |
| };
 | |
| 
 | |
| function close_read_hotspots(new_hotspots) {
 | |
|     const unwanted_hotspots = _.difference(_.keys(HOTSPOT_LOCATIONS), _.pluck(new_hotspots, 'name'));
 | |
| 
 | |
|     for (const hotspot_name of unwanted_hotspots) {
 | |
|         exports.close_hotspot_icon($('#hotspot_' + hotspot_name + '_icon'));
 | |
|     }
 | |
| }
 | |
| 
 | |
| exports.load_new = function (new_hotspots) {
 | |
|     close_read_hotspots(new_hotspots);
 | |
|     new_hotspots.forEach(function (hotspot) {
 | |
|         hotspot.location = HOTSPOT_LOCATIONS[hotspot.name];
 | |
|     });
 | |
|     new_hotspots.forEach(insert_hotspot_into_DOM);
 | |
| };
 | |
| 
 | |
| exports.initialize = function () {
 | |
|     exports.load_new(page_params.hotspots);
 | |
| };
 | |
| 
 | |
| window.hotspots = exports;
 |