if ( globalThis.document instanceof Object === false ) { return r.s; }

uBO Lite (uBOL) 是款以 MV3 為基礎的「免權限」內容阻擋器。

預設規則集對應了 uBlock Origin 的預設過濾集：

- uBlock Origin 內建的過濾器清單
- EasyList
- EasyPrivacy
- Peter Lowe’s Ad and tracking server list

您可以前往選項頁面（按下彈出面板的 **齒輪** 按鈕）新增更多規則集。

uBOL 是完全宣告式的，意即過濾過程中不需要持續性的 uBOL 處理程序參與，且以 CSS/JS 注入為基礎進行的內容過濾由可靠的瀏覽器執行，而非是擴充功能。 這就代表 uBOL 在內容阻擋過程不會佔用 CPU 和記憶體資源——除了和彈出面板或選項頁面互動的場景外，都不需要 uBOL 的 Service Worker 程序。

uBOL 在安裝期間不需要氾濫的「讀取與修改資料」權限，因此它出廠時的功能和 uBlock Origin 或其他在安裝期間要求「讀取與修改資料」權限的內容阻擋程式相比，會相對受限。

不過 uBOL 能讓你 **明確地** 在自選的特定網站授予額外的權限，使其在這些網站的過濾效果可以在元素過濾及 scripetlet 注入的加持下得到提升。

若要授予指定網站延伸權限，請開啟對話框並選擇更高的過濾模式，如「最佳化」或「完整」。

瀏覽器接著會警告您授予擴充功能請求的額外權限會帶來的後果，而你需要告訴瀏覽器要同意還是拒絕請求。

如果你接受 uBOL 在目前網站請求的額外權限，其在這個網站的過濾效果將會更好。

您可以在 uBOL 的選項頁面設定預設的過濾模式。 如果您選擇「最佳化」或「完整」為預設的模式，您需要授予 uBOL 讀取與修改所有網站資料的權限。

注意這尚未完工，最終目標有：

- 安裝期間不要有氾濫的 host 權限 —— 以網站為基準讓使用者授予延伸權限。

- 完全宣告式，以提升可靠性和 CPU / 記憶體效率。


import {AccountsTest} from "accounts-base";

Tinytest.add("accounts - parse urls for accounts-password",

var actions = ["reset-password", "verify-email", "enroll-account"];

import {AccountsTest} from "meteor/accounts-base";

import assert from "assert";
import {isString, has, keys, each, without} from "underscore";
import {sha1, readAndWatchFileWithHash} from "../fs/watch.js";
import {matches as archMatches} from "../utils/archinfo.js";
import {findImportedModuleIdentifiers} from "./js-analyze.js";
import {
  pathJoin,
  pathRelative,
  pathNormalize,
  pathDirname,
  pathBasename,
  pathExtname,
  statOrNull,
  convertToPosixPath,
} from "../fs/files.js";

// Default handlers for well-known file extensions.
const extensions = {
  ".js"(data) {
    return data;
  },

  ".json"(data) {
    return "module.exports = " +
      JSON.stringify(JSON.parse(data), null, 2) +
      ";\n";
  }
};

export default class ImportScanner {
  constructor({
    name,
    bundleArch,
    sourceRoot,
    usedPackageNames = {},
    nodeModulesPath,
    watchSet,
  }) {
    assert.ok(isString(sourceRoot));

    this.name = name;
    this.bundleArch = bundleArch;
    this.sourceRoot = sourceRoot;
    this.usedPackageNames = usedPackageNames;
    this.nodeModulesPath = nodeModulesPath;
    this.watchSet = watchSet;
    this.absPathToOutputIndex = {};
    this.outputFiles = [];
  }

  addInputFiles(files) {
    files.forEach(file => {
      const absPath = pathJoin(this.sourceRoot, file.sourcePath);

      // Files that are not eagerly evaluated (lazy) will only be included
      // in the bundle if they are actually imported.
      file.lazy = this._isFileLazy(file);

      // Files that are eagerly evaluated are effectively "imported" as
      // entry points.
      file.imported = ! file.lazy;

      file.installPath = this._getInstallPath(absPath);

      if (has(this.absPathToOutputIndex, absPath)) {
        const index = this.absPathToOutputIndex[absPath];
        this.outputFiles[index] = file;
      } else {
        this.absPathToOutputIndex[absPath] =
          this.outputFiles.push(file) - 1;
      }
    });

    return this;
  }

  getOutputFiles() {
    this.outputFiles.forEach(file => {
      const absPath = pathJoin(this.sourceRoot, file.sourcePath);
      file.deps = this._scanDeps(absPath, file.data);
    });

    return this.outputFiles;
  }

  _isFileLazy(file) {
    if (typeof file.lazy === "boolean") {
      return file.lazy;
    }

    if (file.sourcePath.endsWith(".json")) {
      // JSON files have no side effects, so there is no reason for them
      // ever to be evaluated eagerly.
      return true;
    }

    // If file.lazy was not previously defined, mark the file lazy if it
    // is contained by an imports directory. Note that any files contained
    // by a node_modules directory will already have been marked lazy in
    // PackageSource#_inferFileOptions. The reason we can't do all our
    // lazy marking in the _inferFileOptions method is that we don't know
    // then whether the current app or package is using the modules
    // package. At this point, we know the modules package must be in use,
    // because the ImportScanner is only ever used when modules are used.
    return this._splitPath(
      pathDirname(file.sourcePath)
    ).indexOf("imports") >= 0;
  }

  _scanDeps(absPath, data) {
    const deps = keys(findImportedModuleIdentifiers(data.toString("utf8")));

    each(deps, id => {
      const absImportedPath = this._tryToResolveImportedPath(id, absPath);
      if (! absImportedPath) {
        return;
      }

      if (has(this.absPathToOutputIndex, absImportedPath)) {
        // Avoid scanning files that we've scanned before, but mark them
        // as imported so we know to include them in the bundle if they
        // are lazy.
        const index = this.absPathToOutputIndex[absImportedPath];
        this.outputFiles[index].imported = true;
        return;
      }

      if (! this._hasKnownExtension(absImportedPath)) {
        // The _readModule method provides hardcoded support for files
        // with known extensions, but any other type of file must be
        // ignored at this point, because it was not in the set of input
        // files and therefore must not have been processed by a compiler
        // plugin for the current architecture (this.bundleArch).
        return;
      }

      const installPath = this._getInstallPath(absImportedPath);
      if (! installPath) {
        // The given path cannot be installed on this architecture.
        return;
      }

      var relImportedPath = pathRelative(this.sourceRoot, absImportedPath);

      // The result of _readModule will have .data and .hash properties.
      const depFile = this._readModule(absImportedPath);
      depFile.type = "js"; // TODO Is this correct?
      depFile.sourcePath = relImportedPath;
      depFile.installPath = installPath;
      depFile.servePath = installPath;
      depFile.lazy = true;
      depFile.imported = true;

      // Append this file to the output array and record its index.
      this.absPathToOutputIndex[absImportedPath] =
        this.outputFiles.push(depFile) - 1;

      depFile.deps = this._scanDeps(absImportedPath, depFile.data);
    });

    return deps;
  }

  _readFile(absPath) {
    let { contents, hash } =
      readAndWatchFileWithHash(this.watchSet, absPath);

    return {
      data: contents.toString("utf8"),
      hash,
    };
  }

  _readModule(absPath) {
    const info = this._readFile(absPath);

    // Same logic/comment as stripBOM in node/lib/module.js:
    // Remove byte order marker. This catches EF BB BF (the UTF-8 BOM)
    // because the buffer-to-string conversion in `fs.readFileSync()`
    // translates it to FEFF, the UTF-16 BOM.
    if (info.data.charCodeAt(0) === 0xfeff) {
      info.data = info.data.slice(1);
    }

    const ext = pathExtname(absPath).toLowerCase();
    info.data = extensions[ext](info.data);

    return info;
  }

  // Returns a relative path indicating where to install the given file
  // via meteorInstall. May return undefined if the file should not be
  // installed on the current architecture.
  _getInstallPath(absPath) {
    let path =
      this._getNodeModulesInstallPath(absPath) ||
      this._getSourceRootInstallPath(absPath);

    if (! path) {
      return;
    }

    if (this.name) {
      // If we're bundling a package, prefix path with
      // node_modules/<package name>/.
      path = pathJoin("node_modules", this.name, path);
    } else {
      // If we're bundling an app, prefix path with app/.
      path = pathJoin("app", path);
    }

    return path;
  }

  _getNodeModulesInstallPath(absPath) {
    if (this.nodeModulesPath) {
      const relPathWithinNodeModules =
        pathRelative(this.nodeModulesPath, absPath);

      if (relPathWithinNodeModules.startsWith("..")) {
        // absPath is not a subdirectory of this.nodeModulesPath.
        return;
      }

      if (! this._hasKnownExtension(relPathWithinNodeModules)) {
        // Only accept files within node_modules directories if they
        // have one of the known extensions.
        return;
      }

      // Install the module into the local node_modules directory within
      // this app or package.
      return pathJoin("node_modules", relPathWithinNodeModules);
    }
  }

  _getSourceRootInstallPath(absPath) {
    const installPath = pathRelative(this.sourceRoot, absPath);

    if (installPath.startsWith("..")) {
      // absPath is not a subdirectory of this.sourceRoot.
      return;
    }

    const dirs = this._splitPath(pathDirname(installPath));
    const bundlingClientApp =
      ! this.name && // Indicates we are bundling an app.
      archMatches(this.bundleArch, "web");

    for (let dir of dirs) {
      if (dir.charAt(0) === "." ||
          dir === "packages" ||
          dir === "programs" ||
          dir === "cordova-build-override") {
        // These directories are never loaded as part of an app.
        return;
      }

      if (bundlingClientApp && (dir === "server" ||
                                dir === "private")) {
        // If we're bundling an app for a client architecture, any files
        // contained by a server-only directory that is not contained by
        // a node_modules directory must be ignored.
        return;
      }

      if (dir === "node_modules") {
        if (! this._hasKnownExtension(installPath)) {
          // Reject any files within node_modules directories that do
          // not have one of the known extensions.
          return;
        }

        // Accept any file within a node_modules directory if it has a
        // known file extension.
        return installPath;
      }
    }

    return installPath;
  }

  _hasKnownExtension(path) {
    return has(extensions, pathExtname(path).toLowerCase());
  }

  _splitPath(path) {
    const partsInReverse = [];
    for (let dir; (dir = pathDirname(path)) !== path; path = dir) {
      partsInReverse.push(pathBasename(path));
    }
    return partsInReverse.reverse();
  }

  _tryToResolveImportedPath(id, path) {
    let resolved =
      this._resolveAbsolute(id) ||
      this._resolveRelative(id, path) ||
      this._resolveNodeModule(id, path);

    while (resolved && resolved.stat.isDirectory()) {
      resolved = this._resolvePkgJsonMain(resolved.path) ||
        this._joinAndStat(resolved.path, "index.js");
    }

    return resolved && resolved.path;
  }

  _joinAndStat(...joinArgs) {
    const path = pathNormalize(pathJoin(...joinArgs));
    const exactStat = statOrNull(path);
    const exactResult = exactStat && { path, stat: exactStat };
    if (exactResult && exactStat.isFile()) {
      return exactResult;
    }

    for (let ext in extensions) {
      if (has(extensions, ext)) {
        const pathWithExt = path + ext;
        const stat = statOrNull(pathWithExt);
        if (stat) {
          return { path: pathWithExt, stat };
        }
      }
    }

    if (exactResult && exactStat.isDirectory()) {
      // After trying all available file extensions, fall back to the
      // original result if it was a directory.
      return exactResult;
    }

    return null;
  }

  _resolveAbsolute(id) {
    return id.charAt(0) === "/" &&
      this._joinAndStat(this.sourceRoot, id.slice(1));
  }

  _resolveRelative(id, path) {
    return id.charAt(0) === "." &&
      this._joinAndStat(path, "..", id);
  }

  _resolveNodeModule(id, path) {
    let resolved = null;
    let dir = path;

    do {
      dir = pathDirname(dir);
      resolved = this._joinAndStat(dir, "node_modules", id);
    } while (! resolved && dir !== this.sourceRoot);

    if (! resolved && this.nodeModulesPath) {
      // After checking any local node_modules directories, fall back to
      // the package NPM directory, if one was specified.
      resolved = this._joinAndStat(this.nodeModulesPath, id);
    }

    if (! resolved) {
      // If the package is still not resolved, and it appears to refer to
      // a Meteor package, create a stub for that package that just sets
      // module.exports = Package[packageName].
      const packageName = this._getMeteorPackageNameFromId(id);
      if (packageName) {
        this._addMeteorPackageStubToOutput(packageName);
      }
    }

    return resolved;
  }

  _getMeteorPackageNameFromId(id) {
    const possiblePackageName = id.split("/", 1)[0];
    if (has(this.usedPackageNames, possiblePackageName)) {
      return possiblePackageName;
    }
  }

  _resolvePkgJsonMain(dirPath) {
    const pkgJsonPath = pathJoin(dirPath, "package.json");

    let pkg;
    try {
      pkg = JSON.parse(this._readFile(pkgJsonPath).data);
    } catch (e) {
      return null;
    }

    if (pkg && isString(pkg.main)) {
      const resolved = this._joinAndStat(dirPath, pkg.main);
      if (resolved) {
        this._addPkgJsonToOutput(pkgJsonPath, pkg);
        return resolved;
      }
    }

    return null;
  }

  _addPkgJsonToOutput(pkgJsonPath, pkg) {
    if (! has(this.absPathToOutputIndex, pkgJsonPath)) {
      const data = new Buffer(
        // Output a JS module that exports just the "name" and "main"
        // properties defined in the package.json file.
        "exports.name = " + JSON.stringify(pkg.name) + ";\n" +
        "exports.main = " + JSON.stringify(pkg.main) + ";\n"
      );

      const relPkgJsonPath = pathRelative(this.sourceRoot, pkgJsonPath);

      const pkgFile = {
        type: "js", // We represent the JSON module with JS.
        data,
        deps: [], // Avoid accidentally re-scanning this file.
        sourcePath: relPkgJsonPath,
        installPath: this._getInstallPath(pkgJsonPath),
        servePath: relPkgJsonPath,
        hash: sha1(data),
        lazy: true,
        imported: true,
      };

      this.absPathToOutputIndex[pkgJsonPath] =
        this.outputFiles.push(pkgFile) - 1;
    }
  }

  // Adds a <packageName>.js module file to the top-level node_modules
  // directory. This module just exports the Package[packageName] object.
  // If the package installs any modules when we link it, those modules
  // will end up in a directory called node_modules/<packageName>/, which
  // is importantly distinct from node_modules/<packageName>.js. Though
  // Node allows .js module files as direct children of node_modules
  // directories, npm never takes advantage of this possibility, which
  // conveniently allows Meteor to install files there without conflict.
  _addMeteorPackageStubToOutput(packageName) {
    const relPkgPath = pathJoin("node_modules", packageName + ".js");
    const absPkgPath = pathJoin(this.sourceRoot, relPkgPath);

    // Note that this absPkgPath need not actually exist on disk!
    if (! has(this.absPathToOutputIndex, absPkgPath)) {
      const data = new Buffer(
        "module.exports = Package[" +
          JSON.stringify(packageName) +
        "];\n"
      );

      const stubFile = {
        type: "js",
        data,
        deps: [], // Avoid accidentally re-scanning this file.
        sourcePath: relPkgPath,
        installPath: relPkgPath,
        servePath: relPkgPath,
        lazy: true,
        imported: true,
      };

      this.absPathToOutputIndex[absPkgPath] =
        this.outputFiles.push(stubFile) - 1;
    }
  }
}


.overlay.auth {
  .btn-twitter { .position(absolute, auto, 0, 0, 0, 100%); }
}


Package.describe({
  summary: "Run tests interactively in the browser",
  version: '1.0.9-modules.2',
  documentation: null
});

Package.onUse(function (api) {
  // XXX this should go away, and there should be a clean interface
  // that tinytest and the driver both implement?
  api.use('tinytest');
  api.use('bootstrap@1.0.1');
  api.use('underscore');

  api.use('session');
  api.use('reload');

  api.use(['webapp', 'blaze', 'templating', 'spacebars',
           'ddp', 'tracker'], 'client');

  api.addFiles('diff_match_patch_uncompressed.js', 'client');

  api.addFiles([
    'driver.css',
    'driver.html',
    'driver.js'
  ], "client");

  api.use('autoupdate', 'server', {weak: true});
  api.use('random', 'server');
  api.addFiles('autoupdate.js', 'server');
});


// If we're bundling a package, prefix path with

path = pathJoin("node_modules", this.name, path);

// If we're bundling an app, prefix path with app/.

keep keep keep keep replace replace replace replace keep keep keep keep keep

path = pathJoin("node_modules", "meteor", this.name, path);


Activities = new Mongo.Collection('activities');

Activities.allow({
  insert: function(userId, doc) {
    return doc.userId === userId;
  }
});

Activities.latest = function() {
  return Activities.find({}, {sort: {date: -1}, limit: 1});
}

Meteor.methods({
  createActivity: function(activity, tweet, loc) {
    check(Meteor.userId(), String);
    check(activity, {
      recipeName: String,
      text: String,
      image: String
    });
    check(tweet, Boolean);
    check(loc, Match.OneOf(Object, null));

    activity.userId = Meteor.userId();
    activity.userAvatar = Meteor.user().services.twitter.profile_image_url_https;
    activity.userName = Meteor.user().profile.name;
    activity.date = new Date;

    if (! this.isSimulation && loc)
      activity.place = getLocationPlace(loc);

    var id = Activities.insert(activity);

    if (! this.isSimulation && tweet)
      tweetActivity(activity);

    return id;
  }
});

if (Meteor.isServer) {
  var twitterOauth = function(options) {
    var config = Meteor.settings.twitter
    var userConfig = Meteor.user().services.twitter;

    return {
      consumer_key: config.consumerKey,
      consumer_secret: config.secret,
      token: userConfig.accessToken,
      token_secret: userConfig.accessTokenSecret
    };
  }

  var tweetActivity = function(activity) {
    // creates the tweet text, optionally truncating to fit the appended text
    function appendTweet(text, append) {
      var MAX = 117; // Max size of tweet with image attached

      if ((text + append).length > MAX)
        return text.substring(0, (MAX - append.length - 3)) + '...' + append;
      else
        return text + append;
    }

    // we need to strip the "data:image/jpeg;base64," bit off the data url
    var image = activity.image.replace(/^data.*base64,/, '');

    var response = HTTP.post(
      'https://upload.twitter.com/1.1/media/upload.json', {
        params: { media: image },
        npmRequestOptions: { oauth: twitterOauth() }
      }
    );

    if (response.statusCode !== 200)
      throw new Meteor.Error(500, 'Unable to post image to twitter');

    if (! response.data)
      throw new Meteor.Error(500, 'Did not receive attachment from twitter');

    var attachment = response.data;

    response = HTTP.post(
      'https://api.twitter.com/1.1/statuses/update.json', {
        params: {
          status: appendTweet(activity.text, ' #localmarket'),
          media_ids: attachment.media_id_string
        },
        npmRequestOptions: { oauth: twitterOauth() }
      }
    );

    if (response.statusCode !== 200)
      throw new Meteor.Error(500, 'Unable to create tweet');
  }

  var getLocationPlace = function(loc) {
    var url = 'https://api.twitter.com/1.1/geo/reverse_geocode.json'
      + '?granularity=neighborhood'
      + '&max_results=1'
      + '&accuracy=' + loc.coords.accuracy
      + '&lat=' + loc.coords.latitude
      + '&long=' + loc.coords.longitude;

    var response = HTTP.get(url,
                            {npmRequestOptions: { oauth: twitterOauth() } });

    if (response.statusCode === 200 && response.data) {
      var place = _.find(response.data.result.places, function(place) {
        return place.place_type === 'neighborhood';
      });

      return place && place.full_name;
    }
  }
}

// Initialize a seed activity
Meteor.startup(function() {
  if (Meteor.isServer && Activities.find().count() === 0) {
    Activities.insert({
      recipeName: 'summer-apricots-honey-panna-cotta',
      text: 'I substituted strawberries for apricots - incredible!',
      image: '/img/activity/activity-placeholder-strawberry-640x640.jpg',
      userAvatar: 'https://avatars3.githubusercontent.com/u/204768?v=2&s=400',
      userName: 'Matt Debergalis',
      place: 'SoMA, San Francisco',
      date: new Date
    });
  }
});


//-------------------------------------------------------------------------------------------------
// <copyright file="BalBaseBootstrapperApplication.h" company="Outercurve Foundation">
//   Copyright (c) 2004, Outercurve Foundation.
//   This software is released under Microsoft Reciprocal License (MS-RL).
//   The license and further copyright text can be found in the file
//   LICENSE.TXT at the root directory of the distribution.
// </copyright>
//-------------------------------------------------------------------------------------------------

#include <windows.h>
#include <msiquery.h>

#include "IBootstrapperEngine.h"
#include "IBootstrapperApplication.h"

#include "balutil.h"
#include "balretry.h"

class CBalBaseBootstrapperApplication : public IBootstrapperApplication
{
public: // IUnknown
    virtual STDMETHODIMP QueryInterface(
        __in REFIID riid,
        __out LPVOID *ppvObject
        )
    {
        if (!ppvObject)
        {
            return E_INVALIDARG;
        }

        *ppvObject = NULL;

        if (::IsEqualIID(__uuidof(IBootstrapperApplication), riid))
        {
            *ppvObject = static_cast<IBootstrapperApplication*>(this);
        }
        else if (::IsEqualIID(IID_IUnknown, riid))
        {
            *ppvObject = static_cast<IUnknown*>(this);
        }
        else // no interface for requested iid
        {
            return E_NOINTERFACE;
        }

        AddRef();
        return S_OK;
    }

    virtual STDMETHODIMP_(ULONG) AddRef()
    {
        return ::InterlockedIncrement(&this->m_cReferences);
    }

    virtual STDMETHODIMP_(ULONG) Release()
    {
        long l = ::InterlockedDecrement(&this->m_cReferences);
        if (0 < l)
        {
            return l;
        }

        delete this;
        return 0;
    }

public: // IBurnUserExperience
    virtual STDMETHODIMP OnStartup()
    {
        return S_OK;
    }

    virtual STDMETHODIMP_(int) OnShutdown()
    {
        return IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnSystemShutdown(
        __in DWORD dwEndSession,
        __in int /*nRecommendation*/
        )
    {
        // Allow requests to shut down when critical or not applying.
        if (ENDSESSION_CRITICAL & dwEndSession || !m_fApplying)
        {
            return IDOK;
        }

        return IDCANCEL;
    }

    virtual STDMETHODIMP_(int) OnDetectBegin(
        __in BOOL /*fInstalled*/,
        __in DWORD /*cPackages*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnDetectForwardCompatibleBundle(
        __in_z LPCWSTR /*wzBundleId*/,
        __in BOOTSTRAPPER_RELATION_TYPE /*relationType*/,
        __in_z LPCWSTR /*wzBundleTag*/,
        __in BOOL /*fPerMachine*/,
        __in DWORD64 /*dw64Version*/,
        __in int nRecommendation
        )
    {
        return CheckCanceled() ? IDCANCEL : nRecommendation;
    }

    virtual STDMETHODIMP_(int) OnDetectUpdateBegin(
        __in_z LPCWSTR /*wzUpdateLocation*/,
        __in int nRecommendation
        )
    {
        return CheckCanceled() ? IDCANCEL : nRecommendation;
    }

    virtual STDMETHODIMP_(void) OnDetectUpdateComplete(
        __in HRESULT /*hrStatus*/,
        __in_z_opt LPCWSTR /*wzUpdateLocation*/
        )
    {
    }

    virtual STDMETHODIMP_(int) OnDetectPriorBundle(
        __in_z LPCWSTR /*wzBundleId*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnDetectPackageBegin(
        __in_z LPCWSTR /*wzPackageId*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnDetectRelatedBundle(
        __in_z LPCWSTR /*wzBundleId*/,
        __in BOOTSTRAPPER_RELATION_TYPE /*relationType*/,
        __in_z LPCWSTR /*wzBundleTag*/,
        __in BOOL /*fPerMachine*/,
        __in DWORD64 /*dw64Version*/,
        __in BOOTSTRAPPER_RELATED_OPERATION /*operation*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnDetectRelatedMsiPackage(
        __in_z LPCWSTR /*wzPackageId*/,
        __in_z LPCWSTR /*wzProductCode*/,
        __in BOOL /*fPerMachine*/,
        __in DWORD64 /*dw64Version*/,
        __in BOOTSTRAPPER_RELATED_OPERATION /*operation*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnDetectTargetMsiPackage(
        __in_z LPCWSTR /*wzPackageId*/,
        __in_z LPCWSTR /*wzProductCode*/,
        __in BOOTSTRAPPER_PACKAGE_STATE /*patchState*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnDetectMsiFeature(
        __in_z LPCWSTR /*wzPackageId*/,
        __in_z LPCWSTR /*wzFeatureId*/,
        __in BOOTSTRAPPER_FEATURE_STATE /*state*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(void) OnDetectPackageComplete(
        __in_z LPCWSTR /*wzPackageId*/,
        __in HRESULT /*hrStatus*/,
        __in BOOTSTRAPPER_PACKAGE_STATE /*state*/
        )
    {
    }

    virtual STDMETHODIMP_(void) OnDetectComplete(
        __in HRESULT /*hrStatus*/
        )
    {
    }

    virtual STDMETHODIMP_(int) OnPlanBegin(
        __in DWORD /*cPackages*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnPlanRelatedBundle(
        __in_z LPCWSTR /*wzBundleId*/,
        __inout BOOTSTRAPPER_REQUEST_STATE* /*pRequestedState*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnPlanPackageBegin(
        __in_z LPCWSTR /*wzPackageId*/,
        __inout BOOTSTRAPPER_REQUEST_STATE* /*pRequestState*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnPlanTargetMsiPackage(
        __in_z LPCWSTR /*wzPackageId*/,
        __in_z LPCWSTR /*wzProductCode*/,
        __inout BOOTSTRAPPER_REQUEST_STATE* /*pRequestedState*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnPlanMsiFeature(
        __in_z LPCWSTR /*wzPackageId*/,
        __in_z LPCWSTR /*wzFeatureId*/,
        __inout BOOTSTRAPPER_FEATURE_STATE* /*pRequestedState*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(void) OnPlanPackageComplete(
        __in_z LPCWSTR /*wzPackageId*/,
        __in HRESULT /*hrStatus*/,
        __in BOOTSTRAPPER_PACKAGE_STATE /*state*/,
        __in BOOTSTRAPPER_REQUEST_STATE /*requested*/,
        __in BOOTSTRAPPER_ACTION_STATE /*execute*/,
        __in BOOTSTRAPPER_ACTION_STATE /*rollback*/
        )
    {
    }

    virtual STDMETHODIMP_(void) OnPlanComplete(
        __in HRESULT /*hrStatus*/
        )
    {
    }

    virtual STDMETHODIMP_(int) OnApplyBegin()
    {
        m_fApplying = TRUE;

        m_dwProgressPercentage = 0;
        m_dwOverallProgressPercentage = 0;

        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnElevate()
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnRegisterBegin()
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(void) OnRegisterComplete(
        __in HRESULT /*hrStatus*/
        )
    {
        return;
    }

    virtual STDMETHODIMP_(void) OnUnregisterBegin()
    {
        return;
    }

    virtual STDMETHODIMP_(void) OnUnregisterComplete(
        __in HRESULT /*hrStatus*/
        )
    {
        return;
    }

    virtual STDMETHODIMP_(int) OnApplyComplete(
        __in HRESULT /*hrStatus*/,
        __in BOOTSTRAPPER_APPLY_RESTART restart
        )
    {
        m_fApplying = FALSE;
        return BOOTSTRAPPER_APPLY_RESTART_REQUIRED == restart ? IDRESTART : CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnCacheBegin()
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnCachePackageBegin(
        __in_z LPCWSTR /*wzPackageId*/,
        __in DWORD /*cCachePayloads*/,
        __in DWORD64 /*dw64PackageCacheSize*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnCacheAcquireBegin(
        __in_z LPCWSTR wzPackageOrContainerId,
        __in_z_opt LPCWSTR wzPayloadId,
        __in BOOTSTRAPPER_CACHE_OPERATION /*operation*/,
        __in_z LPCWSTR /*wzSource*/
        )
    {
        BalRetryStartPackage(BALRETRY_TYPE_CACHE, wzPackageOrContainerId, wzPayloadId);
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnCacheAcquireProgress(
        __in_z LPCWSTR /*wzPackageOrContainerId*/,
        __in_z_opt LPCWSTR /*wzPayloadId*/,
        __in DWORD64 /*dw64Progress*/,
        __in DWORD64 /*dw64Total*/,
        __in DWORD /*dwOverallPercentage*/
        )
    {
        HRESULT hr = S_OK;
        int nResult = IDNOACTION;

        // Send progress even though we don't update the numbers to at least give the caller an opportunity
        // to cancel.
        if (BOOTSTRAPPER_DISPLAY_EMBEDDED == m_display)
        {
            hr = m_pEngine->SendEmbeddedProgress(m_dwProgressPercentage, m_dwOverallProgressPercentage, &nResult);
            BalExitOnFailure(hr, "Failed to send embedded cache progress.");
        }

    LExit:
        return FAILED(hr) ? IDERROR : CheckCanceled() ? IDCANCEL : nResult;
    }

    virtual STDMETHODIMP_(int) OnCacheAcquireComplete(
        __in_z LPCWSTR wzPackageOrContainerId,
        __in_z_opt LPCWSTR wzPayloadId,
        __in HRESULT hrStatus,
        __in int nRecommendation
        )
    {
        int nResult = CheckCanceled() ? IDCANCEL : BalRetryEndPackage(BALRETRY_TYPE_CACHE, wzPackageOrContainerId, wzPayloadId, hrStatus);
        return IDNOACTION == nResult ? nRecommendation : nResult;
    }

    virtual STDMETHODIMP_(int) OnCacheVerifyBegin(
        __in_z LPCWSTR /*wzPackageId*/,
        __in_z LPCWSTR /*wzPayloadId*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnCacheVerifyComplete(
        __in_z LPCWSTR /*wzPackageId*/,
        __in_z LPCWSTR /*wzPayloadId*/,
        __in HRESULT /*hrStatus*/,
        __in int nRecommendation
        )
    {
        return CheckCanceled() ? IDCANCEL : nRecommendation;
    }

    virtual STDMETHODIMP_(int) OnCachePackageComplete(
        __in_z LPCWSTR /*wzPackageId*/,
        __in HRESULT /*hrStatus*/,
        __in int nRecommendation
        )
    {
        return CheckCanceled() ? IDCANCEL : nRecommendation;
    }

    virtual STDMETHODIMP_(void) OnCacheComplete(
        __in HRESULT /*hrStatus*/
        )
    {
    }

    virtual STDMETHODIMP_(int) OnExecuteBegin(
        __in DWORD /*cExecutingPackages*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnExecutePackageBegin(
        __in_z LPCWSTR wzPackageId,
        __in BOOL fExecute
        )
    {
        // Only track retry on execution (not rollback).
        if (fExecute)
        {
            BalRetryStartPackage(BALRETRY_TYPE_EXECUTE, wzPackageId, NULL);
        }

        m_fRollingBack = !fExecute;
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnExecutePatchTarget(
        __in_z LPCWSTR /*wzPackageId*/,
        __in_z LPCWSTR /*wzTargetProductCode*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnError(
        __in BOOTSTRAPPER_ERROR_TYPE errorType,
        __in_z LPCWSTR wzPackageId,
        __in DWORD dwCode,
        __in_z LPCWSTR /*wzError*/,
        __in DWORD /*dwUIHint*/,
        __in DWORD /*cData*/,
        __in_ecount_z_opt(cData) LPCWSTR* /*rgwzData*/,
        __in int nRecommendation
        )
    {
        BalRetryErrorOccurred(wzPackageId, dwCode);

        if (BOOTSTRAPPER_DISPLAY_FULL == m_display)
        {
            if (BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_SERVER == errorType ||BOOTSTRAPPER_ERROR_TYPE_HTTP_AUTH_PROXY == errorType)
            {
                nRecommendation = IDTRYAGAIN;
            }
        }

        return CheckCanceled() ? IDCANCEL : nRecommendation;
    }

    virtual STDMETHODIMP_(int) OnProgress(
        __in DWORD dwProgressPercentage,
        __in DWORD dwOverallProgressPercentage
        )
    {
        HRESULT hr = S_OK;
        int nResult = IDNOACTION;

        m_dwProgressPercentage = dwProgressPercentage;
        m_dwOverallProgressPercentage = dwOverallProgressPercentage;

        if (BOOTSTRAPPER_DISPLAY_EMBEDDED == m_display)
        {
            hr = m_pEngine->SendEmbeddedProgress(m_dwProgressPercentage, m_dwOverallProgressPercentage, &nResult);
            BalExitOnFailure(hr, "Failed to send embedded overall progress.");
        }

    LExit:
        return FAILED(hr) ? IDERROR : CheckCanceled() ? IDCANCEL : nResult;
    }

    virtual STDMETHODIMP_(int) OnDownloadPayloadBegin(
        __in_z LPCWSTR /*wzPayloadId*/,
        __in_z LPCWSTR /*wzPayloadFileName*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnDownloadPayloadComplete(
        __in_z LPCWSTR /*wzPayloadId*/,
        __in_z LPCWSTR /*wzPayloadFileName*/,
        __in HRESULT /*hrStatus*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnExecuteProgress(
        __in_z LPCWSTR /*wzPackageId*/,
        __in DWORD /*dwProgressPercentage*/,
        __in DWORD /*dwOverallProgressPercentage*/
        )
    {
        HRESULT hr = S_OK;
        int nResult = IDNOACTION;

        // Send progress even though we don't update the numbers to at least give the caller an opportunity
        // to cancel.
        if (BOOTSTRAPPER_DISPLAY_EMBEDDED == m_display)
        {
            hr = m_pEngine->SendEmbeddedProgress(m_dwProgressPercentage, m_dwOverallProgressPercentage, &nResult);
            BalExitOnFailure(hr, "Failed to send embedded execute progress.");
        }

    LExit:
        return FAILED(hr) ? IDERROR : CheckCanceled() ? IDCANCEL : nResult;
    }

    virtual STDMETHODIMP_(int) OnExecuteMsiMessage(
        __in_z LPCWSTR /*wzPackageId*/,
        __in INSTALLMESSAGE /*mt*/,
        __in UINT /*uiFlags*/,
        __in_z LPCWSTR /*wzMessage*/,
        __in DWORD /*cData*/,
        __in_ecount_z_opt(cData) LPCWSTR* /*rgwzData*/,
        __in int nRecommendation
        )
    {
        return CheckCanceled() ? IDCANCEL : nRecommendation;
    }

    virtual STDMETHODIMP_(int) OnExecuteFilesInUse(
        __in_z LPCWSTR /*wzPackageId*/,
        __in DWORD /*cFiles*/,
        __in_ecount_z(cFiles) LPCWSTR* /*rgwzFiles*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

    virtual STDMETHODIMP_(int) OnExecutePackageComplete(
        __in_z LPCWSTR wzPackageId,
        __in HRESULT hrExitCode,
        __in BOOTSTRAPPER_APPLY_RESTART /*restart*/,
        __in int nRecommendation
        )
    {
        int nResult = CheckCanceled() ? IDCANCEL : CheckCanceled() ? IDCANCEL : BalRetryEndPackage(BALRETRY_TYPE_EXECUTE, wzPackageId, NULL, hrExitCode);
        return IDNOACTION == nResult ? nRecommendation : nResult;
    }

    virtual STDMETHODIMP_(void) OnExecuteComplete(
        __in HRESULT /*hrStatus*/
        )
    {
    }

    virtual STDMETHODIMP_(int) OnResolveSource(
        __in_z LPCWSTR /*wzPackageOrContainerId*/,
        __in_z_opt LPCWSTR /*wzPayloadId*/,
        __in_z LPCWSTR /*wzLocalSource*/,
        __in_z_opt LPCWSTR /*wzDownloadSource*/
        )
    {
        return CheckCanceled() ? IDCANCEL : IDNOACTION;
    }

protected:
    //
    // PromptCancel - prompts the user to close (if not forced).
    //
    virtual BOOL PromptCancel(
        __in HWND hWnd,
        __in BOOL fForceCancel,
        __in_z LPCWSTR wzMessage,
        __in_z LPCWSTR wzCaption
        )
    {
        ::EnterCriticalSection(&m_csCanceled);

        // Only prompt the user to close if we have not canceled already.
        if (!m_fCanceled)
        {
            if (fForceCancel)
            {
                m_fCanceled = TRUE;
            }
            else
            {
                m_fCanceled = (IDYES == ::MessageBoxW(hWnd, wzMessage, wzCaption, MB_YESNO | MB_ICONEXCLAMATION));
            }
        }

        ::LeaveCriticalSection(&m_csCanceled);

        return m_fCanceled;
    }

    //
    // CheckCanceled - waits if the cancel dialog is up and checks to see if the user canceled the operation.
    //
    BOOL CheckCanceled()
    {
        ::EnterCriticalSection(&m_csCanceled);
        ::LeaveCriticalSection(&m_csCanceled);
        return m_fRollingBack ? FALSE : m_fCanceled;
    }

    BOOL IsRollingBack()
    {
        return m_fRollingBack;
    }

    BOOL IsCanceled()
    {
        return m_fCanceled;
    }

    CBalBaseBootstrapperApplication(
        __in IBootstrapperEngine* pEngine,
        __in const BOOTSTRAPPER_COMMAND* pCommand,
        __in DWORD dwRetryCount = 0,
        __in DWORD dwRetryTimeout = 1000
        )
    {
        m_cReferences = 1;
        m_display = pCommand->display;
        m_restart = pCommand->restart;

        pEngine->AddRef();
        m_pEngine = pEngine;

        ::InitializeCriticalSection(&m_csCanceled);
        m_fCanceled = FALSE;
        m_fApplying = FALSE;
        m_fRollingBack = FALSE;

        BalRetryInitialize(dwRetryCount, dwRetryTimeout);
    }

    virtual ~CBalBaseBootstrapperApplication()
    {
        BalRetryUninitialize();
        ::DeleteCriticalSection(&m_csCanceled);

        ReleaseNullObject(m_pEngine);
    }

private:
    long m_cReferences;
    BOOTSTRAPPER_DISPLAY m_display;
    BOOTSTRAPPER_RESTART m_restart;
    IBootstrapperEngine* m_pEngine;

    CRITICAL_SECTION m_csCanceled;
    BOOL m_fCanceled;
    BOOL m_fApplying;
    BOOL m_fRollingBack;

    DWORD m_dwProgressPercentage;
    DWORD m_dwOverallProgressPercentage;
};


const possiblePackageName = id.split("/", 1)[0];

if (has(this.usedPackageNames, possiblePackageName)) {

keep keep keep replace replace replace keep keep keep

const [meteor, packageName] = id.split("/", 2);


Tinytest.add("coffeescript - presence", function(test) {
  test.isTrue(Meteor.__COFFEESCRIPT_PRESENT);
});
Tinytest.add("literate coffeescript - presence", function(test) {
  test.isTrue(Meteor.__LITCOFFEESCRIPT_PRESENT);
  test.isTrue(Meteor.__COFFEEMDSCRIPT_PRESENT);
});

Tinytest.add("coffeescript - exported variable", function(test) {
  test.equal(COFFEESCRIPT_EXPORTED, 123);
  test.equal(Package['coffeescript-test-helper'].COFFEESCRIPT_EXPORTED, 123);
});


var _ = require('underscore');
var selftest = require('../tool-testing/selftest.js');
var files = require('../fs/files.js');
import { getUrl } from '../utils/http-helpers.js';
import { sleepMs } from '../utils/utils.js';

var Sandbox = selftest.Sandbox;

var MONGO_LISTENING =
  { stdout: " [initandlisten] waiting for connections on port" };

function startRun(sandbox) {
  var run = sandbox.run();
  run.match("myapp");
  run.match("proxy");
  run.tellMongo(MONGO_LISTENING);
  run.match("MongoDB");
  return run;
};

// Tests the actual cache logic used by coffeescript.
selftest.define("compiler plugin caching - coffee", () => {
  var s = new Sandbox({ fakeMongo: true });

  s.createApp("myapp", "caching-coffee");
  s.cd("myapp");
  // Ask them to print out when they build a file (instead of using it from the
  // cache) as well as when they load cache from disk.
  s.set('METEOR_COFFEESCRIPT_CACHE_DEBUG', 't');
  var run = startRun(s);

  // First program built (server or web.browser) compiles everything.
  run.match('CACHE(coffeescript): Ran (#1) on: ' + JSON.stringify(
    ['/f1.coffee', '/f2.coffee', '/f3.coffee', '/packages/local-pack/p.coffee']
  ));
  // Second program doesn't need to compile anything because compilation works
  // the same on both programs.
  run.match("CACHE(coffeescript): Ran (#2) on: []");
  // App prints this:
  run.match("Coffeescript X is 2 Y is 1 FromPackage is 4");

  s.write("f2.coffee", "share.Y = 'Y is 3'\n");
  // Only recompiles f2.
  run.match('CACHE(coffeescript): Ran (#3) on: ["/f2.coffee"]');
  // And other program doesn't even need to do f2.
  run.match("CACHE(coffeescript): Ran (#4) on: []");
  // Program prints this:
  run.match("Coffeescript X is 2 Y is 3 FromPackage is 4");

  // Force a rebuild of the local package without actually changing the
  // coffeescript file in it. This should not require us to coffee.compile
  // anything (for either program).
  s.append("packages/local-pack/package.js", "\n// foo\n");
  run.match("CACHE(coffeescript): Ran (#5) on: []");
  run.match("CACHE(coffeescript): Ran (#6) on: []");
  run.match("Coffeescript X is 2 Y is 3 FromPackage is 4");

  // But writing to the actual source file in the local package should
  // recompile.
  s.write("packages/local-pack/p.coffee", "FromPackage = 'FromPackage is 5'");
  run.match('CACHE(coffeescript): Ran (#7) on: ["/packages/local-pack/p.coffee"]');
  run.match('CACHE(coffeescript): Ran (#8) on: []');
  run.match("Coffeescript X is 2 Y is 3 FromPackage is 5");

  // We never should have loaded cache from disk, since we only made
  // each compiler once and there were no cache files at this point.
  run.forbid('CACHE(coffeescript): Loaded');

  // Kill the run. Change one coffee file and re-run.
  run.stop();
  s.write("f2.coffee", "share.Y = 'Y is edited'\n");
  run = startRun(s);

  // This time there's a cache to load!
  run.match('CACHE(coffeescript): Loaded /packages/local-pack/p.coffee');
  run.match('CACHE(coffeescript): Loaded /f1.coffee');
  run.match('CACHE(coffeescript): Loaded /f3.coffee');
  // And we only need to re-compiler the changed file, even though we restarted.
  run.match('CACHE(coffeescript): Ran (#1) on: ["/f2.coffee"]');
  run.match('CACHE(coffeescript): Ran (#2) on: []');

  run.match('Coffeescript X is 2 Y is edited FromPackage is 5');

  run.stop();
});

// Tests the actual cache logic used by less and stylus.
['less', 'stylus'].forEach((packageName) => {
  const extension = packageName === 'stylus' ? 'styl' : packageName;

  selftest.define("compiler plugin caching - " + packageName, () => {
    var s = new Sandbox({ fakeMongo: true });

    s.createApp("myapp", "caching-" + packageName);
    s.cd("myapp");
    // Ask them to print out when they build a file (instead of using it from
    // the cache) as well as when they load cache from disk.
    s.set(`METEOR_${ packageName.toUpperCase() }_CACHE_DEBUG`, "t");
    var run = startRun(s);

    const cacheMatch = selftest.markStack((message) => {
      run.match(`CACHE(${ packageName }): ${ message }`);
    });

    // First program built (web.browser) compiles everything.
    cacheMatch('Ran (#1) on: ' + JSON.stringify(
      ["/subdir/nested-root." + extension, "/top." + extension]));
    // There is no render execution in the server program, because it has
    // archMatching:'web'.  We'll see this more clearly when the next call later
    // is "#2" --- we didn't miss a call!
    // App prints this:
    run.match("Hello world");

    // Check that the CSS is what we expect.
    var checkCSS = selftest.markStack((borderStyleMap) => {
      var builtBrowserProgramDir = files.pathJoin(
        s.cwd, '.meteor', 'local', 'build', 'programs', 'web.browser');
      var cssFile = _.find(
        files.readdir(
          files.pathJoin(s.cwd, '.meteor/local/build/programs/web.browser')),
        path => path.match(/\.css$/)
      );
      selftest.expectTrue(cssFile);
      var actual = s.read(
        files.pathJoin('.meteor/local/build/programs/web.browser', cssFile));
      actual = actual.replace(/\s+/g, ' ');  // simplify whitespace
      var expected = _.map(borderStyleMap, (style, className) => {
        return '.' + className + " { border-style: " + style + "; }";
      }).join(' ');
      selftest.expectEqual(actual, expected);
    });
    var expectedBorderStyles = {
      el0: "dashed", el1: "dotted", el2: "solid", el3: "groove", el4: "ridge"};
    checkCSS(expectedBorderStyles);

    // Force a rebuild of the local package without actually changing the
    // preprocessor file in it. This should not require us to render anything.
    s.append("packages/local-pack/package.js", "\n// foo\n");
    cacheMatch('Ran (#2) on: []');
    run.match("Hello world");

    function setVariable(variableName, value) {
      switch (packageName) {
      case 'less':
        return `@${ variableName }: ${ value };\n`;
      case 'stylus':
        return `$${ variableName } = ${ value }\n`;
      }
    }
    function importLine(fileWithoutExtension) {
      switch (packageName) {
      case 'less':
        return `@import "${ fileWithoutExtension }.less";\n`;
      case 'stylus':
        return `@import "${ fileWithoutExtension }.styl"\n`;
      }
    }

    // Writing to a single file only re-renders the root that depends on it.
    s.write('packages/local-pack/p.' + extension,
            setVariable('el4-style', 'inset'));
    expectedBorderStyles.el4 = 'inset';
    cacheMatch(`Ran (#3) on: ["/top.${ extension }"]`);
    run.match("Client modified -- refreshing");
    checkCSS(expectedBorderStyles);

    // This works for changing a root too.
    s.write('subdir/nested-root.' + extension,
            '.el0 { border-style: double; }\n');
    expectedBorderStyles.el0 = 'double';
    cacheMatch(`Ran (#4) on: ["/subdir/nested-root.${ extension }"]`);
    run.match("Client modified -- refreshing");
    checkCSS(expectedBorderStyles);

    // Adding a new root works too.
    s.write('yet-another-root.' + extension,
            '.el6 { border-style: solid; }\n');
    expectedBorderStyles.el6 = 'solid';
    cacheMatch(`Ran (#5) on: ["/yet-another-root.${ extension }"]`);
    run.match("Client modified -- refreshing");
    checkCSS(expectedBorderStyles);

    // We never should have loaded cache from disk, since we only made
    // each compiler once and there were no cache files at this point.
    run.forbid('CACHE(${ packageName }): Loaded');

    // Kill the run. Change one file and re-run.
    run.stop();
    s.write('packages/local-pack/p.' + extension,
            setVariable('el4-style', 'double'));
    expectedBorderStyles.el4 = 'double';
    run = startRun(s);

    // This time there's a cache to load!  Note that for
    // MultiFileCachingCompiler we load all the cache entries, even for the
    // not-up-to-date file 'top', because we only key off of filename, not off
    // of cache key.
    cacheMatch('Loaded {}/subdir/nested-root.' + extension);
    cacheMatch('Loaded {}/top.' + extension);
    cacheMatch('Loaded {}/yet-another-root.' + extension);
    cacheMatch(`Ran (#1) on: ["/top.${ extension }"]`);
    run.match('Hello world');
    checkCSS(expectedBorderStyles);

    s.write('bad-import.' + extension, importLine('/foo/bad'));
    run.match('Errors prevented startup');
    switch (packageName) {
    case 'less':
      run.match('bad-import.less:1: Unknown import: /foo/bad.less');
      break;
    case 'stylus':
      run.match('bad-import.styl: Stylus compiler error: bad-import.styl:1');
      run.match('failed to locate @import file /foo/bad.styl');
      break;
    }
    run.match('Waiting for file change');

    run.stop();
  });
});

// Tests that rebuilding a compiler plugin re-instantiates the source processor,
// but other changes don't.
selftest.define("compiler plugin caching - local plugin", function () {
  var s = new Sandbox({ fakeMongo: true });

  s.createApp("myapp", "local-compiler-plugin");
  s.cd("myapp");

  var run = startRun(s);

  // The compiler gets used the first time...
  run.match("PrintmeCompiler invocation 1");
  // ... and the program runs the generated code.
  run.match("PMC: Print out bar");
  run.match("PMC: Print out foo");

  s.write("quux.printme", "And print out quux");
  // PrintmeCompiler gets reused.
  run.match("PrintmeCompiler invocation 2");
  // And the right output prints out
  run.match("PMC: Print out bar");
  run.match("PMC: Print out foo");
  run.match("PMC: And print out quux");

  // Restart meteor; see that the disk cache gets used.
  run.stop();
  run = startRun(s);
  // Disk cache gets us up to 3.
  run.match("PrintmeCompiler invocation 3");
  // And the right output prints out
  run.match("PMC: Print out bar");
  run.match("PMC: Print out foo");
  run.match("PMC: And print out quux");

  // Edit the compiler itself.
  s.write('packages/local-plugin/plugin.js',
          s.read('packages/local-plugin/plugin.js').replace(/PMC/, 'pmc'));
  // New PrintmeCompiler object, and empty disk cache dir.
  run.match("PrintmeCompiler invocation 1");
  // And the right output prints out (lower case now)
  run.match("pmc: Print out bar");
  run.match("pmc: Print out foo");
  run.match("pmc: And print out quux");

  run.stop();
});

// Test error on duplicate compiler plugins.
selftest.define("compiler plugins - duplicate extension", () => {
  const s = new Sandbox({ fakeMongo: true });

  s.createApp("myapp", "duplicate-compiler-extensions");
  s.cd("myapp");

  let run = startRun(s);
  run.match('Errors prevented startup');
  run.match('conflict: two packages');
  run.match('trying to handle *.myext');

  // Fix it by changing one extension.
  s.write('packages/local-plugin/plugin.js',
          s.read('packages/local-plugin/plugin.js').replace('myext', 'xext'));
  run.match('Modified -- restarting');

  run.stop();
});

// Test error when a source file no longer has an active plugin.
selftest.define("compiler plugins - inactive source", () => {
  const s = new Sandbox({ fakeMongo: true });

  // This app depends on the published package 'glasser:uses-sourcish', and
  // contains a local package 'local-plugin'.
  //
  // glasser:uses-sourcish depends on local-plugin and contains a file
  // 'foo.sourcish'. When glasser:uses-sourcish@0.0.1 was published, a local
  // copy of 'local-plugin' had a plugin which called registerCompiler for the
  // extension '*.sourcish', and so 'foo.sourcish' is in the published isopack
  // as a source file. However, the copy of 'local-plugin' currently in the test
  // app contains no plugins. So we hit this weird error.
  s.createApp('myapp', 'uses-published-package-with-inactive-source');
  s.cd('myapp');

  let run = startRun(s);
  run.match('Errors prevented startup');
  run.match('no plugin found for foo.sourcish in glasser:use-sourcish');
  run.match('none is now');

  run.stop();
});

// Test error when the registerCompiler callback throws.
selftest.define("compiler plugins - compiler throws", () => {
  const s = new Sandbox({ fakeMongo: true });

  s.createApp('myapp', 'compiler-plugin-throws-on-instantiate');
  s.cd('myapp');

  const run = s.run('add', 'local-plugin');
  run.matchErr('Errors while adding packages');
  run.matchErr(
    'While running registerCompiler callback in package local-plugin');
  // XXX This is wrong! The path on disk is packages/local-plugin/plugin.js, but
  // at some point we switched to the servePath which is based on the *plugin*'s
  // "package" name.
  run.matchErr('packages/compilePrintme/plugin.js:5:1: Error in my ' +
               'registerCompiler callback!');
  run.expectExit(1);
});

// Test that compiler plugins can add static assets. Also tests `filenames`
// option to registerCompiler.
selftest.define("compiler plugins - compiler addAsset", () => {
  const s = new Sandbox({ fakeMongo: true });

  s.createApp('myapp', 'compiler-plugin-add-asset');
  s.cd('myapp');

  const run = startRun(s);
  // Test server-side asset.
  run.match("extension is null");  // test getExtension -> null
  run.match("Asset says Print out foo");

  // Test client-side asset.
  const body = getUrl('http://localhost:3000/foo.printme');
  selftest.expectEqual(body, 'Print out foo\n');

  run.stop();
});

// Test that a package can have a single file that is both source code and an
// asset
selftest.define("compiler plugins - addAssets", () => {
  const s = new Sandbox({ fakeMongo: true });

  s.createApp('myapp', 'compiler-plugin-asset-and-source');
  s.cd('myapp');

  const run = startRun(s);

  // Test server-side asset.
  run.match("Printing out my own source code!");

  // Test client-side asset.
  let body = getUrl('http://localhost:3000/packages/' +
    'asset-and-source/asset-and-source.js');
  selftest.expectTrue(body.indexOf('Printing out my own source code!') !== -1);

  // Test that deprecated API still works (added in 1.2.1 in response to people
  // having trouble upgrading to 1.2)
  s.write("packages/asset-and-source/package.js", `Package.describe({
      name: 'asset-and-source',
      version: '0.0.1'
    });

    Package.onUse(function(api) {
      api.addFiles('asset-and-source.js');
      api.addFiles('asset-and-source.js',
        ['client', 'server'], { isAsset: true });
    });
  `);

  // Test server-side asset.
  run.match("Printing out my own source code!");

  // Test client-side asset.
  body = getUrl('http://localhost:3000/packages/' +
    'asset-and-source/asset-and-source.js');
  selftest.expectTrue(body.indexOf('Printing out my own source code!') !== -1);

  // Test error messages for malformed package files
  s.write("packages/asset-and-source/package.js", `Package.describe({
      name: 'asset-and-source',
      version: '0.0.1'
    });

    Package.onUse(function(api) {
      api.addFiles('asset-and-source.js');
      api.addAssets('asset-and-source.js', ['client', 'server']);
      api.addFiles('asset-and-source.js');
    });
  `);

  run.match(/Duplicate source file/);

  s.write("packages/asset-and-source/package.js", `Package.describe({
      name: 'asset-and-source',
      version: '0.0.1'
    });

    Package.onUse(function(api) {
      api.addFiles('asset-and-source.js');
      api.addAssets('asset-and-source.js', ['client', 'server']);
      api.addAssets('asset-and-source.js', ['client', 'server']);
    });
  `);

  run.match(/Duplicate asset file/);

  s.write("packages/asset-and-source/package.js", `Package.describe({
      name: 'asset-and-source',
      version: '0.0.1'
    });

    Package.onUse(function(api) {
      api.addAssets('asset-and-source.js');
    });
  `);

  run.match(/requires a second argument/);

  run.stop();
});


Accounts.oauth.registerService("meteor-developer");

if (Meteor.isClient) {
  Meteor.loginWithMeteorDeveloperAccount = function (options, callback) {
    // support a callback without options
    if (! callback && typeof options === "function") {
      callback = options;
      options = null;
    }

    var credentialRequestCompleteCallback =
          Accounts.oauth.credentialRequestCompleteHandler(callback);
    MeteorDeveloperAccounts.requestCredential(options, credentialRequestCompleteCallback);
  };
} else {
  Accounts.addAutopublishFields({
    // publish all fields including access token, which can legitimately be used
    // from the client (if transmitted over ssl or on localhost).
    forLoggedInUser: ['services.meteor-developer'],
    forOtherUsers: [
      'services.meteor-developer.username',
      'services.meteor-developer.profile',
      'services.meteor-developer.id'
    ]
  });
}


// Node allows .js module files as direct children of node_modules

// directories, npm never takes advantage of this possibility, which

// conveniently allows Meteor to install files there without conflict.

const relPkgPath = pathJoin("node_modules", packageName + ".js");

const absPkgPath = pathJoin(this.sourceRoot, relPkgPath);

// Note that this absPkgPath need not actually exist on disk!

if (! has(this.absPathToOutputIndex, absPkgPath)) {

keep keep keep keep replace keep keep keep keep

const relPkgPath = pathJoin("node_modules", "meteor", packageName + ".js");


Package.describe({
  name: 'ddp-rate-limiter',
  version: '1.0.1-modules.2',
  // Brief, one-line summary of the package.
  summary: 'The DDPRateLimiter allows users to add rate limits to DDP' +
  ' methods and subscriptions.',
  // URL to the Git repository containing the source code for this package.
  git: '',
  // By default, Meteor will default to using README.md for documentation.
  // To avoid submitting documentation, set this field to null.
  documentation: 'README.md'
});

Package.onUse(function(api) {
  api.use('rate-limit', 'server');
  api.export('DDPRateLimiter', 'server');
  api.addFiles('ddp-rate-limiter.js', 'server');
});

Package.onTest(function(api) {
  api.use('underscore');
  api.use(['accounts-password', 'tinytest', 'test-helpers', 'tracker',
           'accounts-base', 'random', 'email', 'underscore', 'check',
           'ddp', 'ecmascript', 'es5-shim']);
  api.use('ddp-rate-limiter');

  api.addFiles('ddp-rate-limiter-tests-common.js');
  api.addFiles('ddp-rate-limiter-test-service.js', 'server');
  api.addFiles('ddp-rate-limiter-tests.js', 'client');
});


Package.describe({
  summary: "Encrypt account secrets stored in the database",
  version: '1.0.7-modules.2'
});

Package.onUse(function (api) {
  api.use("npm-node-aes-gcm@=0.1.3_6");

  api.export("OAuthEncryption", ["server"]);
  api.use([
    "underscore",
    "ejson"
  ]);
  api.addFiles("encrypt.js", ["server"]);
});

Package.onTest(function (api) {
  api.use("tinytest");
  api.use("oauth-encryption");
  api.addFiles("encrypt_tests.js", ["server"]);
});


export default function (keybindings, userPreference) {

const { aidou } = userPreference.getAll()

keep keep keep replace keep keep keep keep

import path from 'path'
import { shell } from 'electron'
import { isFile } from 'common/filesystem'
import * as actions from '../actions/help'
import { checkUpdates } from '../actions/marktext'

/// Check whether the package is updatable at runtime.
const isUpdatable = () => {
  // TODO: If not updatable, allow to check whether there is a new version available.

  const resFile = isFile(path.join(process.resourcesPath, 'app-update.yml'))
  if (!resFile) {
    // No update resource file available.
    return false
  } else if (process.env.APPIMAGE) {
    // We are running as AppImage.
    return true
  } else if (process.platform === 'win32' && isFile(path.join(process.resourcesPath, 'md.ico'))) {
    // Windows is a little but tricky. The update resource file is always available and
    // there is no way to check the target type at runtime (electron-builder#4119).
    // As workaround we check whether "md.ico" exists that is only included in the setup.
    return true
  }

  // Otherwise assume that we cannot perform an auto update (standalone binary, archives,
  // packed for package manager).
  return false
}

export default function () {
  const helpMenu = {
    label: 'Help',
    role: 'help',
    submenu: [{
      label: 'Learn More',
      click () {
        shell.openExternal('https://marktext.app')
      }
    }, {
      label: 'Source Code on GitHub',
      click () {
        shell.openExternal('https://github.com/marktext/marktext')
      }
    }, {
      label: 'License',
      click () {
        shell.openExternal('https://github.com/marktext/marktext/blob/master/LICENSE')
      }
    }, {
      label: 'Changelog',
      click () {
        shell.openExternal('https://github.com/marktext/marktext/blob/master/.github/CHANGELOG.md')
      }
    }, {
      label: 'Markdown syntax',
      click () {
        shell.openExternal('https://spec.commonmark.org/0.29/')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Feedback via Twitter',
      click (item, win) {
        actions.showTweetDialog(win, 'twitter')
      }
    }, {
      label: 'Report Issue or Feature request',
      click () {
        shell.openExternal('https://github.com/marktext/marktext/issues')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Follow @Jocs on Github',
      click () {
        shell.openExternal('https://github.com/Jocs')
      }
    }]
  }

  if (isUpdatable()) {
    helpMenu.submenu.push({
      type: 'separator'
    }, {
      label: 'Check for updates...',
      click (menuItem, browserWindow) {
        checkUpdates(menuItem, browserWindow)
      }
    })
  }

  if (process.platform !== 'darwin') {
    helpMenu.submenu.push({
      type: 'separator'
    }, {
      label: 'About Mark Text',
      click (menuItem, browserWindow) {
        actions.showAboutDialog(browserWindow)
      }
    })
  }
  return helpMenu
}


# Inter-Process Communication (IPC)

[Electron](https://electronjs.org/docs/api/ipc-main) provides `ipcMain` and `ipcRenderer` to communicate asynchronously between the main process and renderer processes. The event name/channel must be prefixed with `mt::` (previously `AGANI::`) if used between main process and renderer processes. The default argument list will be `(event, ...args)`. The event name/channel is not prefixed when using `ipcMain` to emit events to the main process directly and emitted events don't have an `event` parameter. The parameter list will only be `(...args)`! When simulate a renderer event you must specify a [event](https://electronjs.org/docs/api/ipc-main#event-object) parameter (`null` or `undefined` may lead to unexpected exceptions).

## Examples

Listening to a renderer event in the main process:

```js
import { ipcMain } from 'electron'

// Listen for renderer events
ipcMain.on('mt::some-event-name', (event, arg1, arg2) => {
  // ...

  // Send a direct response to the renderer process
  event.sender.send('mt::some-event-name-response', 'pong')
})

// Listen for main events
ipcMain.on('some-event-name', (arg1, arg2) => {
  // ...
})

ipcMain.emit('some-event-name', 'arg 1', 'arg 2')
// ipcMain.emit('mt::some-event-name-response', undefined, 'arg 1', 'arg 2') // crash because event is used
```

Listening to a main event in the renderer process:

```js
import { ipcRenderer } from 'electron'

// Listen for main events
ipcRenderer.on('mt::some-event-name-response', (event, arg1, arg2) => {
  // ...
})

ipcRenderer.send('mt::some-event-name-response', 'arg 1', 'arg 2')
```


// TODO: Remove information from other vue source files into this file.

export const isValidService = name => {
  return name !== 'none' && services.hasOwnProperty(name)
}

const services = {
  // Dummy service used to opt-in real services.
  none: {
    name: '',
    isGdprCompliant: true,
    privacyUrl: '',
    tosUrl: '',

    // Set to true to always allow to change to this dummy service
    agreedToLegalNotices: true
  },

  // Real services
  smms: {
    name: 'sm.ms',
    isGdprCompliant: false,
    privacyUrl: '',
    tosUrl: 'https://sm.ms/about/',

    // Currently a non-persistent value
    agreedToLegalNotices: false
  },
  github: {
    name: 'GitHub',
    isGdprCompliant: true,
    privacyUrl: 'https://github.com/site/privacy',
    tosUrl: 'https://github.com/site/terms',

    // Currently a non-persistent value
    agreedToLegalNotices: false
  }
}

export default services


import paragraphIcon from '../../assets/pngicon/paragraph/2.png'
import htmlIcon from '../../assets/pngicon/html/2.png'
import hrIcon from '../../assets/pngicon/horizontal_line/2.png'
import frontMatterIcon from '../../assets/pngicon/front_matter/2.png'
import header1Icon from '../../assets/pngicon/heading_1/2.png'
import header2Icon from '../../assets/pngicon/heading_2/2.png'
import header3Icon from '../../assets/pngicon/heading_3/2.png'
import header4Icon from '../../assets/pngicon/heading_4/2.png'
import header5Icon from '../../assets/pngicon/heading_5/2.png'
import header6Icon from '../../assets/pngicon/heading_6/2.png'
import newTableIcon from '../../assets/pngicon/new_table/2.png'
import bulletListIcon from '../../assets/pngicon/bullet_list/2.png'
import codeIcon from '../../assets/pngicon/code/2.png'
import quoteIcon from '../../assets/pngicon/quote_block/2.png'
import todoListIcon from '../../assets/pngicon/todolist/2.png'
import mathblockIcon from '../../assets/pngicon/math/2.png'
import orderListIcon from '../../assets/pngicon/order_list/2.png'
import flowchartIcon from '../../assets/pngicon/flowchart/2.png'
import sequenceIcon from '../../assets/pngicon/sequence/2.png'
import mermaidIcon from '../../assets/pngicon/mermaid/2.png'
import vegaIcon from '../../assets/pngicon/chart/2.png'
import { isOsx } from '../../config'

const COMMAND_KEY = isOsx ? '⌘' : '⌃'

// Command (or Cmd) ⌘
// Shift ⇧
// Option (or Alt) ⌥
// Control (or Ctrl) ⌃
// Caps Lock ⇪
// Fn

// Command (or Cmd) ⌘
// Shift ⇧
// Option (or Alt) ⌥
// Control (or Ctrl) ⌃
// Caps Lock ⇪
// Fn

export const quicInsertObj = {
  'basic block': [{
    title: 'Paragraph',
    subTitle: 'Lorem Ipsum is simply dummy text',
    label: 'paragraph',
    shortCut: `${COMMAND_KEY}0`,
    icon: paragraphIcon
  }, {
    title: 'Horizontal Line',
    subTitle: '---',
    label: 'hr',
    shortCut: `⌥${COMMAND_KEY}-`,
    icon: hrIcon
  }, {
    title: 'Front Matter',
    subTitle: '--- Lorem Ipsum ---',
    label: 'front-matter',
    shortCut: `⌥${COMMAND_KEY}Y`,
    icon: frontMatterIcon
  }],
  header: [{
    title: 'Header 1',
    subTitle: '# Lorem Ipsum is simply ...',
    label: 'heading 1',
    shortCut: `${COMMAND_KEY}1`,
    icon: header1Icon
  }, {
    title: 'Header 2',
    subTitle: '## Lorem Ipsum is simply ...',
    label: 'heading 2',
    shortCut: `${COMMAND_KEY}2`,
    icon: header2Icon
  }, {
    title: 'Header 3',
    subTitle: '### Lorem Ipsum is simply ...',
    label: 'heading 3',
    shortCut: `${COMMAND_KEY}3`,
    icon: header3Icon
  }, {
    title: 'Header 4',
    subTitle: '#### Lorem Ipsum is simply ...',
    label: 'heading 4',
    shortCut: `${COMMAND_KEY}4`,
    icon: header4Icon
  }, {
    title: 'Header 5',
    subTitle: '##### Lorem Ipsum is simply ...',
    label: 'heading 5',
    shortCut: `${COMMAND_KEY}5`,
    icon: header5Icon
  }, {
    title: 'Header 6',
    subTitle: '###### Lorem Ipsum is simply ...',
    label: 'heading 6',
    shortCut: `${COMMAND_KEY}6`,
    icon: header6Icon
  }],
  'advanced block': [{
    title: 'Table Block',
    subTitle: '|Lorem | Ipsum is simply |',
    label: 'table',
    shortCut: `${COMMAND_KEY}T`,
    icon: newTableIcon
  }, {
    title: 'Math Formula',
    subTitle: '$$ Lorem Ipsum is simply $$',
    label: 'mathblock',
    shortCut: `⌥${COMMAND_KEY}M`,
    icon: mathblockIcon
  }, {
    title: 'HTML Block',
    subTitle: '<div> Lorem Ipsum is simply </div>',
    label: 'html',
    shortCut: `⌥${COMMAND_KEY}J`,
    icon: htmlIcon
  }, {
    title: 'Code Block',
    subTitle: '```java Lorem Ipsum is simply ```',
    label: 'pre',
    shortCut: `⌥${COMMAND_KEY}C`,
    icon: codeIcon
  }, {
    title: 'Quote Block',
    subTitle: '>Lorem Ipsum is simply ...',
    label: 'blockquote',
    shortCut: `⌥${COMMAND_KEY}Q`,
    icon: quoteIcon
  }],
  'list block': [{
    title: 'Order List',
    subTitle: '1. Lorem Ipsum is simply ...',
    label: 'ol-order',
    shortCut: `⌥${COMMAND_KEY}O`,
    icon: orderListIcon
  }, {
    title: 'Bullet List',
    subTitle: '- Lorem Ipsum is simply ...',
    label: 'ul-bullet',
    shortCut: `⌥${COMMAND_KEY}U`,
    icon: bulletListIcon
  }, {
    title: 'To-do List',
    subTitle: '- [x] Lorem Ipsum is simply ...',
    label: 'ul-task',
    shortCut: `⌥${COMMAND_KEY}X`,
    icon: todoListIcon
  }],
  diagram: [{
    title: 'Vega Chart',
    subTitle: 'Render flow chart by vega-lite.js.',
    label: 'vega-lite',
    icon: vegaIcon
  }, {
    title: 'Flow Chart',
    subTitle: 'Render flow chart by flowchart.js.',
    label: 'flowchart',
    icon: flowchartIcon
  }, {
    title: 'Sequence Diagram',
    subTitle: 'Render sequence diagram by js-sequence.',
    label: 'sequence',
    icon: sequenceIcon
  }, {
    title: 'Mermaid',
    subTitle: 'Render Diagram by mermaid.',
    label: 'mermaid',
    icon: mermaidIcon
  }]
}


export default function (keybindings, userPreference, recentlyUsedFiles) {

const { autoSave } = userPreference.getAll()

keep keep keep keep replace keep keep keep

import * as actions from '../actions/format'

export default function (keybindings) {
  return {
    id: 'formatMenuItem',
    label: 'Format',
    submenu: [{
      id: 'strongMenuItem',
      label: 'Strong',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('formatStrong'),
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'strong')
      }
    }, {
      id: 'emphasisMenuItem',
      label: 'Emphasis',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('formatEmphasis'),
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'em')
      }
    }, {
      id: 'underlineMenuItem',
      label: 'Underline',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('formatUnderline'),
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'u')
      }
    }, {
      type: 'separator'
    }, {
      id: 'superscriptMenuItem',
      label: 'Superscript',
      type: 'checkbox',
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'sup')
      }
    }, {
      id: 'subscriptMenuItem',
      label: 'Subscript',
      type: 'checkbox',
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'sub')
      }
    }, {
      type: 'separator'
    }, {
      id: 'inlineCodeMenuItem',
      label: 'Inline Code',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('formatInlineCode'),
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'inline_code')
      }
    }, {
      id: 'inlineMathMenuItem',
      label: 'Inline Math',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('formatInlineMath'),
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'inline_math')
      }
    }, {
      type: 'separator'
    }, {
      id: 'strikeMenuItem',
      label: 'Strike',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('formatStrike'),
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'del')
      }
    }, {
      id: 'hyperlinkMenuItem',
      label: 'Hyperlink',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('formatHyperlink'),
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'link')
      }
    }, {
      id: 'imageMenuItem',
      label: 'Image',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('formatImage'),
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'image')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Clear Format',
      accelerator: keybindings.getAccelerator('formatClearFormat'),
      click (menuItem, browserWindow) {
        actions.format(browserWindow, 'clear')
      }
    }]
  }
}


# XSS Tests

<svg/onload="throw new Error('XSS 3')">

<svg width="100" height="100">

  <rect width="100" height="100" style="fill:rgb(0,0,0)" />
</svg>

<iframe/src="data:text/html,<svg onload=\"throw new Error('XSS 7')\"">

<embed src="data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjAiIHdpZHRoPSIxMDAiIGhlaWdodD0iMTAwIj48c2NyaXB0PnRocm93IG5ldyBFcnJvcignWFNTIDgnKTwvc2NyaXB0Pjwvc3ZnPgo=" type="image/svg+xml" AllowScriptAccess="always"></embed>


export default function del (h, cursor, block, token, outerClass) {
  return this.delEmStrongFac('del', h, cursor, block, token, outerClass)
}


import { isWindows } from '../../config'

let GetStringRegKey = null
if (isWindows) {
  try {
    GetStringRegKey = require('vscode-windows-registry').GetStringRegKey
  } catch (e) {
    // Ignore webpack build error on macOS and Linux.
  }
}

export const winHKEY = {
  HKCU: 'HKEY_CURRENT_USER',
  HKLM: 'HKEY_LOCAL_MACHINE',
  HKCR: 'HKEY_CLASSES_ROOT',
  HKU: 'HKEY_USERS',
  HKCC: 'HKEY_CURRENT_CONFIG'
}

/**
 * Returns the registry key value.
 *
 * @param {winHKEY} hive The registry key
 * @param {string} path The registry subkey
 * @param {string} name The registry name
 * @returns {string|null|undefined} The registry key value or null/undefined.
 */
export const getStringRegKey = (hive, path, name) => {
  try {
    return GetStringRegKey(hive, path, name)
  } catch (e) {
    return null
  }
}


import * as actions from '../actions/format'

keep keep keep keep keep replace keep keep keep keep

import { ipcMain } from 'electron'
import * as actions from '../actions/view'

export default function (keybindings) {
  const viewMenu = {
    label: 'View',
    submenu: [{
      id: 'sourceCodeModeMenuItem',
      label: 'Source Code Mode',
      accelerator: keybindings.getAccelerator('viewSourceCodeMode'),
      type: 'checkbox',
      checked: false,
      click (item, browserWindow, event) {
        // if we call this function, the checked state is not set
        if (!event) {
          item.checked = !item.checked
        }
        actions.typeMode(browserWindow, 'sourceCode', item)
      }
    }, {
      id: 'typewriterModeMenuItem',
      label: 'Typewriter Mode',
      accelerator: keybindings.getAccelerator('viewTypewriterMode'),
      type: 'checkbox',
      checked: false,
      click (item, browserWindow, event) {
        // if we call this function, the checked state is not set
        if (!event) {
          item.checked = !item.checked
        }
        actions.typeMode(browserWindow, 'typewriter', item)
      }
    }, {
      id: 'focusModeMenuItem',
      label: 'Focus Mode',
      accelerator: keybindings.getAccelerator('viewFocusMode'),
      type: 'checkbox',
      checked: false,
      click (item, browserWindow, event) {
        // if we call this function, the checked state is not set
        if (!event) {
          item.checked = !item.checked
        }
        actions.typeMode(browserWindow, 'focus', item)
      }
    }, {
      type: 'separator'
    }, {
      label: 'Toggle Side Bar',
      id: 'sideBarMenuItem',
      accelerator: keybindings.getAccelerator('viewToggleSideBar'),
      type: 'checkbox',
      checked: false,
      click (item, browserWindow, event) {
        // if we call this function, the checked state is not set
        if (!event) {
          item.checked = !item.checked
        }

        actions.layout(item, browserWindow, 'showSideBar')
      }
    }, {
      label: 'Toggle Tab Bar',
      id: 'tabBarMenuItem',
      accelerator: keybindings.getAccelerator('viewToggleTabBar'),
      type: 'checkbox',
      checked: false,
      click (item, browserWindow, event) {
        // if we call this function, the checked state is not set
        if (!event) {
          item.checked = !item.checked
        }

        actions.layout(item, browserWindow, 'showTabBar')
      }
    }, {
      type: 'separator'
    }]
  }

  if (global.MARKTEXT_DEBUG) {
    viewMenu.submenu.push({
      label: 'Toggle Developer Tools',
      accelerator: keybindings.getAccelerator('viewDevToggleDeveloperTools'),
      click (item, focusedWindow) {
        if (focusedWindow) {
          focusedWindow.webContents.toggleDevTools()
        }
      }
    })
    viewMenu.submenu.push({
      label: 'Reload',
      accelerator: keybindings.getAccelerator('viewDevReload'),
      click (item, focusedWindow) {
        if (focusedWindow) {
          ipcMain.emit('window-reload-by-id', focusedWindow.id)
        }
      }
    })
  }
  return viewMenu
}


import defaultOptions from './options'
import { cleanUrl, escape } from './utils'

/**
 * Renderer
 */

function Renderer (options = {}) {
  this.options = options || defaultOptions
}

Renderer.prototype.frontmatter = function (text) {
  return `<pre class="front-matter">\n${text}</pre>\n`
}

Renderer.prototype.multiplemath = function (text) {
  let output = ''
  if (this.options.mathRenderer) {
    const displayMode = true
    output = this.options.mathRenderer(text, displayMode)
  }
  return output || `<pre class="multiple-math">\n${text}</pre>\n`
}

Renderer.prototype.inlineMath = function (math) {
  let output = ''
  if (this.options.mathRenderer) {
    const displayMode = false
    output = this.options.mathRenderer(math, displayMode)
  }
  return output || math
}

Renderer.prototype.emoji = function (text, emoji) {
  if (this.options.emojiRenderer) {
    return this.options.emojiRenderer(emoji)
  } else {
    return text
  }
}

Renderer.prototype.code = function (code, infostring, escaped, codeBlockStyle) {
  const lang = (infostring || '').match(/\S*/)[0]
  if (this.options.highlight) {
    const out = this.options.highlight(code, lang)
    if (out !== null && out !== code) {
      escaped = true
      code = out
    }
  }

  let className = codeBlockStyle === 'fenced' ? 'fenced-code-block' : 'indented-code-block'
  className = lang ? `${className} ${this.options.langPrefix}${escape(lang, true)}` : className

  return '
' +
    (escaped ? code : escape(code, true)) +
    '\n
\n'
}

Renderer.prototype.blockquote = function (quote) {
  return '
\n' + quote + '
\n'
}

Renderer.prototype.html = function (html) {
  return html
}

Renderer.prototype.heading = function (text, level, raw, slugger, headingStyle) {
  if (this.options.headerIds) {
    return '<h' +
      level +
      ' id="' +
      this.options.headerPrefix +
      slugger.slug(raw) +
      '" class="' +
      headingStyle +
      '">' +
      text +
      '</h' +
      level +
      '>\n'
  }
  // ignore IDs
  return '<h' + level + '>' + text + '</h' + level + '>\n'
}

Renderer.prototype.hr = function () {
  return this.options.xhtml ? '
\n' : '
\n'
}

Renderer.prototype.list = function (body, ordered, start, taskList) {
  const type = ordered ? 'ol' : 'ul'
  const startatt = (ordered && start !== 1) ? (' start="' + start + '"') : ''
  return '<' + type + startatt + '>\n' + body + '</' + type + '>\n'
}

Renderer.prototype.listitem = function (text, checked) {
  // normal list
  if (checked === undefined) {
    return '
' + text + '
\n'
  }

  // task list
  return '
<input type="checkbox"' +
    (checked ? ' checked=""' : '') +
    ' disabled=""' +
    (this.options.xhtml ? ' /' : '') +
    '> ' +
    text +
    '
\n'
}

Renderer.prototype.paragraph = function (text) {
  return '
' + text + '
\n'
}

Renderer.prototype.table = function (header, body) {
  if (body) body = '
' + body + '
'

  return '
\n' +
    '
\n' +
    header +
    '
\n' +
    body +
    '
\n'
}

Renderer.prototype.tablerow = function (content) {
  return '
\n' + content + '
\n'
}

Renderer.prototype.tablecell = function (content, flags) {
  const type = flags.header ? 'th' : 'td'
  const tag = flags.align
    ? '<' + type + ' align="' + flags.align + '">'
    : '<' + type + '>'
  return tag + content + '</' + type + '>\n'
}

// span level renderer
Renderer.prototype.strong = function (text) {
  return '' + text + ''
}

Renderer.prototype.em = function (text) {
  return '' + text + ''
}

Renderer.prototype.codespan = function (text) {
  return '' + text + ''
}

Renderer.prototype.br = function () {
  return this.options.xhtml ? '
' : '
'
}

Renderer.prototype.del = function (text) {
  return '' + text + ''
}

Renderer.prototype.link = function (href, title, text) {
  href = cleanUrl(this.options.sanitize, this.options.baseUrl, href)
  if (href === null) {
    return text
  }
  let out = '' + text + ''
  return out
}

Renderer.prototype.image = function (href, title, text) {
  href = cleanUrl(this.options.sanitize, this.options.baseUrl, href)
  if (href === null) {
    return text
  }

  let out = '' : '>'
  return out
}

Renderer.prototype.text = function (text) {
  return text
}

export default Renderer


import notification from './notification'

export default [
  notification
]


keep keep keep replace keep keep keep keep keep


/*
 * ------------------------------------
 * Prism.js light theme
 */

code[class*="language-"],
pre.ag-paragraph {
  color: black;
  /*font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;*/
  text-align: left;
  white-space: pre;
  word-spacing: normal;
  word-break: normal;
  word-wrap: normal;
  -moz-tab-size: 4;
  -o-tab-size: 4;
  tab-size: 4;

  -webkit-hyphens: none;
  -moz-hyphens: none;
  -ms-hyphens: none;
  hyphens: none;
}

/* Code Fence */
pre.ag-paragraph {
  padding: 1em;
  margin: 1em 0;
}

/* Inline Code */
:not(pre) > code[class*="language-"] {
  padding: .1em;
  border-radius: .3em;
  white-space: normal;
}

.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
  color: slategray;
}

.token.punctuation {
  color: #999;
}

.namespace {
  opacity: .7;
}

.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol {
  color: #905;
}

.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin {
  color: #690;
}

.token.inserted {
  color: #22863a;
  background: #f0fff4;
}

.token.deleted {
  color: #b31d28;
  background: #ffeef0;
}

.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
  color: #9a6e3a;
}

.token.atrule,
.token.attr-value,
.token.keyword {
  color: #07a;
}

.token.function,
.token.class-name {
  color: #DD4A68;
}

.token.regex,
.token.important,
.token.variable {
  color: #e90;
}

.token.important,
.token.bold {
  font-weight: bold;
}
.token.italic {
  font-style: italic;
}

.token.entity {
  cursor: help;
}


<!-- Attention: please fill in the issue in the template format, including but not limited to: detailed description, reproduction steps, expected results, actual results, error screenshots (not required), Mark Text and operation system version. If an issue is marked as `more detail`, stating that the issue is opened and no content has been added after one week and will be closed, thanks for your cooperation. -->

<!-- 注意：请按照 template 格式填写 issue，包括但不仅限于：详尽的描述、重现步骤、期望结果、实际结果、错误截图（非必须）、Mark Text 和 操作系统版本型号或版本号，如果一个 issue 被标记为 `more detail`，说明 issue 填写不完整，一周后仍未补充任何内容，将被关闭,谢谢合作 -->

<!-- Please make sure your application version is up to date -->

### Description

<!-- Description of the bug or feature -->

### Steps to reproduce

1. [First step]
2. [Second step]
3. [and so on...]

**Expected behavior:**

<!-- What you expected to happen -->

**Actual behavior:**

<!-- What actually happened -->

**Link to an example: [optional]**

<!-- If you're reporting a bug that's not reproducible, or it's hard to description, please paste a screenshot of reproducing this issue - gif format is appropriate -->

### Versions

- Mark Text:
- Operating system:


import * as actions from '../actions/paragraph'

keep keep keep keep keep replace keep keep keep keep keep

import * as actions from '../actions/edit'
import { isOsx } from '../../config'

export default function (keybindings, userPreference) {
  const { aidou } = userPreference.getAll()
  return {
    label: 'Edit',
    submenu: [{
      label: 'Undo',
      accelerator: keybindings.getAccelerator('editUndo'),
      click: (menuItem, browserWindow) => {
        actions.edit(browserWindow, 'undo')
      }
    }, {
      label: 'Redo',
      accelerator: keybindings.getAccelerator('editRedo'),
      click: (menuItem, browserWindow) => {
        actions.edit(browserWindow, 'redo')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Cut',
      accelerator: keybindings.getAccelerator('editCut'),
      role: 'cut'
    }, {
      label: 'Copy',
      accelerator: keybindings.getAccelerator('editCopy'),
      role: 'copy'
    }, {
      label: 'Paste',
      accelerator: keybindings.getAccelerator('editPaste'),
      role: 'paste'
    }, {
      type: 'separator'
    }, {
      label: 'Copy As Markdown',
      accelerator: keybindings.getAccelerator('editCopyAsMarkdown'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'copyAsMarkdown')
      }
    }, {
      label: 'Copy As HTML',
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'copyAsHtml')
      }
    }, {
      label: 'Paste As Plain Text',
      accelerator: keybindings.getAccelerator('editCopyAsPlaintext'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'pasteAsPlainText')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Select All',
      accelerator: keybindings.getAccelerator('editSelectAll'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'selectAll')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Duplicate',
      accelerator: keybindings.getAccelerator('editDuplicate'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'duplicate')
      }
    }, {
      label: 'Create Paragraph',
      accelerator: keybindings.getAccelerator('editCreateParagraph'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'createParagraph')
      }
    }, {
      label: 'Delete Paragraph',
      accelerator: keybindings.getAccelerator('editDeleteParagraph'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'deleteParagraph')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Find',
      accelerator: keybindings.getAccelerator('editFind'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'find')
      }
    }, {
      label: 'Find Next',
      accelerator: keybindings.getAccelerator('editFindNext'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'fineNext')
      }
    }, {
      label: 'Find Previous',
      accelerator: keybindings.getAccelerator('editFindPrevious'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'findPrev')
      }
    }, {
      label: 'Replace',
      accelerator: keybindings.getAccelerator('editReplace'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'replace')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Aidou',
      visible: aidou,
      id: 'aidou',
      accelerator: keybindings.getAccelerator('editAidou'),
      click (menuItem, browserWindow) {
        actions.edit(browserWindow, 'aidou')
      }
    }, {
      label: 'Screenshot',
      id: 'screenshot',
      visible: isOsx,
      accelerator: keybindings.getAccelerator('editScreenshot'),
      click (menuItem, browserWindow) {
        actions.screenshot(browserWindow, 'screenshot')
      }
    }, {
      type: 'separator'
    }, {
      label: 'Line Ending',
      submenu: [{
        id: 'crlfLineEndingMenuEntry',
        label: 'Carriage return and line feed (CRLF)',
        type: 'radio',
        click (menuItem, browserWindow) {
          actions.lineEnding(browserWindow, 'crlf')
        }
      }, {
        id: 'lfLineEndingMenuEntry',
        label: 'Line feed (LF)',
        type: 'radio',
        click (menuItem, browserWindow) {
          actions.lineEnding(browserWindow, 'lf')
        }
      }]
    }, {
      type: 'separator'
    }]
  }
}


import { ipcMain, BrowserWindow } from 'electron'
import { getMenuItemById } from '../../menu'

const sourceCodeModeMenuItemId = 'sourceCodeModeMenuItem'
const typewriterModeMenuItemId = 'typewriterModeMenuItem'
const focusModeMenuItemId = 'focusModeMenuItem'

export const typeMode = (win, type, item) => {
  const { checked } = item
  win.webContents.send('AGANI::view', { type, checked })

  if (type === 'sourceCode') {
    const typewriterModeMenuItem = getMenuItemById(typewriterModeMenuItemId)
    const focusModeMenuItem = getMenuItemById(focusModeMenuItemId)
    typewriterModeMenuItem.enabled = !checked
    focusModeMenuItem.enabled = !checked
  }
}

export const layout = (item, win, type) => {
  win.webContents.send('AGANI::listen-for-view-layout', { [type]: item.checked })
}

export const showTabBar = win => {
  const tabBarMenuItem = getMenuItemById('tabBarMenuItem')
  if (tabBarMenuItem && !tabBarMenuItem.checked && tabBarMenuItem.click) {
    tabBarMenuItem.click(tabBarMenuItem, win)
  }
}

ipcMain.on('AGANI::ask-for-mode', e => {
  const sourceCodeModeMenuItem = getMenuItemById(sourceCodeModeMenuItemId)
  const typewriterModeMenuItem = getMenuItemById(typewriterModeMenuItemId)
  const focusModeMenuItem = getMenuItemById(focusModeMenuItemId)
  const modes = {
    sourceCode: sourceCodeModeMenuItem.checked,
    typewriter: typewriterModeMenuItem.checked,
    focus: focusModeMenuItem.checked
  }
  const win = BrowserWindow.fromWebContents(e.sender)
  win.webContents.send('AGANI::res-for-mode', modes)
})

ipcMain.on('AGANI::set-view-layout', (e, { showSideBar, showTabBar }) => {
  const sideBarMenuItem = getMenuItemById('sideBarMenuItem')
  const tabBarMenuItem = getMenuItemById('tabBarMenuItem')

  sideBarMenuItem.checked = showSideBar
  tabBarMenuItem.checked = showTabBar
})


import { CLASS_OR_ID } from '../../../config'

export default function backlash (h, cursor, block, token, outerClass) {
  const className = this.getClassName(outerClass, block, token, cursor)
  const { start, end } = token.range
  const content = this.highlight(h, block, start, end, token)

  return [
    h(`span.${className}.${CLASS_OR_ID['AG_REMOVE']}`, content)
  ]
}


export default function (userPreference) {

const { theme } = userPreference.getAll()

import * as actions from '../actions/paragraph'

export default function (keybindings) {
  return {
    id: 'paragraphMenuEntry',
    label: 'Paragraph',
    submenu: [{
      id: 'heading1MenuItem',
      label: 'Heading 1',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphHeading1'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'heading 1')
      }
    }, {
      id: 'heading2MenuItem',
      label: 'Heading 2',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphHeading2'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'heading 2')
      }
    }, {
      id: 'heading3MenuItem',
      label: 'Heading 3',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphHeading3'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'heading 3')
      }
    }, {
      id: 'heading4MenuItem',
      label: 'Heading 4',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphHeading4'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'heading 4')
      }
    }, {
      id: 'heading5MenuItem',
      label: 'Heading 5',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphHeading5'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'heading 5')
      }
    }, {
      id: 'heading6MenuItem',
      label: 'Heading 6',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphHeading6'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'heading 6')
      }
    }, {
      type: 'separator'
    }, {
      id: 'upgradeHeadingMenuItem',
      label: 'Upgrade Heading',
      accelerator: keybindings.getAccelerator('paragraphUpgradeHeading'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'upgrade heading')
      }
    }, {
      id: 'degradeHeadingMenuItem',
      label: 'Degrade Heading',
      accelerator: keybindings.getAccelerator('paragraphDegradeHeading'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'degrade heading')
      }
    }, {
      type: 'separator'
    }, {
      id: 'tableMenuItem',
      label: 'Table',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphTable'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'table')
      }
    }, {
      id: 'codeFencesMenuItem',
      label: 'Code Fences',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphCodeFence'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'pre')
      }
    }, {
      id: 'quoteBlockMenuItem',
      label: 'Quote Block',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphQuoteBlock'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'blockquote')
      }
    }, {
      id: 'mathBlockMenuItem',
      label: 'Math Block',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphMathBlock'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'mathblock')
      }
    }, {
      id: 'htmlBlockMenuItem',
      label: 'Html Block',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphHtmlBlock'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'html')
      }
    }, {
      type: 'separator'
    }, {
      id: 'orderListMenuItem',
      label: 'Order List',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphOrderList'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'ol-order')
      }
    }, {
      id: 'bulletListMenuItem',
      label: 'Bullet List',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphBulletList'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'ul-bullet')
      }
    }, {
      id: 'taskListMenuItem',
      label: 'Task List',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphTaskList'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'ul-task')
      }
    }, {
      type: 'separator'
    }, {
      id: 'looseListItemMenuItem',
      label: 'Loose List Item',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphLooseListItem'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'loose-list-item')
      }
    }, {
      type: 'separator'
    }, {
      id: 'paragraphMenuItem',
      label: 'Paragraph',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphParagraph'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'paragraph')
      }
    }, {
      id: 'horizontalLineMenuItem',
      label: 'Horizontal Line',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphHorizontalLine'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'hr')
      }
    }, {
      id: 'frontMatterMenuItem',
      label: 'YAML Front Matter',
      type: 'checkbox',
      accelerator: keybindings.getAccelerator('paragraphYAMLFrontMatter'),
      click (menuItem, browserWindow) {
        actions.paragraph(browserWindow, 'front-matter')
      }
    }]
  }
}


export default function em (h, cursor, block, token, outerClass) {
  return this.delEmStrongFac('em', h, cursor, block, token, outerClass)
}


class Clipboard {
  constructor (muya) {
    this.muya = muya
    this._copyType = 'normal' // `normal` or `copyAsMarkdown` or `copyAsHtml`
    this._pasteType = 'normal' // `normal` or `pasteAsPlainText`
    this.listen()
  }

  listen () {
    const { container, eventCenter, contentState } = this.muya
    const docPasteHandler = event => {
      contentState.docPasteHandler(event)
    }
    const copyCutHandler = event => {
      contentState.copyHandler(event, this._copyType)
      if (event.type === 'cut') {
        // when user use `cut` function, the dom has been deleted by default.
        // But should update content state manually.
        contentState.cutHandler()
      }
      this._copyType = 'normal'
    }
    const pasteHandler = event => {
      contentState.pasteHandler(event, this._pasteType)
      this._pasteType = 'normal'
      this.muya.dispatchChange()
    }

    eventCenter.attachDOMEvent(document, 'paste', docPasteHandler)
    eventCenter.attachDOMEvent(container, 'paste', pasteHandler)
    eventCenter.attachDOMEvent(container, 'cut', copyCutHandler)
    eventCenter.attachDOMEvent(container, 'copy', copyCutHandler)
  }

  copyAsMarkdown () {
    this._copyType = 'copyAsMarkdown'
    document.execCommand('copy')
  }

  copyAsHtml () {
    this._copyType = 'copyAsHtml'
    document.execCommand('copy')
  }

  pasteAsPlainText () {
    this._pasteType = 'pasteAsPlainText'
    document.execCommand('paste')
  }

  copy (name) {
    switch (name) {
      case 'table':
        this._copyType = 'copyTable'
        document.execCommand('copy')
        break
      default:
        break
    }
  }
}

export default Clipboard


import { union } from '../../../utils'

// change text to highlight vdom
export default function highlight (h, block, rStart, rEnd, token) {
  const { text } = block
  const { highlights } = token
  let result = []
  const unions = []
  let pos = rStart

  if (highlights) {
    for (const light of highlights) {
      const un = union({ start: rStart, end: rEnd }, light)
      if (un) unions.push(un)
    }
  }

  if (unions.length) {
    for (const u of unions) {
      const { start, end, active } = u
      const className = this.getHighlightClassName(active)

      if (pos < start) {
        result.push(text.substring(pos, start))
      }

      result.push(h(`span.${className}`, text.substring(start, end)))
      pos = end
    }
    if (pos < rEnd) {
      result.push(block.text.substring(pos, rEnd))
    }
  } else {
    result = [text.substring(rStart, rEnd)]
  }

  return result
}