mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-09-11 11:15:24 +03:00
Merge branch 'master' into consistent-cirrus-builds
This commit is contained in:
commit
f05dbde044
27
.cirrus.yml
27
.cirrus.yml
@ -1,3 +1,6 @@
|
||||
env:
|
||||
PYTHON_VERSION: 3.10
|
||||
|
||||
linux_task:
|
||||
alias: linux
|
||||
container:
|
||||
@ -101,13 +104,19 @@ arm_linux_task:
|
||||
silicon_mac_task:
|
||||
alias: mac
|
||||
macos_instance:
|
||||
image: ghcr.io/cirruslabs/macos-monterey-base:latest
|
||||
image: ghcr.io/cirruslabs/macos-monterey-xcode:14
|
||||
memory: 8G
|
||||
env:
|
||||
CSC_LINK: ENCRYPTED[0078015a03bb6cfdbd80113ae5bbb6f448fd4bbbc40efd81bf2cb1554373046b475a4d7c77e3e3e82ac1ce2f7e3d2da5]
|
||||
CSC_KEY_PASSWORD: ENCRYPTED[82bb72653d39578035ed1860ab4978703d50bd326d925a146ff08782f987ceb37ac2d8dbace52dec2b0e2ef92debf097]
|
||||
APPLEID: ENCRYPTED[549ce052bd5666dba5245f4180bf93b74ed206fe5e6e7c8f67a8596d3767c1f682b84e347b326ac318c62a07c8844a57]
|
||||
APPLEID_PASSWORD: ENCRYPTED[774c3307fd3b62660ecf5beb8537a24498c76e8d90d7f28e5bc816742fd8954a34ffed13f9aa2d1faf66ce08b4496e6f]
|
||||
TEAM_ID: ENCRYPTED[11f3fedfbaf4aff1859bf6c105f0437ace23d84f5420a2c1cea884fbfa43b115b7834a463516d50cb276d4c4d9128b49]
|
||||
prepare_script:
|
||||
- brew install node@16 yarn git python
|
||||
- brew install node@16 yarn git python@$PYTHON_VERSION
|
||||
- git submodule init
|
||||
- git submodule update
|
||||
- ln -s /opt/homebrew/bin/python3 /opt/homebrew/bin/python
|
||||
- ln -s /opt/homebrew/bin/python$PYTHON_VERSION /opt/homebrew/bin/python
|
||||
- export PATH="/opt/homebrew/bin:/opt/homebrew/opt/node@16/bin:$PATH"
|
||||
- sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
|
||||
install_script:
|
||||
@ -136,16 +145,22 @@ silicon_mac_task:
|
||||
intel_mac_task:
|
||||
alias: mac
|
||||
macos_instance:
|
||||
image: ghcr.io/cirruslabs/macos-monterey-base:latest
|
||||
image: ghcr.io/cirruslabs/macos-monterey-xcode:14
|
||||
memory: 8G
|
||||
env:
|
||||
CSC_LINK: ENCRYPTED[0078015a03bb6cfdbd80113ae5bbb6f448fd4bbbc40efd81bf2cb1554373046b475a4d7c77e3e3e82ac1ce2f7e3d2da5]
|
||||
CSC_KEY_PASSWORD: ENCRYPTED[82bb72653d39578035ed1860ab4978703d50bd326d925a146ff08782f987ceb37ac2d8dbace52dec2b0e2ef92debf097]
|
||||
APPLEID: ENCRYPTED[549ce052bd5666dba5245f4180bf93b74ed206fe5e6e7c8f67a8596d3767c1f682b84e347b326ac318c62a07c8844a57]
|
||||
APPLEID_PASSWORD: ENCRYPTED[774c3307fd3b62660ecf5beb8537a24498c76e8d90d7f28e5bc816742fd8954a34ffed13f9aa2d1faf66ce08b4496e6f]
|
||||
TEAM_ID: ENCRYPTED[11f3fedfbaf4aff1859bf6c105f0437ace23d84f5420a2c1cea884fbfa43b115b7834a463516d50cb276d4c4d9128b49]
|
||||
prepare_script:
|
||||
- sudo rm -rf /Library/Developer/CommandLineTools
|
||||
- echo A | softwareupdate --install-rosetta
|
||||
- arch -x86_64 xcode-select --install
|
||||
- arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
|
||||
- export PATH="/usr/local/opt/node@16/bin:/usr/local/bin:$PATH"
|
||||
- arch -x86_64 brew install node@16 yarn git python
|
||||
- ln -s /usr/local/bin/python3 /usr/local/bin/python
|
||||
- arch -x86_64 brew install node@16 yarn git python@$PYTHON_VERSION
|
||||
- ln -s /usr/local/bin/python$PYTHON_VERSION /usr/local/bin/python
|
||||
- git submodule init
|
||||
- git submodule update
|
||||
- sed -i -e "s/[0-9]*-dev/`date -u +%Y%m%d%H`/g" package.json
|
||||
|
4
.github/workflows/documentation.yml
vendored
4
.github/workflows/documentation.yml
vendored
@ -3,6 +3,10 @@ name: Documentation
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
jobs:
|
||||
documentation:
|
||||
|
9
.github/workflows/package-tests-linux.yml
vendored
9
.github/workflows/package-tests-linux.yml
vendored
@ -37,7 +37,7 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: pulsar.deb
|
||||
key: pulsar-$env:GITHUB_SHA
|
||||
key: pulsar-${{ github.sha }}
|
||||
|
||||
test:
|
||||
name: Package
|
||||
@ -144,7 +144,10 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install Dependencies
|
||||
run: yarn install || yarn install
|
||||
|
||||
@ -156,7 +159,7 @@ jobs:
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: pulsar.deb
|
||||
key: pulsar-$env:GITHUB_SHA
|
||||
key: pulsar-${{ github.sha }}
|
||||
|
||||
- name: Install Pulsar
|
||||
run: sudo dpkg -i pulsar.deb && sudo apt-get -f install -y
|
||||
|
52
CHANGELOG.md
52
CHANGELOG.md
@ -6,8 +6,60 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## 1.102.0
|
||||
|
||||
- Fixed a bug where `pulsar` on Windows could never trigger
|
||||
- Fixed `github` package shelling out to `git` on macOS
|
||||
- Fixed minor bugs found during fixes to tests
|
||||
- Improved our testing infastructure to aide in finding and fixing further bugs
|
||||
- Updated many dependencies of Pulsar and its core packages
|
||||
- New Pulsar Icon on macOS
|
||||
- Selected text is styled by default
|
||||
- Restored `right-clicked` CSS class on tags
|
||||
- Fixed syntax highlighting on C++
|
||||
- Updated JavaScript snippets to modern ES6 syntax
|
||||
- PPM no longer assumes `master` for git branches
|
||||
|
||||
### Pulsar
|
||||
- Added: implement signing and notarizing for macOS, PR #4 lol [@Meadowsys](https://github.com/pulsar-edit/pulsar/pull/387)
|
||||
- Fixed: Pin `python` brew installation to `3.10` during MacOS Intel Cirrus Build [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/384)
|
||||
- Update: Bump `ppm` to `a46537c0b7f0eaaef5404ef88003951fdc988c65` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/383)
|
||||
- Added: Add new macOS icon [@mdibella-dev](https://github.com/pulsar-edit/pulsar/pull/372)
|
||||
- Fixed: type $ as # [@Meadowsys](https://github.com/pulsar-edit/pulsar/pull/378)
|
||||
- Update: deps: Update github to v0.36.14-pretranspiled-take-2 [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/375)
|
||||
- Added: add style to selected text by default [@Sertonix](https://github.com/pulsar-edit/pulsar/pull/238)
|
||||
- Added: Set Max Concurrent Package Tests [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/376)
|
||||
- Fixed: c++ fixes [@icecream17](https://github.com/pulsar-edit/pulsar/pull/369)
|
||||
- Update: deps: Update github to v0.36.14-pretranspiled [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/373)
|
||||
- Update `coffeescript` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/361)
|
||||
- Updated: Misc Dependency Updates [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/362)
|
||||
- Added: Bundle `autocomplete-plus` [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/358)
|
||||
- Fixed: Add LICENSE.md to extra resources (resourcesPath) [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/354)
|
||||
- Fixed: Get Windows `pulsar` Working [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/340)
|
||||
- Fixed: Restore `right-clicked` class on a right-clicked tab [@savetheclocktower](https://github.com/pulsar-edit/pulsar/pull/368)
|
||||
- Updated: ppm: Update submodule to commit 4645ba2905747897b0 [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/371)
|
||||
- Added: Machine decaf tabs spec [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/367)
|
||||
- Added: Manually Decaf `tabs` package Specs [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/357)
|
||||
- Fixed: Uncomment and fix a settings-view package test [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/366)
|
||||
- Added: Decaf Changes from Manual and Machine Decaf to Main [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/356)
|
||||
- Added: Manual decafe tabs [@confused-Techie](https://github.com/pulsar-edit/pulsar/pull/352)
|
||||
- Added: Organize failing tests [@mauricioszabo](https://github.com/pulsar-edit/pulsar/pull/307)
|
||||
- Fixed: autocomplete-snippets: Fix repo URL [@DeeDeeG](https://github.com/pulsar-edit/pulsar/pull/341)
|
||||
- Updated: update apm message to pulsar -p [@Daeraxa](https://github.com/pulsar-edit/pulsar/pull/337)
|
||||
- Fixed: Replace incorrect spellings of 'macOS' with the correct one [@mdibella-dev](https://github.com/pulsar-edit/pulsar/pull/336)
|
||||
- Changed: use `let` and `const` in js snippets [@Sertonix](https://github.com/pulsar-edit/pulsar/pull/326)
|
||||
- Fixed: Fix URI to correct address [@mdibella-dev](https://github.com/pulsar-edit/pulsar/pull/335)
|
||||
- Updated: update copyright year (2023) [@icecream17](https://github.com/pulsar-edit/pulsar/pull/332)
|
||||
|
||||
### ppm
|
||||
- Fixed: fix: Don't assume `master` when checking git packages for upgrades [@savetheclocktower](https://github.com/pulsar-edit/ppm/pull/56)
|
||||
- Fixed: meta: Normalize package.json and lockfile line endings [@DeeDeeG](https://github.com/pulsar-edit/ppm/pull/54)
|
||||
- Update: spec: Fixtures Node v10.20.1 --> Electron v12.2.3 [@DeeDeeG](https://github.com/pulsar-edit/ppm/pull/52)
|
||||
- Fixed: Fix .com links, pulsar rebranding and rebranding readme [@Daeraxa](https://github.com/pulsar-edit/ppm/pull/48)
|
||||
|
||||
### github
|
||||
- Fixed: lib: Rebrand getAtomAppName() function (fix shelling out to `git` on macOS) [@DeeDeeG](https://github.com/pulsar-edit/github/pull/13)
|
||||
- Fixed: meta: Revert "main" to "./lib/index", no dist (fix package on `master` branch) [@DeeDeeG](https://github.com/pulsar-edit/github/pull/12)
|
||||
|
||||
## 1.101.0-beta
|
||||
|
||||
|
@ -26,12 +26,13 @@
|
||||
|
||||
<br>
|
||||
|
||||
*A Community-led Hyper-Hackable Text Editor, built on **[Electron]**,*
|
||||
*and based on everything we love about our favorite editors.*
|
||||
*A Community-led Hyper-Hackable Text Editor,*
|
||||
*Forked from [Atom], built on [Electron].*
|
||||
|
||||
*We designed it to be deeply customizable, but still*
|
||||
*Designed to be deeply customizable, but still*
|
||||
*approachable using the default configuration.*
|
||||
|
||||
|
||||
<br>
|
||||
<br>
|
||||
|
||||
@ -44,6 +45,7 @@
|
||||
[OpenCollective]: https://opencollective.com/pulsar-edit
|
||||
[Discussions]: https://github.com/orgs/pulsar-edit/discussions
|
||||
[Electron]: https://github.com/electron/electron
|
||||
[Atom]: https://github.blog/2022-06-08-sunsetting-atom/
|
||||
[Discord]: https://discord.gg/7aEbB9dGRT 'Join the Pulsar Discord today!'
|
||||
[Crowdin]: https://crowdin.pulsar-edit.dev
|
||||
[Status]: https://cirrus-ci.com/github/pulsar-edit/pulsar/master
|
||||
|
11
docs/.jsdoc.json
Normal file
11
docs/.jsdoc.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"recurseDepth": 10,
|
||||
"source": {
|
||||
"include": ["src", "packages"],
|
||||
"exclude": ["node_modules"],
|
||||
"excludePattern": "((^|\\/|\\\\)_|node_modules)"
|
||||
},
|
||||
"opts": {
|
||||
"recurse": true
|
||||
}
|
||||
}
|
@ -0,0 +1,298 @@
|
||||
## Classes
|
||||
|
||||
<dl>
|
||||
<dt><a href="#AtomEnvironment">AtomEnvironment</a></dt>
|
||||
<dd><p>Pulsar global for dealing with packages, themes, menus, and the window.</p>
|
||||
<p>An instance of this class is always available as the <code>atom</code> global.</p>
|
||||
</dd>
|
||||
<dt><a href="#Clipboard">Clipboard</a></dt>
|
||||
<dd></dd>
|
||||
</dl>
|
||||
|
||||
## Constants
|
||||
|
||||
<dl>
|
||||
<dt><a href="#etch">etch</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#fs">fs</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#dalek">dalek</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#assert">assert</a></dt>
|
||||
<dd></dd>
|
||||
</dl>
|
||||
|
||||
## Functions
|
||||
|
||||
<dl>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#conditionPromise">conditionPromise()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#destroy">destroy()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#destroyChildren">destroyChildren()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#releaseChildren">releaseChildren()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#subscribeToRepository">subscribeToRepository()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#updateDiffs">updateDiffs()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#conditionPromise">conditionPromise()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
</dl>
|
||||
|
||||
<a name="AtomEnvironment"></a>
|
||||
|
||||
## AtomEnvironment
|
||||
Pulsar global for dealing with packages, themes, menus, and the window.
|
||||
|
||||
An instance of this class is always available as the `atom` global.
|
||||
|
||||
**Kind**: global class
|
||||
|
||||
* [AtomEnvironment](#AtomEnvironment)
|
||||
* _instance_
|
||||
* [.clipboard](#AtomEnvironment+clipboard) : [<code>Clipboard</code>](#Clipboard)
|
||||
* [.deserializers](#AtomEnvironment+deserializers) : <code>DeserializerManager</code>
|
||||
* [.views](#AtomEnvironment+views) : <code>ViewRegistry</code>
|
||||
* [.notifications](#AtomEnvironment+notifications) : <code>NotificationManager</code>
|
||||
* [.config](#AtomEnvironment+config) : <code>Config</code>
|
||||
* [.keymaps](#AtomEnvironment+keymaps) : <code>KeymapManager</code>
|
||||
* [.tooltips](#AtomEnvironment+tooltips) : <code>TooltipManager</code>
|
||||
* [.commands](#AtomEnvironment+commands) : <code>CommandRegistry</code>
|
||||
* [.grammars](#AtomEnvironment+grammars) : <code>GrammarRegistry</code>
|
||||
* [.styles](#AtomEnvironment+styles) : <code>StyleManager</code>
|
||||
* [.packages](#AtomEnvironment+packages) : <code>PackageManager</code>
|
||||
* [.themes](#AtomEnvironment+themes) : <code>ThemeManager</code>
|
||||
* [.menu](#AtomEnvironment+menu) : <code>MenuManager</code>
|
||||
* [.contextMenu](#AtomEnvironment+contextMenu) : <code>ContextMenuManager</code>
|
||||
* [.project](#AtomEnvironment+project) : <code>Project</code>
|
||||
* [.textEditors](#AtomEnvironment+textEditors) : <code>TextEditorRegistry</code>
|
||||
* [.workspace](#AtomEnvironment+workspace) : <code>Workspace</code>
|
||||
* [.history](#AtomEnvironment+history) : <code>HistoryManager</code>
|
||||
* _Messaging the User_
|
||||
* [.beep()](#AtomEnvironment+beep)
|
||||
* _static_
|
||||
* _Event Subscription_
|
||||
* [.onDidBeep(callback)](#AtomEnvironment.onDidBeep) ⇒ <code>Disposable</code>
|
||||
|
||||
<a name="AtomEnvironment+clipboard"></a>
|
||||
|
||||
### atomEnvironment.clipboard : [<code>Clipboard</code>](#Clipboard)
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+deserializers"></a>
|
||||
|
||||
### atomEnvironment.deserializers : <code>DeserializerManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+views"></a>
|
||||
|
||||
### atomEnvironment.views : <code>ViewRegistry</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+notifications"></a>
|
||||
|
||||
### atomEnvironment.notifications : <code>NotificationManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+config"></a>
|
||||
|
||||
### atomEnvironment.config : <code>Config</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+keymaps"></a>
|
||||
|
||||
### atomEnvironment.keymaps : <code>KeymapManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+tooltips"></a>
|
||||
|
||||
### atomEnvironment.tooltips : <code>TooltipManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+commands"></a>
|
||||
|
||||
### atomEnvironment.commands : <code>CommandRegistry</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+grammars"></a>
|
||||
|
||||
### atomEnvironment.grammars : <code>GrammarRegistry</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+styles"></a>
|
||||
|
||||
### atomEnvironment.styles : <code>StyleManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+packages"></a>
|
||||
|
||||
### atomEnvironment.packages : <code>PackageManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+themes"></a>
|
||||
|
||||
### atomEnvironment.themes : <code>ThemeManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+menu"></a>
|
||||
|
||||
### atomEnvironment.menu : <code>MenuManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+contextMenu"></a>
|
||||
|
||||
### atomEnvironment.contextMenu : <code>ContextMenuManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+project"></a>
|
||||
|
||||
### atomEnvironment.project : <code>Project</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+textEditors"></a>
|
||||
|
||||
### atomEnvironment.textEditors : <code>TextEditorRegistry</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+workspace"></a>
|
||||
|
||||
### atomEnvironment.workspace : <code>Workspace</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+history"></a>
|
||||
|
||||
### atomEnvironment.history : <code>HistoryManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+beep"></a>
|
||||
|
||||
### atomEnvironment.beep()
|
||||
Visually and audibly trigger a beep.
|
||||
|
||||
**Kind**: instance method of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
**Category**: Messaging the User
|
||||
**Emits**: <code>event:beep</code>
|
||||
<a name="AtomEnvironment.onDidBeep"></a>
|
||||
|
||||
### AtomEnvironment.onDidBeep(callback) ⇒ <code>Disposable</code>
|
||||
Invoke the given callback whenever [::beep](::beep) is called.
|
||||
|
||||
**Kind**: static method of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
**Returns**: <code>Disposable</code> - on which `.dispose()` can be called to unsubscribe.
|
||||
**Category**: Event Subscription
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| callback | <code>function</code> | Function to be called whenever [::beep](::beep) is called. |
|
||||
|
||||
<a name="Clipboard"></a>
|
||||
|
||||
## Clipboard
|
||||
**Kind**: global class
|
||||
<a name="new_Clipboard_new"></a>
|
||||
|
||||
### new Clipboard()
|
||||
Represents the clipboard used for copying and pasting in Pulsar.
|
||||
|
||||
An instance of this class is always available as the `atom.clipboard` global.
|
||||
|
||||
**Example**
|
||||
```js
|
||||
// returns 'hello'
|
||||
atom.clipboard.write('hello');
|
||||
|
||||
console.log(atom.clipboard.read());
|
||||
```
|
||||
<a name="etch"></a>
|
||||
|
||||
## etch
|
||||
**Kind**: global constant
|
||||
**Jsx**: etch.dom
|
||||
<a name="fs"></a>
|
||||
|
||||
## fs
|
||||
**Kind**: global constant
|
||||
**Babel**:
|
||||
<a name="dalek"></a>
|
||||
|
||||
## dalek
|
||||
**Kind**: global constant
|
||||
**Babel**:
|
||||
<a name="assert"></a>
|
||||
|
||||
## assert
|
||||
**Kind**: global constant
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="conditionPromise"></a>
|
||||
|
||||
## conditionPromise()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="destroy"></a>
|
||||
|
||||
## destroy()
|
||||
**Kind**: global function
|
||||
**Describe**: Handles tear down of destructables and subscriptions.
|
||||
Does not handle release of memory. This method should only be called
|
||||
just before this object is freed, and should only tear down the main
|
||||
object components that are guarunteed to exist at all times.
|
||||
<a name="destroyChildren"></a>
|
||||
|
||||
## destroyChildren()
|
||||
**Kind**: global function
|
||||
**Describe**: Destroys this objects children (non-freeing), it's intended
|
||||
to be an ease-of use function for maintaing this object. This method
|
||||
should only tear down objects that are selectively allocated upon
|
||||
repository discovery.
|
||||
|
||||
Example: this.diffs only exists when we have a repository.
|
||||
<a name="releaseChildren"></a>
|
||||
|
||||
## releaseChildren()
|
||||
**Kind**: global function
|
||||
**Describe**: The memory releasing complement function of `destroyChildren`.
|
||||
frees the memory allocated at all child object storage locations
|
||||
when there is no repository.
|
||||
<a name="subscribeToRepository"></a>
|
||||
|
||||
## subscribeToRepository()
|
||||
**Kind**: global function
|
||||
**Describe**: handles all subscriptions based on the repository in focus
|
||||
<a name="updateDiffs"></a>
|
||||
|
||||
## updateDiffs()
|
||||
**Kind**: global function
|
||||
**Describe**: Uses text markers in the target editor to visualize
|
||||
git modifications, additions, and deletions. The current algorithm
|
||||
just redraws the markers each call.
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="conditionPromise"></a>
|
||||
|
||||
## conditionPromise()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
@ -0,0 +1,306 @@
|
||||
## Classes
|
||||
|
||||
<dl>
|
||||
<dt><a href="#AtomEnvironment">AtomEnvironment</a></dt>
|
||||
<dd><p>Pulsar global for dealing with packages, themes, menus, and the window.</p>
|
||||
<p>An instance of this class is always available as the <code>atom</code> global.</p>
|
||||
</dd>
|
||||
<dt><a href="#Clipboard">Clipboard</a></dt>
|
||||
<dd></dd>
|
||||
</dl>
|
||||
|
||||
## Constants
|
||||
|
||||
<dl>
|
||||
<dt><a href="#etch">etch</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#fs">fs</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#dalek">dalek</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#assert">assert</a></dt>
|
||||
<dd></dd>
|
||||
</dl>
|
||||
|
||||
## Functions
|
||||
|
||||
<dl>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#conditionPromise">conditionPromise()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#destroy">destroy()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#destroyChildren">destroyChildren()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#releaseChildren">releaseChildren()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#subscribeToRepository">subscribeToRepository()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#updateDiffs">updateDiffs()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#conditionPromise">conditionPromise()</a></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#beforeEach">beforeEach()</a></dt>
|
||||
<dd></dd>
|
||||
</dl>
|
||||
|
||||
<a name="AtomEnvironment"></a>
|
||||
|
||||
## AtomEnvironment
|
||||
Pulsar global for dealing with packages, themes, menus, and the window.
|
||||
|
||||
An instance of this class is always available as the `atom` global.
|
||||
|
||||
**Kind**: global class
|
||||
|
||||
* [AtomEnvironment](#AtomEnvironment)
|
||||
* _instance_
|
||||
* [.clipboard](#AtomEnvironment+clipboard) : [<code>Clipboard</code>](#Clipboard)
|
||||
* [.deserializers](#AtomEnvironment+deserializers) : <code>DeserializerManager</code>
|
||||
* [.views](#AtomEnvironment+views) : <code>ViewRegistry</code>
|
||||
* [.notifications](#AtomEnvironment+notifications) : <code>NotificationManager</code>
|
||||
* [.config](#AtomEnvironment+config) : <code>Config</code>
|
||||
* [.keymaps](#AtomEnvironment+keymaps) : <code>KeymapManager</code>
|
||||
* [.tooltips](#AtomEnvironment+tooltips) : <code>TooltipManager</code>
|
||||
* [.commands](#AtomEnvironment+commands) : <code>CommandRegistry</code>
|
||||
* [.grammars](#AtomEnvironment+grammars) : <code>GrammarRegistry</code>
|
||||
* [.styles](#AtomEnvironment+styles) : <code>StyleManager</code>
|
||||
* [.packages](#AtomEnvironment+packages) : <code>PackageManager</code>
|
||||
* [.themes](#AtomEnvironment+themes) : <code>ThemeManager</code>
|
||||
* [.menu](#AtomEnvironment+menu) : <code>MenuManager</code>
|
||||
* [.contextMenu](#AtomEnvironment+contextMenu) : <code>ContextMenuManager</code>
|
||||
* [.project](#AtomEnvironment+project) : <code>Project</code>
|
||||
* [.textEditors](#AtomEnvironment+textEditors) : <code>TextEditorRegistry</code>
|
||||
* [.workspace](#AtomEnvironment+workspace) : <code>Workspace</code>
|
||||
* [.history](#AtomEnvironment+history) : <code>HistoryManager</code>
|
||||
* _Messaging the User_
|
||||
* [.beep()](#AtomEnvironment+beep)
|
||||
* _static_
|
||||
* [.preloadPackages](#AtomEnvironment.preloadPackages) ℗
|
||||
* _Event Subscription_
|
||||
* [.onDidBeep(callback)](#AtomEnvironment.onDidBeep) ⇒ <code>Disposable</code>
|
||||
|
||||
<a name="AtomEnvironment+clipboard"></a>
|
||||
|
||||
### atomEnvironment.clipboard : [<code>Clipboard</code>](#Clipboard)
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+deserializers"></a>
|
||||
|
||||
### atomEnvironment.deserializers : <code>DeserializerManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+views"></a>
|
||||
|
||||
### atomEnvironment.views : <code>ViewRegistry</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+notifications"></a>
|
||||
|
||||
### atomEnvironment.notifications : <code>NotificationManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+config"></a>
|
||||
|
||||
### atomEnvironment.config : <code>Config</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+keymaps"></a>
|
||||
|
||||
### atomEnvironment.keymaps : <code>KeymapManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+tooltips"></a>
|
||||
|
||||
### atomEnvironment.tooltips : <code>TooltipManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+commands"></a>
|
||||
|
||||
### atomEnvironment.commands : <code>CommandRegistry</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+grammars"></a>
|
||||
|
||||
### atomEnvironment.grammars : <code>GrammarRegistry</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+styles"></a>
|
||||
|
||||
### atomEnvironment.styles : <code>StyleManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+packages"></a>
|
||||
|
||||
### atomEnvironment.packages : <code>PackageManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+themes"></a>
|
||||
|
||||
### atomEnvironment.themes : <code>ThemeManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+menu"></a>
|
||||
|
||||
### atomEnvironment.menu : <code>MenuManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+contextMenu"></a>
|
||||
|
||||
### atomEnvironment.contextMenu : <code>ContextMenuManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+project"></a>
|
||||
|
||||
### atomEnvironment.project : <code>Project</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+textEditors"></a>
|
||||
|
||||
### atomEnvironment.textEditors : <code>TextEditorRegistry</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+workspace"></a>
|
||||
|
||||
### atomEnvironment.workspace : <code>Workspace</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+history"></a>
|
||||
|
||||
### atomEnvironment.history : <code>HistoryManager</code>
|
||||
**Kind**: instance property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
<a name="AtomEnvironment+beep"></a>
|
||||
|
||||
### atomEnvironment.beep()
|
||||
Visually and audibly trigger a beep.
|
||||
|
||||
**Kind**: instance method of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
**Category**: Messaging the User
|
||||
**Emits**: <code>event:beep</code>
|
||||
<a name="AtomEnvironment.preloadPackages"></a>
|
||||
|
||||
### AtomEnvironment.preloadPackages ℗
|
||||
Returns output of `preloadPackages()` for this Classes Instance of `Packages`.
|
||||
|
||||
**Kind**: static property of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
**Access**: private
|
||||
<a name="AtomEnvironment.onDidBeep"></a>
|
||||
|
||||
### AtomEnvironment.onDidBeep(callback) ⇒ <code>Disposable</code>
|
||||
Invoke the given callback whenever [::beep](::beep) is called.
|
||||
|
||||
**Kind**: static method of [<code>AtomEnvironment</code>](#AtomEnvironment)
|
||||
**Returns**: <code>Disposable</code> - on which `.dispose()` can be called to unsubscribe.
|
||||
**Category**: Event Subscription
|
||||
|
||||
| Param | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| callback | <code>function</code> | Function to be called whenever [::beep](::beep) is called. |
|
||||
|
||||
<a name="Clipboard"></a>
|
||||
|
||||
## Clipboard
|
||||
**Kind**: global class
|
||||
<a name="new_Clipboard_new"></a>
|
||||
|
||||
### new Clipboard()
|
||||
Represents the clipboard used for copying and pasting in Pulsar.
|
||||
|
||||
An instance of this class is always available as the `atom.clipboard` global.
|
||||
|
||||
**Example**
|
||||
```js
|
||||
// returns 'hello'
|
||||
atom.clipboard.write('hello');
|
||||
|
||||
console.log(atom.clipboard.read());
|
||||
```
|
||||
<a name="etch"></a>
|
||||
|
||||
## etch
|
||||
**Kind**: global constant
|
||||
**Jsx**: etch.dom
|
||||
<a name="fs"></a>
|
||||
|
||||
## fs
|
||||
**Kind**: global constant
|
||||
**Babel**:
|
||||
<a name="dalek"></a>
|
||||
|
||||
## dalek
|
||||
**Kind**: global constant
|
||||
**Babel**:
|
||||
<a name="assert"></a>
|
||||
|
||||
## assert
|
||||
**Kind**: global constant
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="conditionPromise"></a>
|
||||
|
||||
## conditionPromise()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="destroy"></a>
|
||||
|
||||
## destroy()
|
||||
**Kind**: global function
|
||||
**Describe**: Handles tear down of destructables and subscriptions.
|
||||
Does not handle release of memory. This method should only be called
|
||||
just before this object is freed, and should only tear down the main
|
||||
object components that are guarunteed to exist at all times.
|
||||
<a name="destroyChildren"></a>
|
||||
|
||||
## destroyChildren()
|
||||
**Kind**: global function
|
||||
**Describe**: Destroys this objects children (non-freeing), it's intended
|
||||
to be an ease-of use function for maintaing this object. This method
|
||||
should only tear down objects that are selectively allocated upon
|
||||
repository discovery.
|
||||
|
||||
Example: this.diffs only exists when we have a repository.
|
||||
<a name="releaseChildren"></a>
|
||||
|
||||
## releaseChildren()
|
||||
**Kind**: global function
|
||||
**Describe**: The memory releasing complement function of `destroyChildren`.
|
||||
frees the memory allocated at all child object storage locations
|
||||
when there is no repository.
|
||||
<a name="subscribeToRepository"></a>
|
||||
|
||||
## subscribeToRepository()
|
||||
**Kind**: global function
|
||||
**Describe**: handles all subscriptions based on the repository in focus
|
||||
<a name="updateDiffs"></a>
|
||||
|
||||
## updateDiffs()
|
||||
**Kind**: global function
|
||||
**Describe**: Uses text markers in the target editor to visualize
|
||||
git modifications, additions, and deletions. The current algorithm
|
||||
just redraws the markers each call.
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="conditionPromise"></a>
|
||||
|
||||
## conditionPromise()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
||||
<a name="beforeEach"></a>
|
||||
|
||||
## beforeEach()
|
||||
**Kind**: global function
|
||||
**Babel**:
|
23
package.json
23
package.json
@ -2,7 +2,7 @@
|
||||
"name": "pulsar",
|
||||
"author": "Pulsar Community <noreply@pulsar-edit.com>",
|
||||
"productName": "Pulsar",
|
||||
"version": "1.101.0-dev",
|
||||
"version": "1.102.0-dev",
|
||||
"description": "A Community-led Hyper-Hackable Text Editor",
|
||||
"branding": {
|
||||
"id": "pulsar",
|
||||
@ -70,7 +70,7 @@
|
||||
"fuzzy-finder": "https://codeload.github.com/atom/fuzzy-finder/legacy.tar.gz/refs/tags/v1.14.3",
|
||||
"git-diff": "file:packages/git-diff",
|
||||
"git-utils": "5.7.1",
|
||||
"github": "https://codeload.github.com/pulsar-edit/github/tar.gz/refs/tags/v0.36.14-pretranspiled-take-2",
|
||||
"github": "https://codeload.github.com/pulsar-edit/github/tar.gz/refs/tags/v0.36.15-pretranspiled",
|
||||
"glob": "^7.1.1",
|
||||
"go-to-line": "file:packages/go-to-line",
|
||||
"grammar-selector": "file:packages/grammar-selector",
|
||||
@ -120,7 +120,7 @@
|
||||
"line-ending-selector": "file:packages/line-ending-selector",
|
||||
"line-top-index": "0.3.1",
|
||||
"link": "file:packages/link",
|
||||
"markdown-preview": "https://codeload.github.com/atom/markdown-preview/legacy.tar.gz/refs/tags/v0.160.2",
|
||||
"markdown-preview": "file:./packages/markdown-preview",
|
||||
"minimatch": "^3.0.3",
|
||||
"mocha": "6.2.3",
|
||||
"mocha-junit-reporter": "2.0.0",
|
||||
@ -149,12 +149,12 @@
|
||||
"service-hub": "^0.7.4",
|
||||
"settings-view": "file:packages/settings-view",
|
||||
"sinon": "9.2.1",
|
||||
"snippets": "https://github.com/pulsar-edit/snippets.git#fe00fd6",
|
||||
"snippets": "github:pulsar-edit/snippets#bb00f909c6c645b173f27346875d8fa0c7af09f7",
|
||||
"solarized-dark-syntax": "file:packages/solarized-dark-syntax",
|
||||
"solarized-light-syntax": "file:packages/solarized-light-syntax",
|
||||
"spell-check": "https://codeload.github.com/atom/spell-check/legacy.tar.gz/refs/tags/v0.77.1",
|
||||
"status-bar": "file:packages/status-bar",
|
||||
"styleguide": "https://codeload.github.com/atom/styleguide/legacy.tar.gz/refs/tags/v0.49.12",
|
||||
"styleguide": "file:./packages/styleguide",
|
||||
"superstring": "^2.4.4",
|
||||
"symbols-view": "https://codeload.github.com/atom/symbols-view/legacy.tar.gz/refs/tags/v0.118.4",
|
||||
"tabs": "file:packages/tabs",
|
||||
@ -170,7 +170,7 @@
|
||||
"welcome": "file:packages/welcome",
|
||||
"whitespace": "https://codeload.github.com/atom/whitespace/legacy.tar.gz/refs/tags/v0.37.8",
|
||||
"winreg": "^1.2.1",
|
||||
"wrap-guide": "https://codeload.github.com/atom/wrap-guide/legacy.tar.gz/refs/tags/v0.41.0",
|
||||
"wrap-guide": "file:./packages/wrap-guide",
|
||||
"yargs": "17.6.2"
|
||||
},
|
||||
"packageDependencies": {
|
||||
@ -215,7 +215,7 @@
|
||||
"keybinding-resolver": "0.39.1",
|
||||
"line-ending-selector": "file:./packages/line-ending-selector",
|
||||
"link": "file:./packages/link",
|
||||
"markdown-preview": "0.160.2",
|
||||
"markdown-preview": "file:./packages/markdown-preview",
|
||||
"notifications": "0.72.1",
|
||||
"open-on-github": "file:./packages/open-on-github",
|
||||
"package-generator": "file:./packages/package-generator",
|
||||
@ -223,7 +223,7 @@
|
||||
"snippets": "1.6.1",
|
||||
"spell-check": "0.77.1",
|
||||
"status-bar": "file:./packages/status-bar",
|
||||
"styleguide": "0.49.12",
|
||||
"styleguide": "file:./packages/styleguide",
|
||||
"symbols-view": "0.118.4",
|
||||
"tabs": "file:./packages/tabs",
|
||||
"timecop": "0.36.2",
|
||||
@ -231,7 +231,7 @@
|
||||
"update-package-dependencies": "file:./packages/update-package-dependencies",
|
||||
"welcome": "file:./packages/welcome",
|
||||
"whitespace": "0.37.8",
|
||||
"wrap-guide": "0.41.0",
|
||||
"wrap-guide": "file:./packages/wrap-guide",
|
||||
"language-c": "file:./packages/language-c",
|
||||
"language-clojure": "file:./packages/language-clojure",
|
||||
"language-coffee-script": "file:./packages/language-coffee-script",
|
||||
@ -273,10 +273,11 @@
|
||||
"build:apm": "cd ppm && yarn install",
|
||||
"start": "electron --no-sandbox --enable-logging . -f",
|
||||
"dist": "node script/electron-builder.js",
|
||||
"js-docs": "jsdoc2md ./src/**/*.js ./packages/**/*.js > ./docs/Pulsar-API-Documentation.md",
|
||||
"private-js-docs": "jsdoc2md --private ./src/**/*.js ./packages/**/*.js > ./docs/Source-Code-Documentation.md"
|
||||
"js-docs": "jsdoc2md --files src --configure docs/.jsdoc.json > ./docs/Pulsar-API-Documentation.md",
|
||||
"private-js-docs": "jsdoc2md --private --files src --configure docs/.jsdoc.json > ./docs/Source-Code-Documentation.md"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/notarize": "^1.2.3",
|
||||
"@playwright/test": "1.22.2",
|
||||
"electron": "12.2.3",
|
||||
"electron-builder": "23.3.1",
|
||||
|
@ -16,7 +16,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
|
||||
| **autocomplete-atom-api** | [`atom/autocomplete-atom-api`][autocomplete-atom-api] | |
|
||||
| **autocomplete-css** | [`./autocomplete-css`](./autocomplete-css) | |
|
||||
| **autocomplete-html** | [`./autocomplete-html`](./autocomplete-html) | |
|
||||
| **autocomplete-plus** | [`atom/autocomplete-plus`][autocomplete-plus] | |
|
||||
| **autocomplete-plus** | [`./autocomplete-plus`][./autocomplete-plus] | |
|
||||
| **autocomplete-snippets** | [`./autocomplete-snippets`](./autocomplete-snippets) | |
|
||||
| **autoflow** | [`./autoflow`](./autoflow) | |
|
||||
| **autosave** | [`pulsar-edit/autosave`][autosave] | [#17834](https://github.com/atom/atom/issues/17834) |
|
||||
@ -76,7 +76,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
|
||||
| **language-yaml** | [`./language-yaml`](./language-yaml) | |
|
||||
| **line-ending-selector** | [`./line-ending-selector`](./line-ending-selector) | |
|
||||
| **link** | [`./link`](./link) | |
|
||||
| **markdown-preview** | [`atom/markdown-preview`][markdown-preview] | |
|
||||
| **markdown-preview** | [`./markdown-preview`][./markdown-preview] | |
|
||||
| **notifications** | [`atom/notifications`][notifications] | [#18277](https://github.com/atom/atom/issues/18277) |
|
||||
| **one-dark-syntax** | [`./one-dark-syntax`](./one-dark-syntax) | |
|
||||
| **one-dark-ui** | [`./one-dark-ui`](./one-dark-ui) | |
|
||||
@ -90,7 +90,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
|
||||
| **solarized-light-syntax** | [`./solarized-light-syntax`](./solarized-light-syntax) | |
|
||||
| **spell-check** | [`atom/spell-check`][spell-check] | |
|
||||
| **status-bar** | [`./status-bar`](./status-bar) | |
|
||||
| **styleguide** | [`pulsar-edit/styleguide`][styleguide] | [#18283](https://github.com/atom/atom/issues/18283) |
|
||||
| **styleguide** | [`./styleguide`][./styleguide] | |
|
||||
| **symbols-view** | [`pulsar-edit/symbols-view`][symbols-view] | |
|
||||
| **tabs** | [`./tabs`](./tabs) | |
|
||||
| **timecop** | [`pulsar-edit/timecop`][timecop] | [#18272](https://github.com/atom/atom/issues/18272) |
|
||||
@ -98,22 +98,18 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
|
||||
| **update-package-dependencies** | [`./update-package-dependencies`](./update-package-dependencies) | |
|
||||
| **welcome** | [`./welcome`](./welcome) | |
|
||||
| **whitespace** | [`./whitespace`](./whitespace) | |
|
||||
| **wrap-guide** | [`atom/wrap-guide`][wrap-guide] | [#18286](https://github.com/atom/atom/issues/18286) |
|
||||
| **wrap-guide** | [`./wrap-guide`][./wrap-guide] | |
|
||||
|
||||
[autocomplete-atom-api]: https://github.com/pulsar-edit/autocomplete-atom-api
|
||||
[autocomplete-plus]: https://github.com/pulsar-edit/autocomplete-plus
|
||||
[autosave]: https://github.com/pulsar-edit/autosave
|
||||
[bracket-matcher]: https://github.com/pulsar-edit/bracket-matcher
|
||||
[find-and-replace]: https://github.com/pulsar-edit/find-and-replace
|
||||
[fuzzy-finder]: https://github.com/pulsar-edit/fuzzy-finder
|
||||
[github]: https://github.com/pulsar-edit/github
|
||||
[keybinding-resolver]: https://github.com/pulsar-edit/keybinding-resolver
|
||||
[markdown-preview]: https://github.com/pulsar-edit/markdown-preview
|
||||
[notifications]: https://github.com/pulsar-edit/notifications
|
||||
[snippets]: https://github.com/pulsar-edit/snippets
|
||||
[spell-check]: https://github.com/pulsar-edit/spell-check
|
||||
[styleguide]: https://github.com/pulsar-edit/styleguide
|
||||
[symbols-view]: https://github.com/pulsar-edit/symbols-view
|
||||
[timecop]: https://github.com/pulsar-edit/timecop
|
||||
[tree-view]: https://github.com/pulsar-edit/tree-view
|
||||
[wrap-guide]: https://github.com/pulsar-edit/wrap-guide
|
||||
|
@ -7,6 +7,7 @@
|
||||
'composer.lock'
|
||||
'geojson'
|
||||
'gltf'
|
||||
'har'
|
||||
'htmlhintrc'
|
||||
'ipynb'
|
||||
'jscsrc'
|
||||
|
@ -10,6 +10,7 @@ fileTypes: [
|
||||
'composer.lock'
|
||||
'geojson'
|
||||
'gltf'
|
||||
'har'
|
||||
'htmlhintrc'
|
||||
'ipynb'
|
||||
'jscsrc'
|
||||
|
2
packages/markdown-preview/.gitignore
vendored
Normal file
2
packages/markdown-preview/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
npm-debug.log
|
21
packages/markdown-preview/README.md
Normal file
21
packages/markdown-preview/README.md
Normal file
@ -0,0 +1,21 @@
|
||||
# Markdown Preview package
|
||||
|
||||
Show the rendered HTML markdown to the right of the current editor using <kbd>ctrl-shift-m</kbd>.
|
||||
|
||||
It is currently enabled for `.markdown`, `.md`, `.mdown`, `.mkd`, `.mkdown`, `.ron`, and `.txt` files.
|
||||
|
||||
![markdown-preview](https://cloud.githubusercontent.com/assets/378023/10013086/24cad23e-6149-11e5-90e6-663009210218.png)
|
||||
|
||||
## Customize
|
||||
|
||||
By default Markdown Preview uses the colors of the active syntax theme. Enable `Use GitHub.com style` in the __package settings__ to make it look closer to how markdown files get rendered on github.com.
|
||||
|
||||
![markdown-preview GitHub style](https://cloud.githubusercontent.com/assets/378023/10013087/24ccc7ec-6149-11e5-97ea-53a842a715ea.png)
|
||||
|
||||
To customize even further, the styling can be overridden in your `styles.less` file. For example:
|
||||
|
||||
```css
|
||||
.markdown-preview.markdown-preview {
|
||||
background-color: #444;
|
||||
}
|
||||
```
|
BIN
packages/markdown-preview/assets/hr.png
Normal file
BIN
packages/markdown-preview/assets/hr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 939 B |
448
packages/markdown-preview/assets/primer-markdown.less
Normal file
448
packages/markdown-preview/assets/primer-markdown.less
Normal file
@ -0,0 +1,448 @@
|
||||
// All of our block level items should have the same margin
|
||||
@margin: 16px;
|
||||
|
||||
// This is styling for generic markdownized text. Anything you put in a
|
||||
// container with .markdown-body on it should render generally well. It also
|
||||
// includes some GitHub Flavored Markdown specific styling (like @mentions)
|
||||
.markdown-body {
|
||||
overflow: hidden;
|
||||
font-family: "Helvetica Neue", Helvetica, "Segoe UI", Arial, freesans, sans-serif;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
word-wrap: break-word;
|
||||
|
||||
> *:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
> *:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
// Anchors like <a name="examples">. These sometimes end up wrapped around
|
||||
// text when users mistakenly forget to close the tag or use self-closing tag
|
||||
// syntax. We don't want them to appear like links.
|
||||
// FIXME: a:not(:link):not(:visited) would be a little clearer here (and
|
||||
// possibly faster to match), but it breaks styling of <a href> elements due
|
||||
// to https://bugs.webkit.org/show_bug.cgi?id=142737.
|
||||
a:not([href]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
// Link Colors
|
||||
.absent {
|
||||
color: #c00;
|
||||
}
|
||||
|
||||
.anchor {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
padding-right: 6px;
|
||||
padding-left: 30px;
|
||||
margin-left: -30px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Headings
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
position: relative;
|
||||
margin-top: 1em;
|
||||
margin-bottom: @margin;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
|
||||
.octicon-link {
|
||||
display: none;
|
||||
color: #000;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&:hover .anchor {
|
||||
padding-left: 8px;
|
||||
margin-left: -30px;
|
||||
text-decoration: none;
|
||||
|
||||
.octicon-link {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
tt,
|
||||
code {
|
||||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 2.25em;
|
||||
line-height: 1.2;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
.anchor {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
padding-bottom: 0.3em;
|
||||
font-size: 1.75em;
|
||||
line-height: 1.225;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
.anchor {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.5em;
|
||||
line-height: 1.43;
|
||||
|
||||
.anchor {
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.25em;
|
||||
|
||||
.anchor {
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
h5 {
|
||||
font-size: 1em;
|
||||
|
||||
.anchor {
|
||||
line-height: 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
h6 {
|
||||
font-size: 1em;
|
||||
color: #777;
|
||||
|
||||
.anchor {
|
||||
line-height: 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
p,
|
||||
blockquote,
|
||||
ul, ol, dl,
|
||||
table,
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: @margin;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 4px;
|
||||
padding: 0;
|
||||
margin: @margin 0;
|
||||
background-color: #e7e7e7;
|
||||
border: 0 none;
|
||||
}
|
||||
|
||||
// Lists, Blockquotes & Such
|
||||
ul,
|
||||
ol {
|
||||
padding-left: 2em;
|
||||
|
||||
&.no-list {
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Did someone complain about list spacing? Encourage them
|
||||
// to create the spacing with their markdown formatting.
|
||||
// List behavior should be controled by the markup, not the css.
|
||||
//
|
||||
// For lists with padding between items, use blank
|
||||
// lines between items. This will generate paragraphs with
|
||||
// padding to space things out.
|
||||
//
|
||||
// - item
|
||||
//
|
||||
// - item
|
||||
//
|
||||
// - item
|
||||
//
|
||||
// For list without padding, don't use blank lines.
|
||||
//
|
||||
// - item
|
||||
// - item
|
||||
// - item
|
||||
//
|
||||
// Modifying the css to emulate these behaviors merely brakes
|
||||
// one case in the process of solving another. Don't change
|
||||
// this unless it's really really a bug.
|
||||
ul ul,
|
||||
ul ol,
|
||||
ol ol,
|
||||
ol ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
li > p {
|
||||
margin-top: @margin;
|
||||
}
|
||||
|
||||
dl {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dt {
|
||||
padding: 0;
|
||||
margin-top: @margin;
|
||||
font-size: 1em;
|
||||
font-style: italic;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
padding: 0 @margin;
|
||||
margin-bottom: @margin;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding: 0 15px;
|
||||
color: #777;
|
||||
border-left: 4px solid #ddd;
|
||||
|
||||
> :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Tables
|
||||
table {
|
||||
display: block;
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
word-break: normal;
|
||||
word-break: keep-all; // For Firefox to horizontally scroll wider tables.
|
||||
|
||||
th {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 6px 13px;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
tr {
|
||||
background-color: #fff;
|
||||
border-top: 1px solid #ccc;
|
||||
|
||||
&:nth-child(2n) {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Images & Stuff
|
||||
img {
|
||||
max-width: 100%;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
// Gollum Image Tags
|
||||
|
||||
// Framed
|
||||
span.frame {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
|
||||
& > span {
|
||||
display: block;
|
||||
float: left;
|
||||
width: auto;
|
||||
padding: 7px;
|
||||
margin: 13px 0 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid #ddd;
|
||||
}
|
||||
|
||||
span img {
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
span span {
|
||||
display: block;
|
||||
padding: 5px 0 0;
|
||||
clear: both;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
span.align-center {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
clear: both;
|
||||
|
||||
& > span {
|
||||
display: block;
|
||||
margin: 13px auto 0;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
span img {
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
span.align-right {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
clear: both;
|
||||
|
||||
& > span {
|
||||
display: block;
|
||||
margin: 13px 0 0;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
span img {
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
span.float-left {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-right: 13px;
|
||||
overflow: hidden;
|
||||
|
||||
span {
|
||||
margin: 13px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
span.float-right {
|
||||
display: block;
|
||||
float: right;
|
||||
margin-left: 13px;
|
||||
overflow: hidden;
|
||||
|
||||
& > span {
|
||||
display: block;
|
||||
margin: 13px auto 0;
|
||||
overflow: hidden;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
// Inline code snippets
|
||||
code,
|
||||
tt {
|
||||
padding: 0;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
margin: 0;
|
||||
font-size: 85%;
|
||||
background-color: rgba(0,0,0,0.04);
|
||||
border-radius: 3px; // don't add padding, gives scrollbars
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
letter-spacing: -0.2em; // this creates padding
|
||||
content: "\00a0";
|
||||
}
|
||||
|
||||
br { display: none; }
|
||||
}
|
||||
|
||||
del code { text-decoration: inherit; }
|
||||
|
||||
// Code tags within code blocks (<pre>s)
|
||||
pre > code {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 100%;
|
||||
word-break: normal;
|
||||
white-space: pre;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
margin-bottom: @margin;
|
||||
}
|
||||
|
||||
.highlight pre,
|
||||
pre {
|
||||
padding: @margin;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: #f7f7f7;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.highlight pre {
|
||||
margin-bottom: 0;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
pre {
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
pre code,
|
||||
pre tt {
|
||||
display: inline;
|
||||
max-width: initial;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: initial;
|
||||
line-height: inherit;
|
||||
word-wrap: normal;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: normal;
|
||||
}
|
||||
}
|
||||
|
||||
kbd {
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
font-size: 11px;
|
||||
line-height: 10px;
|
||||
color: #555;
|
||||
vertical-align: middle;
|
||||
background-color: #fcfcfc;
|
||||
border: solid 1px #ccc;
|
||||
border-bottom-color: #bbb;
|
||||
border-radius: 3px;
|
||||
box-shadow: inset 0 -1px 0 #bbb;
|
||||
}
|
||||
}
|
18
packages/markdown-preview/keymaps/markdown-preview.cson
Normal file
18
packages/markdown-preview/keymaps/markdown-preview.cson
Normal file
@ -0,0 +1,18 @@
|
||||
'atom-text-editor':
|
||||
'ctrl-shift-m': 'markdown-preview:toggle'
|
||||
|
||||
'.platform-darwin .markdown-preview':
|
||||
'cmd-a': 'markdown-preview:select-all'
|
||||
'cmd-+': 'markdown-preview:zoom-in'
|
||||
'cmd-=': 'markdown-preview:zoom-in'
|
||||
'cmd--': 'markdown-preview:zoom-out'
|
||||
'cmd-_': 'markdown-preview:zoom-out'
|
||||
'cmd-0': 'markdown-preview:reset-zoom'
|
||||
|
||||
'.platform-win32 .markdown-preview, .platform-linux .markdown-preview':
|
||||
'ctrl-a': 'markdown-preview:select-all'
|
||||
'ctrl-+': 'markdown-preview:zoom-in'
|
||||
'ctrl-=': 'markdown-preview:zoom-in'
|
||||
'ctrl--': 'markdown-preview:zoom-out'
|
||||
'ctrl-_': 'markdown-preview:zoom-out'
|
||||
'ctrl-0': 'markdown-preview:reset-zoom'
|
50
packages/markdown-preview/lib/extension-helper.js
Normal file
50
packages/markdown-preview/lib/extension-helper.js
Normal file
@ -0,0 +1,50 @@
|
||||
const scopesByFenceName = {
|
||||
bash: 'source.shell',
|
||||
sh: 'source.shell',
|
||||
powershell: 'source.powershell',
|
||||
ps1: 'source.powershell',
|
||||
c: 'source.c',
|
||||
'c++': 'source.cpp',
|
||||
cpp: 'source.cpp',
|
||||
coffee: 'source.coffee',
|
||||
'coffee-script': 'source.coffee',
|
||||
coffeescript: 'source.coffee',
|
||||
cs: 'source.cs',
|
||||
csharp: 'source.cs',
|
||||
css: 'source.css',
|
||||
sass: 'source.sass',
|
||||
scss: 'source.css.scss',
|
||||
erlang: 'source.erl',
|
||||
go: 'source.go',
|
||||
html: 'text.html.basic',
|
||||
java: 'source.java',
|
||||
javascript: 'source.js',
|
||||
js: 'source.js',
|
||||
json: 'source.json',
|
||||
less: 'source.less',
|
||||
mustache: 'text.html.mustache',
|
||||
objc: 'source.objc',
|
||||
'objective-c': 'source.objc',
|
||||
php: 'text.html.php',
|
||||
py: 'source.python',
|
||||
python: 'source.python',
|
||||
rb: 'source.ruby',
|
||||
ruby: 'source.ruby',
|
||||
text: 'text.plain',
|
||||
toml: 'source.toml',
|
||||
ts: 'source.ts',
|
||||
typescript: 'source.ts',
|
||||
xml: 'text.xml',
|
||||
yaml: 'source.yaml',
|
||||
yml: 'source.yaml'
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
scopeForFenceName (fenceName) {
|
||||
fenceName = fenceName.toLowerCase()
|
||||
|
||||
return scopesByFenceName.hasOwnProperty(fenceName)
|
||||
? scopesByFenceName[fenceName]
|
||||
: `source.${fenceName}`
|
||||
}
|
||||
}
|
228
packages/markdown-preview/lib/main.js
Normal file
228
packages/markdown-preview/lib/main.js
Normal file
@ -0,0 +1,228 @@
|
||||
const fs = require('fs-plus')
|
||||
const { CompositeDisposable } = require('atom')
|
||||
|
||||
let MarkdownPreviewView = null
|
||||
let renderer = null
|
||||
|
||||
const isMarkdownPreviewView = function (object) {
|
||||
if (MarkdownPreviewView == null) {
|
||||
MarkdownPreviewView = require('./markdown-preview-view')
|
||||
}
|
||||
return object instanceof MarkdownPreviewView
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
activate () {
|
||||
this.disposables = new CompositeDisposable()
|
||||
this.commandSubscriptions = new CompositeDisposable()
|
||||
|
||||
this.disposables.add(
|
||||
atom.config.observe('markdown-preview.grammars', grammars => {
|
||||
this.commandSubscriptions.dispose()
|
||||
this.commandSubscriptions = new CompositeDisposable()
|
||||
|
||||
if (grammars == null) {
|
||||
grammars = []
|
||||
}
|
||||
|
||||
for (const grammar of grammars.map(grammar =>
|
||||
grammar.replace(/\./g, ' ')
|
||||
)) {
|
||||
this.commandSubscriptions.add(
|
||||
atom.commands.add(`atom-text-editor[data-grammar='${grammar}']`, {
|
||||
'markdown-preview:toggle': () => this.toggle(),
|
||||
'markdown-preview:copy-html': {
|
||||
displayName: 'Markdown Preview: Copy HTML',
|
||||
didDispatch: () => this.copyHTML()
|
||||
},
|
||||
'markdown-preview:save-as-html': {
|
||||
displayName: 'Markdown Preview: Save as HTML',
|
||||
didDispatch: () => this.saveAsHTML()
|
||||
},
|
||||
'markdown-preview:toggle-break-on-single-newline': () => {
|
||||
const keyPath = 'markdown-preview.breakOnSingleNewline'
|
||||
atom.config.set(keyPath, !atom.config.get(keyPath))
|
||||
},
|
||||
'markdown-preview:toggle-github-style': () => {
|
||||
const keyPath = 'markdown-preview.useGitHubStyle'
|
||||
atom.config.set(keyPath, !atom.config.get(keyPath))
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
const previewFile = this.previewFile.bind(this)
|
||||
for (const extension of [
|
||||
'markdown',
|
||||
'md',
|
||||
'mdown',
|
||||
'mkd',
|
||||
'mkdown',
|
||||
'ron',
|
||||
'txt'
|
||||
]) {
|
||||
this.disposables.add(
|
||||
atom.commands.add(
|
||||
`.tree-view .file .name[data-name$=\\.${extension}]`,
|
||||
'markdown-preview:preview-file',
|
||||
previewFile
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
this.disposables.add(
|
||||
atom.workspace.addOpener(uriToOpen => {
|
||||
let [protocol, path] = uriToOpen.split('://')
|
||||
if (protocol !== 'markdown-preview') {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
path = decodeURI(path)
|
||||
} catch (error) {
|
||||
return
|
||||
}
|
||||
|
||||
if (path.startsWith('editor/')) {
|
||||
return this.createMarkdownPreviewView({ editorId: path.substring(7) })
|
||||
} else {
|
||||
return this.createMarkdownPreviewView({ filePath: path })
|
||||
}
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
deactivate () {
|
||||
this.disposables.dispose()
|
||||
this.commandSubscriptions.dispose()
|
||||
},
|
||||
|
||||
createMarkdownPreviewView (state) {
|
||||
if (state.editorId || fs.isFileSync(state.filePath)) {
|
||||
if (MarkdownPreviewView == null) {
|
||||
MarkdownPreviewView = require('./markdown-preview-view')
|
||||
}
|
||||
return new MarkdownPreviewView(state)
|
||||
}
|
||||
},
|
||||
|
||||
toggle () {
|
||||
if (isMarkdownPreviewView(atom.workspace.getActivePaneItem())) {
|
||||
atom.workspace.destroyActivePaneItem()
|
||||
return
|
||||
}
|
||||
|
||||
const editor = atom.workspace.getActiveTextEditor()
|
||||
if (editor == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const grammars = atom.config.get('markdown-preview.grammars') || []
|
||||
if (!grammars.includes(editor.getGrammar().scopeName)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.removePreviewForEditor(editor)) {
|
||||
return this.addPreviewForEditor(editor)
|
||||
}
|
||||
},
|
||||
|
||||
uriForEditor (editor) {
|
||||
return `markdown-preview://editor/${editor.id}`
|
||||
},
|
||||
|
||||
removePreviewForEditor (editor) {
|
||||
const uri = this.uriForEditor(editor)
|
||||
const previewPane = atom.workspace.paneForURI(uri)
|
||||
if (previewPane != null) {
|
||||
previewPane.destroyItem(previewPane.itemForURI(uri))
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
addPreviewForEditor (editor) {
|
||||
const uri = this.uriForEditor(editor)
|
||||
const previousActivePane = atom.workspace.getActivePane()
|
||||
const options = { searchAllPanes: true }
|
||||
if (atom.config.get('markdown-preview.openPreviewInSplitPane')) {
|
||||
options.split = 'right'
|
||||
}
|
||||
|
||||
return atom.workspace
|
||||
.open(uri, options)
|
||||
.then(function (markdownPreviewView) {
|
||||
if (isMarkdownPreviewView(markdownPreviewView)) {
|
||||
previousActivePane.activate()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
previewFile ({ target }) {
|
||||
const filePath = target.dataset.path
|
||||
if (!filePath) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const editor of atom.workspace.getTextEditors()) {
|
||||
if (editor.getPath() === filePath) {
|
||||
return this.addPreviewForEditor(editor)
|
||||
}
|
||||
}
|
||||
|
||||
atom.workspace.open(`markdown-preview://${encodeURI(filePath)}`, {
|
||||
searchAllPanes: true
|
||||
})
|
||||
},
|
||||
|
||||
async copyHTML () {
|
||||
const editor = atom.workspace.getActiveTextEditor()
|
||||
if (editor == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (renderer == null) {
|
||||
renderer = require('./renderer')
|
||||
}
|
||||
const text = editor.getSelectedText() || editor.getText()
|
||||
const html = await renderer.toHTML(
|
||||
text,
|
||||
editor.getPath(),
|
||||
editor.getGrammar()
|
||||
)
|
||||
|
||||
atom.clipboard.write(html)
|
||||
},
|
||||
|
||||
saveAsHTML () {
|
||||
const activePaneItem = atom.workspace.getActivePaneItem()
|
||||
if (isMarkdownPreviewView(activePaneItem)) {
|
||||
atom.workspace.getActivePane().saveItemAs(activePaneItem)
|
||||
return
|
||||
}
|
||||
|
||||
const editor = atom.workspace.getActiveTextEditor()
|
||||
if (editor == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const grammars = atom.config.get('markdown-preview.grammars') || []
|
||||
if (!grammars.includes(editor.getGrammar().scopeName)) {
|
||||
return
|
||||
}
|
||||
|
||||
const uri = this.uriForEditor(editor)
|
||||
const markdownPreviewPane = atom.workspace.paneForURI(uri)
|
||||
const markdownPreviewPaneItem =
|
||||
markdownPreviewPane != null
|
||||
? markdownPreviewPane.itemForURI(uri)
|
||||
: undefined
|
||||
|
||||
if (isMarkdownPreviewView(markdownPreviewPaneItem)) {
|
||||
return markdownPreviewPane.saveItemAs(markdownPreviewPaneItem)
|
||||
}
|
||||
}
|
||||
}
|
513
packages/markdown-preview/lib/markdown-preview-view.js
Normal file
513
packages/markdown-preview/lib/markdown-preview-view.js
Normal file
@ -0,0 +1,513 @@
|
||||
const path = require('path')
|
||||
|
||||
const { Emitter, Disposable, CompositeDisposable, File } = require('atom')
|
||||
const _ = require('underscore-plus')
|
||||
const fs = require('fs-plus')
|
||||
|
||||
const renderer = require('./renderer')
|
||||
|
||||
module.exports = class MarkdownPreviewView {
|
||||
static deserialize (params) {
|
||||
return new MarkdownPreviewView(params)
|
||||
}
|
||||
|
||||
constructor ({ editorId, filePath }) {
|
||||
this.editorId = editorId
|
||||
this.filePath = filePath
|
||||
this.element = document.createElement('div')
|
||||
this.element.classList.add('markdown-preview')
|
||||
this.element.tabIndex = -1
|
||||
this.emitter = new Emitter()
|
||||
this.loaded = false
|
||||
this.disposables = new CompositeDisposable()
|
||||
this.registerScrollCommands()
|
||||
if (this.editorId != null) {
|
||||
this.resolveEditor(this.editorId)
|
||||
} else if (atom.packages.hasActivatedInitialPackages()) {
|
||||
this.subscribeToFilePath(this.filePath)
|
||||
} else {
|
||||
this.disposables.add(
|
||||
atom.packages.onDidActivateInitialPackages(() => {
|
||||
this.subscribeToFilePath(this.filePath)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
serialize () {
|
||||
return {
|
||||
deserializer: 'MarkdownPreviewView',
|
||||
filePath: this.getPath() != null ? this.getPath() : this.filePath,
|
||||
editorId: this.editorId
|
||||
}
|
||||
}
|
||||
|
||||
copy () {
|
||||
return new MarkdownPreviewView({
|
||||
editorId: this.editorId,
|
||||
filePath: this.getPath() != null ? this.getPath() : this.filePath
|
||||
})
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.disposables.dispose()
|
||||
this.element.remove()
|
||||
}
|
||||
|
||||
registerScrollCommands () {
|
||||
this.disposables.add(
|
||||
atom.commands.add(this.element, {
|
||||
'core:move-up': () => {
|
||||
this.element.scrollTop -= document.body.offsetHeight / 20
|
||||
},
|
||||
'core:move-down': () => {
|
||||
this.element.scrollTop += document.body.offsetHeight / 20
|
||||
},
|
||||
'core:page-up': () => {
|
||||
this.element.scrollTop -= this.element.offsetHeight
|
||||
},
|
||||
'core:page-down': () => {
|
||||
this.element.scrollTop += this.element.offsetHeight
|
||||
},
|
||||
'core:move-to-top': () => {
|
||||
this.element.scrollTop = 0
|
||||
},
|
||||
'core:move-to-bottom': () => {
|
||||
this.element.scrollTop = this.element.scrollHeight
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
onDidChangeTitle (callback) {
|
||||
return this.emitter.on('did-change-title', callback)
|
||||
}
|
||||
|
||||
onDidChangeModified (callback) {
|
||||
// No op to suppress deprecation warning
|
||||
return new Disposable()
|
||||
}
|
||||
|
||||
onDidChangeMarkdown (callback) {
|
||||
return this.emitter.on('did-change-markdown', callback)
|
||||
}
|
||||
|
||||
subscribeToFilePath (filePath) {
|
||||
this.file = new File(filePath)
|
||||
this.emitter.emit('did-change-title')
|
||||
this.disposables.add(
|
||||
this.file.onDidRename(() => this.emitter.emit('did-change-title'))
|
||||
)
|
||||
this.handleEvents()
|
||||
return this.renderMarkdown()
|
||||
}
|
||||
|
||||
resolveEditor (editorId) {
|
||||
const resolve = () => {
|
||||
this.editor = this.editorForId(editorId)
|
||||
|
||||
if (this.editor != null) {
|
||||
this.emitter.emit('did-change-title')
|
||||
this.disposables.add(
|
||||
this.editor.onDidDestroy(() =>
|
||||
this.subscribeToFilePath(this.getPath())
|
||||
)
|
||||
)
|
||||
this.handleEvents()
|
||||
this.renderMarkdown()
|
||||
} else {
|
||||
this.subscribeToFilePath(this.filePath)
|
||||
}
|
||||
}
|
||||
|
||||
if (atom.packages.hasActivatedInitialPackages()) {
|
||||
resolve()
|
||||
} else {
|
||||
this.disposables.add(atom.packages.onDidActivateInitialPackages(resolve))
|
||||
}
|
||||
}
|
||||
|
||||
editorForId (editorId) {
|
||||
for (const editor of atom.workspace.getTextEditors()) {
|
||||
if (editor.id != null && editor.id.toString() === editorId.toString()) {
|
||||
return editor
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
handleEvents () {
|
||||
const lazyRenderMarkdown = _.debounce(() => this.renderMarkdown(), 250)
|
||||
this.disposables.add(
|
||||
atom.grammars.onDidAddGrammar(() => lazyRenderMarkdown())
|
||||
)
|
||||
if (typeof atom.grammars.onDidRemoveGrammar === 'function') {
|
||||
this.disposables.add(
|
||||
atom.grammars.onDidRemoveGrammar(() => lazyRenderMarkdown())
|
||||
)
|
||||
} else {
|
||||
// TODO: Remove onDidUpdateGrammar hook once onDidRemoveGrammar is released
|
||||
this.disposables.add(
|
||||
atom.grammars.onDidUpdateGrammar(() => lazyRenderMarkdown())
|
||||
)
|
||||
}
|
||||
|
||||
atom.commands.add(this.element, {
|
||||
'core:copy': event => {
|
||||
event.stopPropagation()
|
||||
return this.copyToClipboard()
|
||||
},
|
||||
'markdown-preview:select-all': () => {
|
||||
this.selectAll()
|
||||
},
|
||||
'markdown-preview:zoom-in': () => {
|
||||
const zoomLevel = parseFloat(getComputedStyle(this.element).zoom)
|
||||
this.element.style.zoom = zoomLevel + 0.1
|
||||
},
|
||||
'markdown-preview:zoom-out': () => {
|
||||
const zoomLevel = parseFloat(getComputedStyle(this.element).zoom)
|
||||
this.element.style.zoom = zoomLevel - 0.1
|
||||
},
|
||||
'markdown-preview:reset-zoom': () => {
|
||||
this.element.style.zoom = 1
|
||||
},
|
||||
'markdown-preview:toggle-break-on-single-newline' () {
|
||||
const keyPath = 'markdown-preview.breakOnSingleNewline'
|
||||
atom.config.set(keyPath, !atom.config.get(keyPath))
|
||||
},
|
||||
'markdown-preview:toggle-github-style' () {
|
||||
const keyPath = 'markdown-preview.useGitHubStyle'
|
||||
atom.config.set(keyPath, !atom.config.get(keyPath))
|
||||
}
|
||||
})
|
||||
|
||||
const changeHandler = () => {
|
||||
this.renderMarkdown()
|
||||
|
||||
const pane = atom.workspace.paneForItem(this)
|
||||
if (pane != null && pane !== atom.workspace.getActivePane()) {
|
||||
pane.activateItem(this)
|
||||
}
|
||||
}
|
||||
|
||||
if (this.file) {
|
||||
this.disposables.add(this.file.onDidChange(changeHandler))
|
||||
} else if (this.editor) {
|
||||
this.disposables.add(
|
||||
this.editor.getBuffer().onDidStopChanging(function () {
|
||||
if (atom.config.get('markdown-preview.liveUpdate')) {
|
||||
changeHandler()
|
||||
}
|
||||
})
|
||||
)
|
||||
this.disposables.add(
|
||||
this.editor.onDidChangePath(() => this.emitter.emit('did-change-title'))
|
||||
)
|
||||
this.disposables.add(
|
||||
this.editor.getBuffer().onDidSave(function () {
|
||||
if (!atom.config.get('markdown-preview.liveUpdate')) {
|
||||
changeHandler()
|
||||
}
|
||||
})
|
||||
)
|
||||
this.disposables.add(
|
||||
this.editor.getBuffer().onDidReload(function () {
|
||||
if (!atom.config.get('markdown-preview.liveUpdate')) {
|
||||
changeHandler()
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
this.disposables.add(
|
||||
atom.config.onDidChange(
|
||||
'markdown-preview.breakOnSingleNewline',
|
||||
changeHandler
|
||||
)
|
||||
)
|
||||
|
||||
this.disposables.add(
|
||||
atom.config.observe('markdown-preview.useGitHubStyle', useGitHubStyle => {
|
||||
if (useGitHubStyle) {
|
||||
this.element.setAttribute('data-use-github-style', '')
|
||||
} else {
|
||||
this.element.removeAttribute('data-use-github-style')
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
document.onselectionchange = () => {
|
||||
const selection = window.getSelection()
|
||||
const selectedNode = selection.baseNode
|
||||
if (
|
||||
selectedNode === null ||
|
||||
this.element === selectedNode ||
|
||||
this.element.contains(selectedNode)
|
||||
) {
|
||||
if (selection.isCollapsed) {
|
||||
this.element.classList.remove('has-selection')
|
||||
} else {
|
||||
this.element.classList.add('has-selection')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderMarkdown () {
|
||||
if (!this.loaded) {
|
||||
this.showLoading()
|
||||
}
|
||||
return this.getMarkdownSource()
|
||||
.then(source => {
|
||||
if (source != null) {
|
||||
return this.renderMarkdownText(source)
|
||||
}
|
||||
})
|
||||
.catch(reason => this.showError({ message: reason }))
|
||||
}
|
||||
|
||||
getMarkdownSource () {
|
||||
if (this.file && this.file.getPath()) {
|
||||
return this.file
|
||||
.read()
|
||||
.then(source => {
|
||||
if (source === null) {
|
||||
return Promise.reject(
|
||||
new Error(`${this.file.getBaseName()} could not be found`)
|
||||
)
|
||||
} else {
|
||||
return Promise.resolve(source)
|
||||
}
|
||||
})
|
||||
.catch(reason => Promise.reject(reason))
|
||||
} else if (this.editor != null) {
|
||||
return Promise.resolve(this.editor.getText())
|
||||
} else {
|
||||
return Promise.reject(new Error('No editor found'))
|
||||
}
|
||||
}
|
||||
|
||||
async getHTML () {
|
||||
const source = await this.getMarkdownSource()
|
||||
|
||||
if (source == null) {
|
||||
return
|
||||
}
|
||||
|
||||
return renderer.toHTML(source, this.getPath(), this.getGrammar())
|
||||
}
|
||||
|
||||
async renderMarkdownText (text) {
|
||||
const { scrollTop } = this.element
|
||||
|
||||
try {
|
||||
const domFragment = await renderer.toDOMFragment(
|
||||
text,
|
||||
this.getPath(),
|
||||
this.getGrammar()
|
||||
)
|
||||
|
||||
this.loading = false
|
||||
this.loaded = true
|
||||
this.element.textContent = ''
|
||||
this.element.appendChild(domFragment)
|
||||
this.emitter.emit('did-change-markdown')
|
||||
this.element.scrollTop = scrollTop
|
||||
} catch (error) {
|
||||
this.showError(error)
|
||||
}
|
||||
}
|
||||
|
||||
getTitle () {
|
||||
if (this.file != null && this.getPath() != null) {
|
||||
return `${path.basename(this.getPath())} Preview`
|
||||
} else if (this.editor != null) {
|
||||
return `${this.editor.getTitle()} Preview`
|
||||
} else {
|
||||
return 'Markdown Preview'
|
||||
}
|
||||
}
|
||||
|
||||
getIconName () {
|
||||
return 'markdown'
|
||||
}
|
||||
|
||||
getURI () {
|
||||
if (this.file != null) {
|
||||
return `markdown-preview://${this.getPath()}`
|
||||
} else {
|
||||
return `markdown-preview://editor/${this.editorId}`
|
||||
}
|
||||
}
|
||||
|
||||
getPath () {
|
||||
if (this.file != null) {
|
||||
return this.file.getPath()
|
||||
} else if (this.editor != null) {
|
||||
return this.editor.getPath()
|
||||
}
|
||||
}
|
||||
|
||||
getGrammar () {
|
||||
return this.editor != null ? this.editor.getGrammar() : undefined
|
||||
}
|
||||
|
||||
getDocumentStyleSheets () {
|
||||
// This function exists so we can stub it
|
||||
return document.styleSheets
|
||||
}
|
||||
|
||||
getTextEditorStyles () {
|
||||
const textEditorStyles = document.createElement('atom-styles')
|
||||
textEditorStyles.initialize(atom.styles)
|
||||
textEditorStyles.setAttribute('context', 'atom-text-editor')
|
||||
document.body.appendChild(textEditorStyles)
|
||||
|
||||
// Extract style elements content
|
||||
return Array.prototype.slice
|
||||
.apply(textEditorStyles.childNodes)
|
||||
.map(styleElement => styleElement.innerText)
|
||||
}
|
||||
|
||||
getMarkdownPreviewCSS () {
|
||||
const markdownPreviewRules = []
|
||||
const ruleRegExp = /\.markdown-preview/
|
||||
const cssUrlRegExp = /url\(atom:\/\/markdown-preview\/assets\/(.*)\)/
|
||||
|
||||
for (const stylesheet of this.getDocumentStyleSheets()) {
|
||||
if (stylesheet.rules != null) {
|
||||
for (const rule of stylesheet.rules) {
|
||||
// We only need `.markdown-review` css
|
||||
if (rule.selectorText && rule.selectorText.match(ruleRegExp)) {
|
||||
markdownPreviewRules.push(rule.cssText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return markdownPreviewRules
|
||||
.concat(this.getTextEditorStyles())
|
||||
.join('\n')
|
||||
.replace(/atom-text-editor/g, 'pre.editor-colors')
|
||||
.replace(/:host/g, '.host') // Remove shadow-dom :host selector causing problem on FF
|
||||
.replace(cssUrlRegExp, function (match, assetsName, offset, string) {
|
||||
// base64 encode assets
|
||||
const assetPath = path.join(__dirname, '../assets', assetsName)
|
||||
const originalData = fs.readFileSync(assetPath, 'binary')
|
||||
const base64Data = Buffer.from(originalData, 'binary').toString(
|
||||
'base64'
|
||||
)
|
||||
return `url('data:image/jpeg;base64,${base64Data}')`
|
||||
})
|
||||
}
|
||||
|
||||
showError (result) {
|
||||
this.element.textContent = ''
|
||||
const h2 = document.createElement('h2')
|
||||
h2.textContent = 'Previewing Markdown Failed'
|
||||
this.element.appendChild(h2)
|
||||
if (result) {
|
||||
const h3 = document.createElement('h3')
|
||||
h3.textContent = result.message
|
||||
this.element.appendChild(h3)
|
||||
}
|
||||
}
|
||||
|
||||
showLoading () {
|
||||
this.loading = true
|
||||
this.element.textContent = ''
|
||||
const div = document.createElement('div')
|
||||
div.classList.add('markdown-spinner')
|
||||
div.textContent = 'Loading Markdown\u2026'
|
||||
this.element.appendChild(div)
|
||||
}
|
||||
|
||||
selectAll () {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
|
||||
const selection = window.getSelection()
|
||||
selection.removeAllRanges()
|
||||
const range = document.createRange()
|
||||
range.selectNodeContents(this.element)
|
||||
selection.addRange(range)
|
||||
}
|
||||
|
||||
async copyToClipboard () {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
|
||||
const selection = window.getSelection()
|
||||
const selectedText = selection.toString()
|
||||
const selectedNode = selection.baseNode
|
||||
|
||||
// Use default copy event handler if there is selected text inside this view
|
||||
if (
|
||||
selectedText &&
|
||||
selectedNode != null &&
|
||||
(this.element === selectedNode || this.element.contains(selectedNode))
|
||||
) {
|
||||
atom.clipboard.write(selectedText)
|
||||
} else {
|
||||
try {
|
||||
const html = await this.getHTML()
|
||||
|
||||
atom.clipboard.write(html)
|
||||
} catch (error) {
|
||||
atom.notifications.addError('Copying Markdown as HTML failed', {
|
||||
dismissable: true,
|
||||
detail: error.message
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSaveDialogOptions () {
|
||||
let defaultPath = this.getPath()
|
||||
if (defaultPath) {
|
||||
defaultPath += '.html'
|
||||
} else {
|
||||
let projectPath
|
||||
defaultPath = 'untitled.md.html'
|
||||
if ((projectPath = atom.project.getPaths()[0])) {
|
||||
defaultPath = path.join(projectPath, defaultPath)
|
||||
}
|
||||
}
|
||||
|
||||
return { defaultPath }
|
||||
}
|
||||
|
||||
async saveAs (htmlFilePath) {
|
||||
if (this.loading) {
|
||||
atom.notifications.addWarning(
|
||||
'Please wait until the Markdown Preview has finished loading before saving'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const filePath = this.getPath()
|
||||
let title = 'Markdown to HTML'
|
||||
if (filePath) {
|
||||
title = path.parse(filePath).name
|
||||
}
|
||||
|
||||
const htmlBody = await this.getHTML()
|
||||
|
||||
const html =
|
||||
`\
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>${title}</title>
|
||||
<style>${this.getMarkdownPreviewCSS()}</style>
|
||||
</head>
|
||||
<body class='markdown-preview' data-use-github-style>${htmlBody}</body>
|
||||
</html>` + '\n' // Ensure trailing newline
|
||||
|
||||
fs.writeFileSync(htmlFilePath, html)
|
||||
return atom.workspace.open(htmlFilePath)
|
||||
}
|
||||
}
|
243
packages/markdown-preview/lib/renderer.js
Normal file
243
packages/markdown-preview/lib/renderer.js
Normal file
@ -0,0 +1,243 @@
|
||||
const { TextEditor } = require('atom')
|
||||
const path = require('path')
|
||||
const createDOMPurify = require('dompurify')
|
||||
const emoji = require('emoji-images')
|
||||
const fs = require('fs-plus')
|
||||
let marked = null // Defer until used
|
||||
let renderer = null
|
||||
let cheerio = null
|
||||
let yamlFrontMatter = null
|
||||
|
||||
const { scopeForFenceName } = require('./extension-helper')
|
||||
const { resourcePath } = atom.getLoadSettings()
|
||||
const packagePath = path.dirname(__dirname)
|
||||
|
||||
const emojiFolder = path.join(
|
||||
path.dirname(require.resolve('emoji-images')),
|
||||
'pngs'
|
||||
)
|
||||
|
||||
exports.toDOMFragment = async function (text, filePath, grammar, callback) {
|
||||
if (text == null) {
|
||||
text = ''
|
||||
}
|
||||
|
||||
const domFragment = render(text, filePath)
|
||||
|
||||
await highlightCodeBlocks(domFragment, grammar, makeAtomEditorNonInteractive)
|
||||
|
||||
return domFragment
|
||||
}
|
||||
|
||||
exports.toHTML = async function (text, filePath, grammar) {
|
||||
if (text == null) {
|
||||
text = ''
|
||||
}
|
||||
|
||||
const domFragment = render(text, filePath)
|
||||
const div = document.createElement('div')
|
||||
|
||||
div.appendChild(domFragment)
|
||||
document.body.appendChild(div)
|
||||
|
||||
await highlightCodeBlocks(div, grammar, convertAtomEditorToStandardElement)
|
||||
|
||||
const result = div.innerHTML
|
||||
div.remove()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var render = function (text, filePath) {
|
||||
if (marked == null || yamlFrontMatter == null || cheerio == null) {
|
||||
marked = require('marked')
|
||||
yamlFrontMatter = require('yaml-front-matter')
|
||||
cheerio = require('cheerio')
|
||||
|
||||
renderer = new marked.Renderer()
|
||||
renderer.listitem = function (text, isTask) {
|
||||
const listAttributes = isTask ? ' class="task-list-item"' : ''
|
||||
|
||||
return `<li ${listAttributes}>${text}</li>\n`
|
||||
}
|
||||
}
|
||||
|
||||
marked.setOptions({
|
||||
sanitize: false,
|
||||
breaks: atom.config.get('markdown-preview.breakOnSingleNewline'),
|
||||
renderer
|
||||
})
|
||||
|
||||
const { __content, ...vars } = yamlFrontMatter.loadFront(text)
|
||||
|
||||
let html = marked(renderYamlTable(vars) + __content)
|
||||
|
||||
// emoji-images is too aggressive, so replace images in monospace tags with the actual emoji text.
|
||||
const $ = cheerio.load(emoji(html, emojiFolder, 20))
|
||||
$('pre img').each((index, element) =>
|
||||
$(element).replaceWith($(element).attr('title'))
|
||||
)
|
||||
$('code img').each((index, element) =>
|
||||
$(element).replaceWith($(element).attr('title'))
|
||||
)
|
||||
|
||||
html = $.html()
|
||||
|
||||
html = createDOMPurify().sanitize(html, {
|
||||
ALLOW_UNKNOWN_PROTOCOLS: atom.config.get(
|
||||
'markdown-preview.allowUnsafeProtocols'
|
||||
)
|
||||
})
|
||||
|
||||
const template = document.createElement('template')
|
||||
template.innerHTML = html.trim()
|
||||
const fragment = template.content.cloneNode(true)
|
||||
|
||||
resolveImagePaths(fragment, filePath)
|
||||
|
||||
return fragment
|
||||
}
|
||||
|
||||
function renderYamlTable (variables) {
|
||||
const entries = Object.entries(variables)
|
||||
|
||||
if (!entries.length) {
|
||||
return ''
|
||||
}
|
||||
|
||||
const markdownRows = [
|
||||
entries.map(entry => entry[0]),
|
||||
entries.map(entry => '--'),
|
||||
entries.map(entry => entry[1])
|
||||
]
|
||||
|
||||
return (
|
||||
markdownRows.map(row => '| ' + row.join(' | ') + ' |').join('\n') + '\n'
|
||||
)
|
||||
}
|
||||
|
||||
var resolveImagePaths = function (element, filePath) {
|
||||
const [rootDirectory] = atom.project.relativizePath(filePath)
|
||||
|
||||
const result = []
|
||||
for (const img of element.querySelectorAll('img')) {
|
||||
// We use the raw attribute instead of the .src property because the value
|
||||
// of the property seems to be transformed in some cases.
|
||||
let src
|
||||
|
||||
if ((src = img.getAttribute('src'))) {
|
||||
if (src.match(/^(https?|atom):\/\//)) {
|
||||
continue
|
||||
}
|
||||
if (src.startsWith(process.resourcesPath)) {
|
||||
continue
|
||||
}
|
||||
if (src.startsWith(resourcePath)) {
|
||||
continue
|
||||
}
|
||||
if (src.startsWith(packagePath)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (src[0] === '/') {
|
||||
if (!fs.isFileSync(src)) {
|
||||
if (rootDirectory) {
|
||||
result.push((img.src = path.join(rootDirectory, src.substring(1))))
|
||||
} else {
|
||||
result.push(undefined)
|
||||
}
|
||||
} else {
|
||||
result.push(undefined)
|
||||
}
|
||||
} else {
|
||||
result.push((img.src = path.resolve(path.dirname(filePath), src)))
|
||||
}
|
||||
} else {
|
||||
result.push(undefined)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var highlightCodeBlocks = function (domFragment, grammar, editorCallback) {
|
||||
let defaultLanguage, fontFamily
|
||||
if (
|
||||
(grammar != null ? grammar.scopeName : undefined) === 'source.litcoffee'
|
||||
) {
|
||||
defaultLanguage = 'coffee'
|
||||
} else {
|
||||
defaultLanguage = 'text'
|
||||
}
|
||||
|
||||
if ((fontFamily = atom.config.get('editor.fontFamily'))) {
|
||||
for (const codeElement of domFragment.querySelectorAll('code')) {
|
||||
codeElement.style.fontFamily = fontFamily
|
||||
}
|
||||
}
|
||||
|
||||
const promises = []
|
||||
for (const preElement of domFragment.querySelectorAll('pre')) {
|
||||
const codeBlock =
|
||||
preElement.firstElementChild != null
|
||||
? preElement.firstElementChild
|
||||
: preElement
|
||||
const className = codeBlock.getAttribute('class')
|
||||
const fenceName =
|
||||
className != null ? className.replace(/^language-/, '') : defaultLanguage
|
||||
|
||||
const editor = new TextEditor({
|
||||
readonly: true,
|
||||
keyboardInputEnabled: false
|
||||
})
|
||||
const editorElement = editor.getElement()
|
||||
|
||||
preElement.classList.add('editor-colors', `lang-${fenceName}`)
|
||||
editorElement.setUpdatedSynchronously(true)
|
||||
preElement.innerHTML = ''
|
||||
preElement.parentNode.insertBefore(editorElement, preElement)
|
||||
editor.setText(codeBlock.textContent.replace(/\r?\n$/, ''))
|
||||
atom.grammars.assignLanguageMode(editor, scopeForFenceName(fenceName))
|
||||
editor.setVisible(true)
|
||||
|
||||
promises.push(editorCallback(editorElement, preElement))
|
||||
}
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
var makeAtomEditorNonInteractive = function (editorElement, preElement) {
|
||||
preElement.remove()
|
||||
editorElement.setAttributeNode(document.createAttribute('gutter-hidden')) // Hide gutter
|
||||
editorElement.removeAttribute('tabindex') // Make read-only
|
||||
|
||||
// Remove line decorations from code blocks.
|
||||
for (const cursorLineDecoration of editorElement.getModel()
|
||||
.cursorLineDecorations) {
|
||||
cursorLineDecoration.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
var convertAtomEditorToStandardElement = (editorElement, preElement) => {
|
||||
return new Promise(function (resolve) {
|
||||
const editor = editorElement.getModel()
|
||||
const done = () =>
|
||||
editor.component.getNextUpdatePromise().then(function () {
|
||||
for (const line of editorElement.querySelectorAll(
|
||||
'.line:not(.dummy)'
|
||||
)) {
|
||||
const line2 = document.createElement('div')
|
||||
line2.className = 'line'
|
||||
line2.innerHTML = line.firstChild.innerHTML
|
||||
preElement.appendChild(line2)
|
||||
}
|
||||
editorElement.remove()
|
||||
resolve()
|
||||
})
|
||||
const languageMode = editor.getBuffer().getLanguageMode()
|
||||
if (languageMode.fullyTokenized || languageMode.tree) {
|
||||
done()
|
||||
} else {
|
||||
editor.onDidTokenize(done)
|
||||
}
|
||||
})
|
||||
}
|
37
packages/markdown-preview/menus/markdown-preview.cson
Normal file
37
packages/markdown-preview/menus/markdown-preview.cson
Normal file
@ -0,0 +1,37 @@
|
||||
menu: [
|
||||
label: 'Packages'
|
||||
submenu: [
|
||||
label: 'Markdown Preview'
|
||||
submenu: [
|
||||
{label: 'Toggle Preview', command: 'markdown-preview:toggle'}
|
||||
{label: 'Toggle Break on Single Newline', command: 'markdown-preview:toggle-break-on-single-newline'}
|
||||
{label: 'Toggle GitHub Style', command: 'markdown-preview:toggle-github-style'}
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
'context-menu':
|
||||
'.markdown-preview': [
|
||||
{label: 'Select All', command: 'markdown-preview:select-all'}
|
||||
{label: 'Save As HTML\u2026', command: 'core:save-as'}
|
||||
]
|
||||
'.markdown-preview.has-selection': [
|
||||
{label: 'Copy', command: 'core:copy'}
|
||||
]
|
||||
'.markdown-preview:not(.has-selection)': [
|
||||
{label: 'Copy As HTML', command: 'core:copy'}
|
||||
]
|
||||
'.tree-view .file .name[data-name$=\\.markdown]':
|
||||
[{label: 'Markdown Preview', command: 'markdown-preview:preview-file'}]
|
||||
'.tree-view .file .name[data-name$=\\.md]':
|
||||
[{label: 'Markdown Preview', command: 'markdown-preview:preview-file'}]
|
||||
'.tree-view .file .name[data-name$=\\.mdown]':
|
||||
[{label: 'Markdown Preview', command: 'markdown-preview:preview-file'}]
|
||||
'.tree-view .file .name[data-name$=\\.mkd]':
|
||||
[{label: 'Markdown Preview', command: 'markdown-preview:preview-file'}]
|
||||
'.tree-view .file .name[data-name$=\\.mkdown]':
|
||||
[{label: 'Markdown Preview', command: 'markdown-preview:preview-file'}]
|
||||
'.tree-view .file .name[data-name$=\\.ron]':
|
||||
[{label: 'Markdown Preview', command: 'markdown-preview:preview-file'}]
|
||||
'.tree-view .file .name[data-name$=\\.txt]':
|
||||
[{label: 'Markdown Preview', command: 'markdown-preview:preview-file'}]
|
426
packages/markdown-preview/package-lock.json
generated
Normal file
426
packages/markdown-preview/package-lock.json
generated
Normal file
@ -0,0 +1,426 @@
|
||||
{
|
||||
"name": "markdown-preview",
|
||||
"version": "0.160.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "markdown-preview",
|
||||
"version": "0.160.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"dompurify": "^2.0.17",
|
||||
"emoji-images": "^0.1.1",
|
||||
"fs-plus": "^3.0.0",
|
||||
"marked": "^0.7.0",
|
||||
"underscore-plus": "^1.0.0",
|
||||
"yaml-front-matter": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"temp": "^0.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "11.13.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.7.tgz",
|
||||
"integrity": "sha512-suFHr6hcA9mp8vFrZTgrmqW2ZU3mbWsryQtQlY/QvwTISCw7nw/j+bCQPPohqmskhmqa5wLNuMHTTsc+xf1MQg=="
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dependencies": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
|
||||
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio": {
|
||||
"version": "1.0.0-rc.3",
|
||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.3.tgz",
|
||||
"integrity": "sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA==",
|
||||
"dependencies": {
|
||||
"css-select": "~1.2.0",
|
||||
"dom-serializer": "~0.1.1",
|
||||
"entities": "~1.1.1",
|
||||
"htmlparser2": "^3.9.1",
|
||||
"lodash": "^4.15.0",
|
||||
"parse5": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-1.0.0.tgz",
|
||||
"integrity": "sha1-XmqI5wcP9ZCINurRkWlUjDD5C80=",
|
||||
"engines": {
|
||||
"node": ">= 0.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
|
||||
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
|
||||
"dependencies": {
|
||||
"boolbase": "~1.0.0",
|
||||
"css-what": "2.1",
|
||||
"domutils": "1.5.1",
|
||||
"nth-check": "~1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
|
||||
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz",
|
||||
"integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^1.3.0",
|
||||
"entities": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
|
||||
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
|
||||
"integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
|
||||
"dependencies": {
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "2.0.17",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.0.17.tgz",
|
||||
"integrity": "sha512-nNwwJfW55r8akD8MSFz6k75bzyT2y6JEa1O3JrZFBf+Y5R9JXXU4OsRl0B9hKoPgHTw2b7ER5yJ5Md97MMUJPg=="
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
|
||||
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
|
||||
"dependencies": {
|
||||
"dom-serializer": "0",
|
||||
"domelementtype": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-images": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/emoji-images/-/emoji-images-0.1.1.tgz",
|
||||
"integrity": "sha1-+ZLccgksA/vgkoJ2MZh+s7Exm2c="
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
|
||||
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
|
||||
},
|
||||
"node_modules/fs-plus": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.1.1.tgz",
|
||||
"integrity": "sha512-Se2PJdOWXqos1qVTkvqqjb0CSnfBnwwD+pq+z4ksT+e97mEShod/hrNg0TRCCsXPbJzcIq+NuzQhigunMWMJUA==",
|
||||
"dependencies": {
|
||||
"async": "^1.5.2",
|
||||
"mkdirp": "^0.5.1",
|
||||
"rimraf": "^2.5.2",
|
||||
"underscore-plus": "1.x"
|
||||
}
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
|
||||
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "3.10.1",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||
"integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^1.3.1",
|
||||
"domhandler": "^2.3.0",
|
||||
"domutils": "^1.5.1",
|
||||
"entities": "^1.1.1",
|
||||
"inherits": "^2.0.1",
|
||||
"readable-stream": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/inherits": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||
"dependencies": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml/node_modules/esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"bin": {
|
||||
"esparse": "bin/esparse.js",
|
||||
"esvalidate": "bin/esvalidate.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-0.7.0.tgz",
|
||||
"integrity": "sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==",
|
||||
"bin": {
|
||||
"marked": "bin/marked"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
|
||||
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
|
||||
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
|
||||
"deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
|
||||
"dependencies": {
|
||||
"minimist": "0.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz",
|
||||
"integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==",
|
||||
"dependencies": {
|
||||
"boolbase": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/os-tmpdir": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
|
||||
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
|
||||
"integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/readable-stream": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz",
|
||||
"integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
|
||||
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"node_modules/string_decoder": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz",
|
||||
"integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==",
|
||||
"dependencies": {
|
||||
"safe-buffer": "~5.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/temp": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz",
|
||||
"integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=",
|
||||
"dev": true,
|
||||
"engines": [
|
||||
"node >=0.8.0"
|
||||
],
|
||||
"dependencies": {
|
||||
"os-tmpdir": "^1.0.0",
|
||||
"rimraf": "~2.2.6"
|
||||
}
|
||||
},
|
||||
"node_modules/temp/node_modules/rimraf": {
|
||||
"version": "2.2.8",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz",
|
||||
"integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/underscore": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.1.tgz",
|
||||
"integrity": "sha512-hzSoAVtJF+3ZtiFX0VgfFPHEDRm7Y/QPjGyNo4TVdnDTdft3tr8hEkD25a1jC+TjTuE7tkHGKkhwCgs9dgBB2g=="
|
||||
},
|
||||
"node_modules/underscore-plus": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.7.0.tgz",
|
||||
"integrity": "sha512-A3BEzkeicFLnr+U/Q3EyWwJAQPbA19mtZZ4h+lLq3ttm9kn8WC4R3YpuJZEXmWdLjYP47Zc8aLZm9kwdv+zzvA==",
|
||||
"dependencies": {
|
||||
"underscore": "^1.9.1"
|
||||
}
|
||||
},
|
||||
"node_modules/util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"node_modules/yaml-front-matter": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml-front-matter/-/yaml-front-matter-4.0.0.tgz",
|
||||
"integrity": "sha1-EcN4xU6sMGGoLLr2k6abTkxE9IQ=",
|
||||
"dependencies": {
|
||||
"commander": "1.0.0",
|
||||
"js-yaml": "^3.10.0"
|
||||
},
|
||||
"bin": {
|
||||
"yaml-front-matter": "bin/js-yaml-front.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
66
packages/markdown-preview/package.json
Normal file
66
packages/markdown-preview/package.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"name": "markdown-preview",
|
||||
"version": "0.160.2",
|
||||
"main": "./lib/main",
|
||||
"description": "Open a rendered version of the Markdown in the current editor with `ctrl-shift-m`.",
|
||||
"repository": "https://github.com/pulsar-edit/pulsar",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"dompurify": "^2.0.17",
|
||||
"emoji-images": "^0.1.1",
|
||||
"fs-plus": "^3.0.0",
|
||||
"marked": "^0.7.0",
|
||||
"underscore-plus": "^1.0.0",
|
||||
"yaml-front-matter": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"temp": "^0.8.1"
|
||||
},
|
||||
"deserializers": {
|
||||
"MarkdownPreviewView": "createMarkdownPreviewView"
|
||||
},
|
||||
"configSchema": {
|
||||
"breakOnSingleNewline": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "In Markdown, a single newline character doesn't cause a line break in the generated HTML. In GitHub Flavored Markdown, that is not true. Enable this config option to insert line breaks in rendered HTML for single newlines in Markdown source."
|
||||
},
|
||||
"liveUpdate": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Re-render the preview as the contents of the source changes, without requiring the source buffer to be saved. If disabled, the preview is re-rendered only when the buffer is saved to disk."
|
||||
},
|
||||
"openPreviewInSplitPane": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Open the preview in a split pane. If disabled, the preview is opened in a new tab in the same pane."
|
||||
},
|
||||
"allowUnsafeProtocols": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Allow HTML attributes to use protocols normally considered unsafe such as `file://` and absolute paths on Windows."
|
||||
},
|
||||
"grammars": {
|
||||
"type": "array",
|
||||
"default": [
|
||||
"source.gfm",
|
||||
"source.litcoffee",
|
||||
"text.html.basic",
|
||||
"text.md",
|
||||
"text.plain",
|
||||
"text.plain.null-grammar"
|
||||
],
|
||||
"description": "List of scopes for languages for which previewing is enabled. See [this README](https://github.com/pulsar-edit/spell-check#readme) for more information on finding the correct scope for a specific language."
|
||||
},
|
||||
"useGitHubStyle": {
|
||||
"title": "Use GitHub.com style",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Use the same CSS styles for preview as the ones used on GitHub.com."
|
||||
}
|
||||
}
|
||||
}
|
1
packages/markdown-preview/spec/fixtures/subdir/áccéntéd.md
vendored
Normal file
1
packages/markdown-preview/spec/fixtures/subdir/áccéntéd.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Testing
|
9
packages/markdown-preview/spec/fixtures/subdir/code-block.md
vendored
Normal file
9
packages/markdown-preview/spec/fixtures/subdir/code-block.md
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# Code Block
|
||||
|
||||
```javascript
|
||||
if a === 3 {
|
||||
b = 5
|
||||
}
|
||||
```
|
||||
|
||||
encoding → issue
|
4
packages/markdown-preview/spec/fixtures/subdir/doctype-tag.md
vendored
Normal file
4
packages/markdown-preview/spec/fixtures/subdir/doctype-tag.md
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
<!doctype html>
|
||||
content
|
||||
<!doctype html>
|
5
packages/markdown-preview/spec/fixtures/subdir/evil.md
vendored
Normal file
5
packages/markdown-preview/spec/fixtures/subdir/evil.md
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
hello
|
||||
<script src="index.js"></script>
|
||||
<script>alert('rm -fr')</script>
|
||||
<img onload="alert('rm -rf')" onerror="alert('rm -fr')">
|
||||
world
|
1
packages/markdown-preview/spec/fixtures/subdir/file with space.md
vendored
Normal file
1
packages/markdown-preview/spec/fixtures/subdir/file with space.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Testing
|
51
packages/markdown-preview/spec/fixtures/subdir/file.markdown
vendored
Normal file
51
packages/markdown-preview/spec/fixtures/subdir/file.markdown
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
variable1: value1
|
||||
array:
|
||||
- foo
|
||||
- bar
|
||||
---
|
||||
|
||||
## File.markdown
|
||||
|
||||
:cool:
|
||||
|
||||
```
|
||||
function f(x) {
|
||||
return x++;
|
||||
}
|
||||
```
|
||||
|
||||
```Ruby
|
||||
def func
|
||||
x = 1
|
||||
end
|
||||
```
|
||||
|
||||
* ```javascript
|
||||
if a === 3 {
|
||||
b = 5
|
||||
}
|
||||
```
|
||||
|
||||
```kombucha
|
||||
drink-that-stuff:
|
||||
tastes-weird~
|
||||
```
|
||||
|
||||
```python
|
||||
def foo()
|
||||
|
||||
bar
|
||||
|
||||
|
||||
baz
|
||||
```
|
||||
|
||||
![Image1](image1.png)
|
||||
|
||||
![Image2](/tmp/image2.png)
|
||||
|
||||
![Image3](http://github.com/image3.png)
|
||||
|
||||
lorem
|
||||
ipsum
|
1
packages/markdown-preview/spec/fixtures/subdir/html-tag.md
vendored
Normal file
1
packages/markdown-preview/spec/fixtures/subdir/html-tag.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
<html>content</html>
|
1
packages/markdown-preview/spec/fixtures/subdir/pre-tag.md
vendored
Normal file
1
packages/markdown-preview/spec/fixtures/subdir/pre-tag.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
<pre>hey</pre>
|
5
packages/markdown-preview/spec/fixtures/subdir/simple.md
vendored
Normal file
5
packages/markdown-preview/spec/fixtures/subdir/simple.md
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
*italic*
|
||||
|
||||
**bold**
|
||||
|
||||
encoding → issue
|
1
packages/markdown-preview/spec/fixtures/subdir/áccéntéd.md
vendored
Normal file
1
packages/markdown-preview/spec/fixtures/subdir/áccéntéd.md
vendored
Normal file
@ -0,0 +1 @@
|
||||
# Testing
|
839
packages/markdown-preview/spec/markdown-preview-spec.js
Normal file
839
packages/markdown-preview/spec/markdown-preview-spec.js
Normal file
@ -0,0 +1,839 @@
|
||||
const path = require('path')
|
||||
const fs = require('fs-plus')
|
||||
const temp = require('temp').track()
|
||||
const MarkdownPreviewView = require('../lib/markdown-preview-view')
|
||||
const { TextEditor } = require('atom')
|
||||
const TextMateLanguageMode = new TextEditor().getBuffer().getLanguageMode()
|
||||
.constructor
|
||||
|
||||
describe('Markdown Preview', function () {
|
||||
let preview = null
|
||||
|
||||
beforeEach(function () {
|
||||
const fixturesPath = path.join(__dirname, 'fixtures')
|
||||
const tempPath = temp.mkdirSync('atom')
|
||||
fs.copySync(fixturesPath, tempPath)
|
||||
atom.project.setPaths([tempPath])
|
||||
|
||||
jasmine.unspy(TextMateLanguageMode.prototype, 'tokenizeInBackground')
|
||||
|
||||
jasmine.useRealClock()
|
||||
jasmine.attachToDOM(atom.views.getView(atom.workspace))
|
||||
|
||||
waitsForPromise(() => atom.packages.activatePackage('markdown-preview'))
|
||||
|
||||
waitsForPromise(() => atom.packages.activatePackage('language-gfm'))
|
||||
|
||||
runs(() =>
|
||||
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
|
||||
)
|
||||
})
|
||||
|
||||
const expectPreviewInSplitPane = function () {
|
||||
waitsFor(() => atom.workspace.getCenter().getPanes().length === 2)
|
||||
|
||||
waitsFor(
|
||||
'markdown preview to be created',
|
||||
() =>
|
||||
(preview = atom.workspace
|
||||
.getCenter()
|
||||
.getPanes()[1]
|
||||
.getActiveItem())
|
||||
)
|
||||
|
||||
runs(() => {
|
||||
expect(preview).toBeInstanceOf(MarkdownPreviewView)
|
||||
expect(preview.getPath()).toBe(
|
||||
atom.workspace.getActivePaneItem().getPath()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
describe('when a preview has not been created for the file', function () {
|
||||
it('displays a markdown preview in a split pane', function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/file.markdown'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
|
||||
runs(() => {
|
||||
const [editorPane] = atom.workspace.getCenter().getPanes()
|
||||
expect(editorPane.getItems()).toHaveLength(1)
|
||||
expect(editorPane.isActive()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the editor's path does not exist", function () {
|
||||
it('splits the current pane to the right with a markdown preview for the file', function () {
|
||||
waitsForPromise(() => atom.workspace.open('new.markdown'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the editor does not have a path', function () {
|
||||
it('splits the current pane to the right with a markdown preview for the file', function () {
|
||||
waitsForPromise(() => atom.workspace.open(''))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the path contains a space', function () {
|
||||
it('renders the preview', function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/file with space.md'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the path contains accented characters', function () {
|
||||
it('renders the preview', function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/áccéntéd.md'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when a preview has been created for the file', function () {
|
||||
beforeEach(function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/file.markdown'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
})
|
||||
|
||||
it('closes the existing preview when toggle is triggered a second time on the editor', function () {
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
|
||||
const [editorPane, previewPane] = atom.workspace.getCenter().getPanes()
|
||||
expect(editorPane.isActive()).toBe(true)
|
||||
expect(previewPane.getActiveItem()).toBeUndefined()
|
||||
})
|
||||
|
||||
it('closes the existing preview when toggle is triggered on it and it has focus', function () {
|
||||
const [editorPane, previewPane] = atom.workspace.getCenter().getPanes()
|
||||
previewPane.activate()
|
||||
|
||||
atom.commands.dispatch(
|
||||
editorPane.getActiveItem().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
expect(previewPane.getActiveItem()).toBeUndefined()
|
||||
})
|
||||
|
||||
describe('when the editor is modified', function () {
|
||||
it('re-renders the preview', function () {
|
||||
spyOn(preview, 'showLoading')
|
||||
|
||||
const markdownEditor = atom.workspace.getActiveTextEditor()
|
||||
markdownEditor.setText('Hey!')
|
||||
|
||||
waitsFor(() => preview.element.textContent.includes('Hey!'))
|
||||
|
||||
runs(() => expect(preview.showLoading).not.toHaveBeenCalled())
|
||||
})
|
||||
|
||||
it('invokes ::onDidChangeMarkdown listeners', function () {
|
||||
let listener
|
||||
const markdownEditor = atom.workspace.getActiveTextEditor()
|
||||
preview.onDidChangeMarkdown(
|
||||
(listener = jasmine.createSpy('didChangeMarkdownListener'))
|
||||
)
|
||||
|
||||
runs(() => markdownEditor.setText('Hey!'))
|
||||
|
||||
waitsFor(
|
||||
'::onDidChangeMarkdown handler to be called',
|
||||
() => listener.callCount > 0
|
||||
)
|
||||
})
|
||||
|
||||
describe('when the preview is in the active pane but is not the active item', function () {
|
||||
it('re-renders the preview but does not make it active', function () {
|
||||
const markdownEditor = atom.workspace.getActiveTextEditor()
|
||||
const previewPane = atom.workspace.getCenter().getPanes()[1]
|
||||
previewPane.activate()
|
||||
|
||||
waitsForPromise(() => atom.workspace.open())
|
||||
|
||||
runs(() => markdownEditor.setText('Hey!'))
|
||||
|
||||
waitsFor(() => preview.element.textContent.includes('Hey!'))
|
||||
|
||||
runs(() => {
|
||||
expect(previewPane.isActive()).toBe(true)
|
||||
expect(previewPane.getActiveItem()).not.toBe(preview)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the preview is not the active item and not in the active pane', function () {
|
||||
it('re-renders the preview and makes it active', function () {
|
||||
const markdownEditor = atom.workspace.getActiveTextEditor()
|
||||
const [
|
||||
editorPane,
|
||||
previewPane
|
||||
] = atom.workspace.getCenter().getPanes()
|
||||
previewPane.splitRight({ copyActiveItem: true })
|
||||
previewPane.activate()
|
||||
|
||||
waitsForPromise(() => atom.workspace.open())
|
||||
|
||||
runs(() => {
|
||||
editorPane.activate()
|
||||
markdownEditor.setText('Hey!')
|
||||
})
|
||||
|
||||
waitsFor(() => preview.element.textContent.includes('Hey!'))
|
||||
|
||||
runs(() => {
|
||||
expect(editorPane.isActive()).toBe(true)
|
||||
expect(previewPane.getActiveItem()).toBe(preview)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the liveUpdate config is set to false', function () {
|
||||
it('only re-renders the markdown when the editor is saved, not when the contents are modified', function () {
|
||||
atom.config.set('markdown-preview.liveUpdate', false)
|
||||
|
||||
const didStopChangingHandler = jasmine.createSpy(
|
||||
'didStopChangingHandler'
|
||||
)
|
||||
atom.workspace
|
||||
.getActiveTextEditor()
|
||||
.getBuffer()
|
||||
.onDidStopChanging(didStopChangingHandler)
|
||||
atom.workspace.getActiveTextEditor().setText('ch ch changes')
|
||||
|
||||
waitsFor(() => didStopChangingHandler.callCount > 0)
|
||||
|
||||
runs(() => {
|
||||
expect(preview.element.textContent).not.toMatch('ch ch changes')
|
||||
atom.workspace.getActiveTextEditor().save()
|
||||
})
|
||||
|
||||
waitsFor(() => preview.element.textContent.includes('ch ch changes'))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the original preview is split', function () {
|
||||
it('renders another preview in the new split pane', function () {
|
||||
atom.workspace
|
||||
.getCenter()
|
||||
.getPanes()[1]
|
||||
.splitRight({ copyActiveItem: true })
|
||||
|
||||
expect(atom.workspace.getCenter().getPanes()).toHaveLength(3)
|
||||
|
||||
waitsFor(
|
||||
'split markdown preview to be created',
|
||||
() =>
|
||||
(preview = atom.workspace
|
||||
.getCenter()
|
||||
.getPanes()[2]
|
||||
.getActiveItem())
|
||||
)
|
||||
|
||||
runs(() => {
|
||||
expect(preview).toBeInstanceOf(MarkdownPreviewView)
|
||||
expect(preview.getPath()).toBe(
|
||||
atom.workspace.getActivePaneItem().getPath()
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the editor is destroyed', function () {
|
||||
beforeEach(() =>
|
||||
atom.workspace
|
||||
.getCenter()
|
||||
.getPanes()[0]
|
||||
.destroyActiveItem()
|
||||
)
|
||||
|
||||
it('falls back to using the file path', function () {
|
||||
atom.workspace
|
||||
.getCenter()
|
||||
.getPanes()[1]
|
||||
.activate()
|
||||
expect(preview.file.getPath()).toBe(
|
||||
atom.workspace.getActivePaneItem().getPath()
|
||||
)
|
||||
})
|
||||
|
||||
it('continues to update the preview if the file is changed on #win32 and #darwin', function () {
|
||||
let listener
|
||||
const titleChangedCallback = jasmine.createSpy('titleChangedCallback')
|
||||
|
||||
runs(() => {
|
||||
expect(preview.getTitle()).toBe('file.markdown Preview')
|
||||
preview.onDidChangeTitle(titleChangedCallback)
|
||||
fs.renameSync(
|
||||
preview.getPath(),
|
||||
path.join(path.dirname(preview.getPath()), 'file2.md')
|
||||
)
|
||||
})
|
||||
|
||||
waitsFor(
|
||||
'title to update',
|
||||
() => preview.getTitle() === 'file2.md Preview'
|
||||
)
|
||||
|
||||
runs(() => expect(titleChangedCallback).toHaveBeenCalled())
|
||||
|
||||
spyOn(preview, 'showLoading')
|
||||
|
||||
runs(() => fs.writeFileSync(preview.getPath(), 'Hey!'))
|
||||
|
||||
waitsFor('contents to update', () =>
|
||||
preview.element.textContent.includes('Hey!')
|
||||
)
|
||||
|
||||
runs(() => expect(preview.showLoading).not.toHaveBeenCalled())
|
||||
|
||||
preview.onDidChangeMarkdown(
|
||||
(listener = jasmine.createSpy('didChangeMarkdownListener'))
|
||||
)
|
||||
|
||||
runs(() => fs.writeFileSync(preview.getPath(), 'Hey!'))
|
||||
|
||||
waitsFor(
|
||||
'::onDidChangeMarkdown handler to be called',
|
||||
() => listener.callCount > 0
|
||||
)
|
||||
})
|
||||
|
||||
it('allows a new split pane of the preview to be created', function () {
|
||||
atom.workspace
|
||||
.getCenter()
|
||||
.getPanes()[1]
|
||||
.splitRight({ copyActiveItem: true })
|
||||
|
||||
expect(atom.workspace.getCenter().getPanes()).toHaveLength(3)
|
||||
|
||||
waitsFor(
|
||||
'split markdown preview to be created',
|
||||
() =>
|
||||
(preview = atom.workspace
|
||||
.getCenter()
|
||||
.getPanes()[2]
|
||||
.getActiveItem())
|
||||
)
|
||||
|
||||
runs(() => {
|
||||
expect(preview).toBeInstanceOf(MarkdownPreviewView)
|
||||
expect(preview.getPath()).toBe(
|
||||
atom.workspace.getActivePaneItem().getPath()
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the markdown preview view is requested by file URI', function () {
|
||||
it('opens a preview editor and watches the file for changes', function () {
|
||||
waitsForPromise('atom.workspace.open promise to be resolved', () =>
|
||||
atom.workspace.open(
|
||||
`markdown-preview://${atom.project
|
||||
.getDirectories()[0]
|
||||
.resolve('subdir/file.markdown')}`
|
||||
)
|
||||
)
|
||||
|
||||
runs(() => {
|
||||
preview = atom.workspace.getActivePaneItem()
|
||||
expect(preview).toBeInstanceOf(MarkdownPreviewView)
|
||||
|
||||
spyOn(preview, 'renderMarkdownText')
|
||||
preview.file.emitter.emit('did-change')
|
||||
})
|
||||
|
||||
waitsFor(
|
||||
'markdown to be re-rendered after file changed',
|
||||
() => preview.renderMarkdownText.callCount > 0
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the editor's grammar it not enabled for preview", function () {
|
||||
it('does not open the markdown preview', function () {
|
||||
atom.config.set('markdown-preview.grammars', [])
|
||||
|
||||
waitsForPromise(() => atom.workspace.open('subdir/file.markdown'))
|
||||
|
||||
runs(() => {
|
||||
spyOn(atom.workspace, 'open').andCallThrough()
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
expect(atom.workspace.open).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the editor's path changes on #win32 and #darwin", function () {
|
||||
it("updates the preview's title", function () {
|
||||
const titleChangedCallback = jasmine.createSpy('titleChangedCallback')
|
||||
|
||||
waitsForPromise(() => atom.workspace.open('subdir/file.markdown'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
|
||||
expectPreviewInSplitPane()
|
||||
|
||||
runs(() => {
|
||||
expect(preview.getTitle()).toBe('file.markdown Preview')
|
||||
preview.onDidChangeTitle(titleChangedCallback)
|
||||
fs.renameSync(
|
||||
atom.workspace.getActiveTextEditor().getPath(),
|
||||
path.join(
|
||||
path.dirname(atom.workspace.getActiveTextEditor().getPath()),
|
||||
'file2.md'
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
waitsFor(() => preview.getTitle() === 'file2.md Preview')
|
||||
|
||||
runs(() => expect(titleChangedCallback).toHaveBeenCalled())
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the URI opened does not have a markdown-preview protocol', function () {
|
||||
it('does not throw an error trying to decode the URI (regression)', function () {
|
||||
waitsForPromise(() => atom.workspace.open('%'))
|
||||
|
||||
runs(() => expect(atom.workspace.getActiveTextEditor()).toBeTruthy())
|
||||
})
|
||||
})
|
||||
|
||||
describe('markdown-preview:toggle', function () {
|
||||
beforeEach(() =>
|
||||
waitsForPromise(() => atom.workspace.open('code-block.md'))
|
||||
)
|
||||
|
||||
it('does not exist for text editors that are not set to a grammar defined in `markdown-preview.grammars`', function () {
|
||||
atom.config.set('markdown-preview.grammars', ['source.weird-md'])
|
||||
const editorElement = atom.workspace.getActiveTextEditor().getElement()
|
||||
const commands = atom.commands
|
||||
.findCommands({ target: editorElement })
|
||||
.map(command => command.name)
|
||||
expect(commands).not.toContain('markdown-preview:toggle')
|
||||
})
|
||||
|
||||
it('exists for text editors that are set to a grammar defined in `markdown-preview.grammars`', function () {
|
||||
atom.config.set('markdown-preview.grammars', ['source.gfm'])
|
||||
const editorElement = atom.workspace.getActiveTextEditor().getElement()
|
||||
const commands = atom.commands
|
||||
.findCommands({ target: editorElement })
|
||||
.map(command => command.name)
|
||||
expect(commands).toContain('markdown-preview:toggle')
|
||||
})
|
||||
|
||||
it('updates whenever the list of grammars changes', function () {
|
||||
// Last two tests combined
|
||||
atom.config.set('markdown-preview.grammars', ['source.gfm', 'text.plain'])
|
||||
const editorElement = atom.workspace.getActiveTextEditor().getElement()
|
||||
let commands = atom.commands
|
||||
.findCommands({ target: editorElement })
|
||||
.map(command => command.name)
|
||||
expect(commands).toContain('markdown-preview:toggle')
|
||||
|
||||
atom.config.set('markdown-preview.grammars', [
|
||||
'source.weird-md',
|
||||
'text.plain'
|
||||
])
|
||||
commands = atom.commands
|
||||
.findCommands({ target: editorElement })
|
||||
.map(command => command.name)
|
||||
expect(commands).not.toContain('markdown-preview:toggle')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when markdown-preview:copy-html is triggered', function () {
|
||||
it('copies the HTML to the clipboard', function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/simple.md'))
|
||||
|
||||
waitsForPromise(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:copy-html'
|
||||
)
|
||||
)
|
||||
|
||||
runs(() => {
|
||||
expect(atom.clipboard.read()).toBe(`\
|
||||
<p><em>italic</em></p>
|
||||
<p><strong>bold</strong></p>
|
||||
<p>encoding \u2192 issue</p>\
|
||||
`)
|
||||
|
||||
atom.workspace
|
||||
.getActiveTextEditor()
|
||||
.setSelectedBufferRange([[0, 0], [1, 0]])
|
||||
})
|
||||
|
||||
waitsForPromise(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:copy-html'
|
||||
)
|
||||
)
|
||||
|
||||
runs(() =>
|
||||
expect(atom.clipboard.read()).toBe(`\
|
||||
<p><em>italic</em></p>\
|
||||
`)
|
||||
)
|
||||
})
|
||||
|
||||
describe('code block tokenization', function () {
|
||||
beforeEach(function () {
|
||||
waitsForPromise(() => atom.packages.activatePackage('language-ruby'))
|
||||
|
||||
waitsForPromise(() => atom.packages.activatePackage('markdown-preview'))
|
||||
|
||||
waitsForPromise(() => atom.workspace.open('subdir/file.markdown'))
|
||||
|
||||
waitsForPromise(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:copy-html'
|
||||
)
|
||||
)
|
||||
|
||||
runs(() => {
|
||||
preview = document.createElement('div')
|
||||
preview.innerHTML = atom.clipboard.read()
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the code block's fence name has a matching grammar", function () {
|
||||
it('tokenizes the code block with the grammar', function () {
|
||||
expect(
|
||||
preview.querySelector('pre span.entity.name.function.ruby')
|
||||
).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the code block's fence name doesn't have a matching grammar", function () {
|
||||
it('does not tokenize the code block', function () {
|
||||
expect(
|
||||
preview.querySelectorAll(
|
||||
'pre.lang-kombucha .line .syntax--null-grammar'
|
||||
).length
|
||||
).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the code block contains empty lines', function () {
|
||||
it("doesn't remove the empty lines", function () {
|
||||
expect(preview.querySelector('pre.lang-python').children.length).toBe(
|
||||
6
|
||||
)
|
||||
expect(
|
||||
preview
|
||||
.querySelector('pre.lang-python div:nth-child(2)')
|
||||
.textContent.trim()
|
||||
).toBe('')
|
||||
expect(
|
||||
preview
|
||||
.querySelector('pre.lang-python div:nth-child(4)')
|
||||
.textContent.trim()
|
||||
).toBe('')
|
||||
expect(
|
||||
preview
|
||||
.querySelector('pre.lang-python div:nth-child(5)')
|
||||
.textContent.trim()
|
||||
).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the code block is nested in a list', function () {
|
||||
it('detects and styles the block', function () {
|
||||
expect(preview.querySelector('pre.lang-javascript')).toHaveClass(
|
||||
'editor-colors'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('sanitization', function () {
|
||||
it('removes script tags and attributes that commonly contain inline scripts', function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/evil.md'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
|
||||
runs(() =>
|
||||
expect(preview.element.innerHTML).toBe(`\
|
||||
<p>hello</p>
|
||||
|
||||
|
||||
<img>
|
||||
world\
|
||||
`)
|
||||
)
|
||||
})
|
||||
|
||||
it('remove any <!doctype> tag on markdown files', function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/doctype-tag.md'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
|
||||
runs(() =>
|
||||
expect(preview.element.innerHTML).toBe(`\
|
||||
<p>content
|
||||
</p>\
|
||||
`)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the markdown contains an <html> tag', function () {
|
||||
it('does not throw an exception', function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/html-tag.md'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
|
||||
runs(() => expect(preview.element.innerHTML).toBe('content'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the markdown contains a <pre> tag', function () {
|
||||
it('does not throw an exception', function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/pre-tag.md'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
|
||||
runs(() =>
|
||||
expect(preview.element.querySelector('atom-text-editor')).toBeDefined()
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is an image with a relative path and no directory', function () {
|
||||
it('does not alter the image src', function () {
|
||||
for (let projectPath of atom.project.getPaths()) {
|
||||
atom.project.removePath(projectPath)
|
||||
}
|
||||
|
||||
const filePath = path.join(temp.mkdirSync('atom'), 'bar.md')
|
||||
fs.writeFileSync(filePath, '![rel path](/foo.png)')
|
||||
|
||||
waitsForPromise(() => atom.workspace.open(filePath))
|
||||
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
|
||||
runs(() =>
|
||||
expect(preview.element.innerHTML).toBe(`\
|
||||
<p><img alt="rel path" src="/foo.png"></p>\
|
||||
`)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GitHub style markdown preview', function () {
|
||||
beforeEach(() => atom.config.set('markdown-preview.useGitHubStyle', false))
|
||||
|
||||
it('renders markdown using the default style when GitHub styling is disabled', function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/simple.md'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
|
||||
runs(() =>
|
||||
expect(preview.element.getAttribute('data-use-github-style')).toBeNull()
|
||||
)
|
||||
})
|
||||
|
||||
it('renders markdown using the GitHub styling when enabled', function () {
|
||||
atom.config.set('markdown-preview.useGitHubStyle', true)
|
||||
|
||||
waitsForPromise(() => atom.workspace.open('subdir/simple.md'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
|
||||
runs(() =>
|
||||
expect(preview.element.getAttribute('data-use-github-style')).toBe('')
|
||||
)
|
||||
})
|
||||
|
||||
it('updates the rendering style immediately when the configuration is changed', function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/simple.md'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
|
||||
runs(() => {
|
||||
expect(preview.element.getAttribute('data-use-github-style')).toBeNull()
|
||||
|
||||
atom.config.set('markdown-preview.useGitHubStyle', true)
|
||||
expect(
|
||||
preview.element.getAttribute('data-use-github-style')
|
||||
).not.toBeNull()
|
||||
|
||||
atom.config.set('markdown-preview.useGitHubStyle', false)
|
||||
expect(preview.element.getAttribute('data-use-github-style')).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when markdown-preview:save-as-html is triggered', function () {
|
||||
beforeEach(function () {
|
||||
waitsForPromise(() => atom.workspace.open('subdir/simple.markdown'))
|
||||
runs(() =>
|
||||
atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:toggle'
|
||||
)
|
||||
)
|
||||
expectPreviewInSplitPane()
|
||||
})
|
||||
|
||||
it('saves the HTML when it is triggered and the editor has focus', function () {
|
||||
const [editorPane] = atom.workspace.getCenter().getPanes()
|
||||
editorPane.activate()
|
||||
|
||||
const outputPath = temp.path({ suffix: '.html' })
|
||||
expect(fs.existsSync(outputPath)).toBe(false)
|
||||
|
||||
runs(() => {
|
||||
spyOn(preview, 'getSaveDialogOptions').andReturn({
|
||||
defaultPath: outputPath
|
||||
})
|
||||
spyOn(atom.applicationDelegate, 'showSaveDialog').andCallFake(function (
|
||||
options,
|
||||
callback
|
||||
) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(options.defaultPath)
|
||||
}
|
||||
// TODO: When https://github.com/atom/atom/pull/16245 lands remove the return
|
||||
// and the existence check on the callback
|
||||
return options.defaultPath
|
||||
})
|
||||
return atom.commands.dispatch(
|
||||
atom.workspace.getActiveTextEditor().getElement(),
|
||||
'markdown-preview:save-as-html'
|
||||
)
|
||||
})
|
||||
|
||||
waitsFor(() => fs.existsSync(outputPath))
|
||||
|
||||
runs(() => expect(fs.existsSync(outputPath)).toBe(true))
|
||||
})
|
||||
|
||||
it('saves the HTML when it is triggered and the preview pane has focus', function () {
|
||||
const [editorPane, previewPane] = atom.workspace.getCenter().getPanes()
|
||||
previewPane.activate()
|
||||
|
||||
const outputPath = temp.path({ suffix: '.html' })
|
||||
expect(fs.existsSync(outputPath)).toBe(false)
|
||||
|
||||
runs(() => {
|
||||
spyOn(preview, 'getSaveDialogOptions').andReturn({
|
||||
defaultPath: outputPath
|
||||
})
|
||||
spyOn(atom.applicationDelegate, 'showSaveDialog').andCallFake(function (
|
||||
options,
|
||||
callback
|
||||
) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(options.defaultPath)
|
||||
}
|
||||
// TODO: When https://github.com/atom/atom/pull/16245 lands remove the return
|
||||
// and the existence check on the callback
|
||||
return options.defaultPath
|
||||
})
|
||||
return atom.commands.dispatch(
|
||||
editorPane.getActiveItem().getElement(),
|
||||
'markdown-preview:save-as-html'
|
||||
)
|
||||
})
|
||||
|
||||
waitsFor(() => fs.existsSync(outputPath))
|
||||
|
||||
runs(() => expect(fs.existsSync(outputPath)).toBe(true))
|
||||
})
|
||||
})
|
||||
})
|
604
packages/markdown-preview/spec/markdown-preview-view-spec.js
Normal file
604
packages/markdown-preview/spec/markdown-preview-view-spec.js
Normal file
@ -0,0 +1,604 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS207: Consider shorter variations of null checks
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
const path = require('path')
|
||||
const fs = require('fs-plus')
|
||||
const temp = require('temp').track()
|
||||
const url = require('url')
|
||||
const { TextEditor } = require('atom')
|
||||
const MarkdownPreviewView = require('../lib/markdown-preview-view')
|
||||
const TextMateLanguageMode = new TextEditor().getBuffer().getLanguageMode()
|
||||
.constructor
|
||||
|
||||
describe('MarkdownPreviewView', function () {
|
||||
let preview = null
|
||||
|
||||
beforeEach(function () {
|
||||
// Makes _.debounce work
|
||||
jasmine.useRealClock()
|
||||
|
||||
jasmine.unspy(TextMateLanguageMode.prototype, 'tokenizeInBackground')
|
||||
|
||||
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
|
||||
|
||||
const filePath = atom.project
|
||||
.getDirectories()[0]
|
||||
.resolve('subdir/file.markdown')
|
||||
preview = new MarkdownPreviewView({ filePath })
|
||||
jasmine.attachToDOM(preview.element)
|
||||
|
||||
waitsForPromise(() => atom.packages.activatePackage('language-ruby'))
|
||||
|
||||
waitsForPromise(() => atom.packages.activatePackage('language-javascript'))
|
||||
|
||||
waitsForPromise(() => atom.packages.activatePackage('markdown-preview'))
|
||||
})
|
||||
|
||||
afterEach(() => preview.destroy())
|
||||
|
||||
describe('::constructor', function () {
|
||||
it('shows a loading spinner and renders the markdown', function () {
|
||||
preview.showLoading()
|
||||
expect(preview.element.querySelector('.markdown-spinner')).toBeDefined()
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(() => expect(preview.element.querySelector('.emoji')).toBeDefined())
|
||||
})
|
||||
|
||||
it('shows an error message when there is an error', function () {
|
||||
preview.showError('Not a real file')
|
||||
expect(preview.element.textContent).toMatch('Failed')
|
||||
})
|
||||
|
||||
it('rerenders the markdown and the scrollTop stays the same', function () {
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(function () {
|
||||
preview.element.style.maxHeight = '10px'
|
||||
preview.element.scrollTop = 24
|
||||
expect(preview.element.scrollTop).toBe(24)
|
||||
})
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(() => expect(preview.element.scrollTop).toBe(24))
|
||||
})
|
||||
})
|
||||
|
||||
describe('serialization', function () {
|
||||
let newPreview = null
|
||||
|
||||
afterEach(function () {
|
||||
if (newPreview) {
|
||||
newPreview.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
it('recreates the preview when serialized/deserialized', function () {
|
||||
newPreview = atom.deserializers.deserialize(preview.serialize())
|
||||
jasmine.attachToDOM(newPreview.element)
|
||||
expect(newPreview.getPath()).toBe(preview.getPath())
|
||||
})
|
||||
|
||||
it('does not recreate a preview when the file no longer exists', function () {
|
||||
const filePath = path.join(temp.mkdirSync('markdown-preview-'), 'foo.md')
|
||||
fs.writeFileSync(filePath, '# Hi')
|
||||
|
||||
preview.destroy()
|
||||
preview = new MarkdownPreviewView({ filePath })
|
||||
const serialized = preview.serialize()
|
||||
fs.removeSync(filePath)
|
||||
|
||||
newPreview = atom.deserializers.deserialize(serialized)
|
||||
expect(newPreview).toBeUndefined()
|
||||
})
|
||||
|
||||
it('serializes the editor id when opened for an editor', function () {
|
||||
preview.destroy()
|
||||
|
||||
waitsForPromise(() => atom.workspace.open('new.markdown'))
|
||||
|
||||
runs(function () {
|
||||
preview = new MarkdownPreviewView({
|
||||
editorId: atom.workspace.getActiveTextEditor().id
|
||||
})
|
||||
|
||||
jasmine.attachToDOM(preview.element)
|
||||
expect(preview.getPath()).toBe(
|
||||
atom.workspace.getActiveTextEditor().getPath()
|
||||
)
|
||||
|
||||
newPreview = atom.deserializers.deserialize(preview.serialize())
|
||||
jasmine.attachToDOM(newPreview.element)
|
||||
expect(newPreview.getPath()).toBe(preview.getPath())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('code block conversion to atom-text-editor tags', function () {
|
||||
beforeEach(function () {
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
})
|
||||
|
||||
it('removes line decorations on rendered code blocks', function () {
|
||||
const editor = preview.element.querySelector(
|
||||
"atom-text-editor[data-grammar='text plain null-grammar']"
|
||||
)
|
||||
const decorations = editor
|
||||
.getModel()
|
||||
.getDecorations({ class: 'cursor-line', type: 'line' })
|
||||
expect(decorations.length).toBe(0)
|
||||
})
|
||||
|
||||
it('sets the editors as read-only', function () {
|
||||
preview.element
|
||||
.querySelectorAll('atom-text-editor')
|
||||
.forEach(editorElement =>
|
||||
expect(editorElement.getAttribute('tabindex')).toBeNull()
|
||||
)
|
||||
})
|
||||
|
||||
describe("when the code block's fence name has a matching grammar", function () {
|
||||
it('assigns the grammar on the atom-text-editor', function () {
|
||||
const rubyEditor = preview.element.querySelector(
|
||||
"atom-text-editor[data-grammar='source ruby']"
|
||||
)
|
||||
expect(rubyEditor.getModel().getText()).toBe(`\
|
||||
def func
|
||||
x = 1
|
||||
end\
|
||||
`)
|
||||
|
||||
// nested in a list item
|
||||
const jsEditor = preview.element.querySelector(
|
||||
"atom-text-editor[data-grammar='source js']"
|
||||
)
|
||||
expect(jsEditor.getModel().getText()).toBe(`\
|
||||
if a === 3 {
|
||||
b = 5
|
||||
}\
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("when the code block's fence name doesn't have a matching grammar", function () {
|
||||
it('does not assign a specific grammar', function () {
|
||||
const plainEditor = preview.element.querySelector(
|
||||
"atom-text-editor[data-grammar='text plain null-grammar']"
|
||||
)
|
||||
expect(plainEditor.getModel().getText()).toBe(`\
|
||||
function f(x) {
|
||||
return x++;
|
||||
}\
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when an editor cannot find the grammar that is later loaded', function () {
|
||||
it('updates the editor grammar', function () {
|
||||
let renderSpy = null
|
||||
|
||||
if (typeof atom.grammars.onDidRemoveGrammar !== 'function') {
|
||||
// TODO: Remove once atom.grammars.onDidRemoveGrammar is released
|
||||
waitsForPromise(() => atom.packages.activatePackage('language-gfm'))
|
||||
}
|
||||
|
||||
runs(
|
||||
() => (renderSpy = spyOn(preview, 'renderMarkdown').andCallThrough())
|
||||
)
|
||||
|
||||
waitsForPromise(() => atom.packages.deactivatePackage('language-ruby'))
|
||||
|
||||
waitsFor(
|
||||
'renderMarkdown to be called after disabling a language',
|
||||
() => renderSpy.callCount === 1
|
||||
)
|
||||
|
||||
runs(function () {
|
||||
const rubyEditor = preview.element.querySelector(
|
||||
"atom-text-editor[data-grammar='source ruby']"
|
||||
)
|
||||
expect(rubyEditor).toBeNull()
|
||||
})
|
||||
|
||||
waitsForPromise(() => atom.packages.activatePackage('language-ruby'))
|
||||
|
||||
waitsFor(
|
||||
'renderMarkdown to be called after enabling a language',
|
||||
() => renderSpy.callCount === 2
|
||||
)
|
||||
|
||||
runs(function () {
|
||||
const rubyEditor = preview.element.querySelector(
|
||||
"atom-text-editor[data-grammar='source ruby']"
|
||||
)
|
||||
expect(rubyEditor.getModel().getText()).toBe(`\
|
||||
def func
|
||||
x = 1
|
||||
end\
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('image resolving', function () {
|
||||
beforeEach(function () {
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
})
|
||||
|
||||
describe('when the image uses a relative path', function () {
|
||||
it('resolves to a path relative to the file', function () {
|
||||
const image = preview.element.querySelector('img[alt=Image1]')
|
||||
expect(image.getAttribute('src')).toBe(
|
||||
atom.project.getDirectories()[0].resolve('subdir/image1.png')
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the image uses an absolute path that does not exist', function () {
|
||||
it('resolves to a path relative to the project root', function () {
|
||||
const image = preview.element.querySelector('img[alt=Image2]')
|
||||
expect(image.src).toMatch(
|
||||
url.parse(atom.project.getDirectories()[0].resolve('tmp/image2.png'))
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the image uses an absolute path that exists', function () {
|
||||
it("doesn't change the URL when allowUnsafeProtocols is true", function () {
|
||||
preview.destroy()
|
||||
|
||||
atom.config.set('markdown-preview.allowUnsafeProtocols', true)
|
||||
|
||||
const filePath = path.join(temp.mkdirSync('atom'), 'foo.md')
|
||||
fs.writeFileSync(filePath, `![absolute](${filePath})`)
|
||||
preview = new MarkdownPreviewView({ filePath })
|
||||
jasmine.attachToDOM(preview.element)
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(() =>
|
||||
expect(
|
||||
preview.element.querySelector('img[alt=absolute]').src
|
||||
).toMatch(url.parse(filePath))
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('removes the URL when allowUnsafeProtocols is false', function () {
|
||||
preview.destroy()
|
||||
|
||||
atom.config.set('markdown-preview.allowUnsafeProtocols', false)
|
||||
|
||||
const filePath = path.join(temp.mkdirSync('atom'), 'foo.md')
|
||||
fs.writeFileSync(filePath, `![absolute](${filePath})`)
|
||||
preview = new MarkdownPreviewView({ filePath })
|
||||
jasmine.attachToDOM(preview.element)
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(() =>
|
||||
expect(preview.element.querySelector('img[alt=absolute]').src).toMatch(
|
||||
''
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
describe('when the image uses a web URL', function () {
|
||||
it("doesn't change the URL", function () {
|
||||
const image = preview.element.querySelector('img[alt=Image3]')
|
||||
expect(image.src).toBe('http://github.com/image3.png')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('gfm newlines', function () {
|
||||
describe('when gfm newlines are not enabled', function () {
|
||||
it('creates a single paragraph with <br>', function () {
|
||||
atom.config.set('markdown-preview.breakOnSingleNewline', false)
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(() =>
|
||||
expect(
|
||||
preview.element.querySelectorAll('p:last-child br').length
|
||||
).toBe(0)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when gfm newlines are enabled', function () {
|
||||
it('creates a single paragraph with no <br>', function () {
|
||||
atom.config.set('markdown-preview.breakOnSingleNewline', true)
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(() =>
|
||||
expect(
|
||||
preview.element.querySelectorAll('p:last-child br').length
|
||||
).toBe(1)
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('yaml front matter', function () {
|
||||
it('creates a table with the YAML variables', function () {
|
||||
atom.config.set('markdown-preview.breakOnSingleNewline', true)
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(() => {
|
||||
expect(
|
||||
[...preview.element.querySelectorAll('table th')].map(
|
||||
el => el.textContent
|
||||
)
|
||||
).toEqual(['variable1', 'array'])
|
||||
expect(
|
||||
[...preview.element.querySelectorAll('table td')].map(
|
||||
el => el.textContent
|
||||
)
|
||||
).toEqual(['value1', 'foo,bar'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('text selections', function () {
|
||||
it('adds the `has-selection` class to the preview depending on if there is a text selection', function () {
|
||||
expect(preview.element.classList.contains('has-selection')).toBe(false)
|
||||
|
||||
const selection = window.getSelection()
|
||||
selection.removeAllRanges()
|
||||
selection.selectAllChildren(document.querySelector('atom-text-editor'))
|
||||
|
||||
waitsFor(
|
||||
() => preview.element.classList.contains('has-selection') === true
|
||||
)
|
||||
|
||||
runs(() => selection.removeAllRanges())
|
||||
|
||||
waitsFor(
|
||||
() => preview.element.classList.contains('has-selection') === false
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('when core:save-as is triggered', function () {
|
||||
beforeEach(function () {
|
||||
preview.destroy()
|
||||
const filePath = atom.project
|
||||
.getDirectories()[0]
|
||||
.resolve('subdir/code-block.md')
|
||||
preview = new MarkdownPreviewView({ filePath })
|
||||
// Add to workspace for core:save-as command to be propagated up to the workspace
|
||||
waitsForPromise(() => atom.workspace.open(preview))
|
||||
runs(() => jasmine.attachToDOM(atom.views.getView(atom.workspace)))
|
||||
})
|
||||
|
||||
it('saves the rendered HTML and opens it', function () {
|
||||
const outputPath = fs.realpathSync(temp.mkdirSync()) + 'output.html'
|
||||
|
||||
const createRule = (selector, css) => ({
|
||||
selectorText: selector,
|
||||
cssText: `${selector} ${css}`
|
||||
})
|
||||
const markdownPreviewStyles = [
|
||||
{
|
||||
rules: [createRule('.markdown-preview', '{ color: orange; }')]
|
||||
},
|
||||
{
|
||||
rules: [
|
||||
createRule('.not-included', '{ color: green; }'),
|
||||
createRule('.markdown-preview :host', '{ color: purple; }')
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const atomTextEditorStyles = [
|
||||
'atom-text-editor .line { color: brown; }\natom-text-editor .number { color: cyan; }',
|
||||
'atom-text-editor :host .something { color: black; }',
|
||||
'atom-text-editor .hr { background: url(atom://markdown-preview/assets/hr.png); }'
|
||||
]
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(() => {
|
||||
expect(fs.isFileSync(outputPath)).toBe(false)
|
||||
spyOn(preview, 'getSaveDialogOptions').andReturn({
|
||||
defaultPath: outputPath
|
||||
})
|
||||
spyOn(atom.applicationDelegate, 'showSaveDialog').andCallFake(function (
|
||||
options,
|
||||
callback
|
||||
) {
|
||||
if (typeof callback === 'function') {
|
||||
callback(options.defaultPath)
|
||||
}
|
||||
// TODO: When https://github.com/atom/atom/pull/16245 lands remove the return
|
||||
// and the existence check on the callback
|
||||
return options.defaultPath
|
||||
})
|
||||
spyOn(preview, 'getDocumentStyleSheets').andReturn(
|
||||
markdownPreviewStyles
|
||||
)
|
||||
spyOn(preview, 'getTextEditorStyles').andReturn(atomTextEditorStyles)
|
||||
})
|
||||
|
||||
waitsForPromise(() =>
|
||||
atom.commands.dispatch(preview.element, 'core:save-as')
|
||||
)
|
||||
|
||||
waitsFor(() => {
|
||||
const activeEditor = atom.workspace.getActiveTextEditor()
|
||||
return activeEditor && activeEditor.getPath() === outputPath
|
||||
})
|
||||
|
||||
runs(() => {
|
||||
const element = document.createElement('div')
|
||||
element.innerHTML = fs.readFileSync(outputPath)
|
||||
expect(element.querySelector('h1').innerText).toBe('Code Block')
|
||||
expect(
|
||||
element.querySelector(
|
||||
'.line .syntax--source.syntax--js .syntax--constant.syntax--numeric'
|
||||
).innerText
|
||||
).toBe('3')
|
||||
expect(
|
||||
element.querySelector(
|
||||
'.line .syntax--source.syntax--js .syntax--keyword.syntax--control'
|
||||
).innerText
|
||||
).toBe('if')
|
||||
expect(
|
||||
element.querySelector(
|
||||
'.line .syntax--source.syntax--js .syntax--constant.syntax--numeric'
|
||||
).innerText
|
||||
).toBe('3')
|
||||
})
|
||||
})
|
||||
|
||||
describe('text editor style extraction', function () {
|
||||
let [extractedStyles] = []
|
||||
|
||||
const textEditorStyle = '.editor-style .extraction-test { color: blue; }'
|
||||
const unrelatedStyle = '.something else { color: red; }'
|
||||
|
||||
beforeEach(function () {
|
||||
atom.styles.addStyleSheet(textEditorStyle, {
|
||||
context: 'atom-text-editor'
|
||||
})
|
||||
|
||||
atom.styles.addStyleSheet(unrelatedStyle, {
|
||||
context: 'unrelated-context'
|
||||
})
|
||||
|
||||
return (extractedStyles = preview.getTextEditorStyles())
|
||||
})
|
||||
|
||||
it('returns an array containing atom-text-editor css style strings', function () {
|
||||
expect(extractedStyles.indexOf(textEditorStyle)).toBeGreaterThan(-1)
|
||||
})
|
||||
|
||||
it('does not return other styles', function () {
|
||||
expect(extractedStyles.indexOf(unrelatedStyle)).toBe(-1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when core:copy is triggered', function () {
|
||||
beforeEach(function () {
|
||||
preview.destroy()
|
||||
preview.element.remove()
|
||||
|
||||
const filePath = atom.project
|
||||
.getDirectories()[0]
|
||||
.resolve('subdir/code-block.md')
|
||||
preview = new MarkdownPreviewView({ filePath })
|
||||
jasmine.attachToDOM(preview.element)
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
})
|
||||
|
||||
describe('when there is no text selected', function () {
|
||||
it('copies the rendered HTML of the entire Markdown document to the clipboard', function () {
|
||||
expect(atom.clipboard.read()).toBe('initial clipboard content')
|
||||
|
||||
waitsForPromise(() =>
|
||||
atom.commands.dispatch(preview.element, 'core:copy')
|
||||
)
|
||||
|
||||
runs(() => {
|
||||
const element = document.createElement('div')
|
||||
element.innerHTML = atom.clipboard.read()
|
||||
expect(element.querySelector('h1').innerText).toBe('Code Block')
|
||||
expect(
|
||||
element.querySelector(
|
||||
'.line .syntax--source.syntax--js .syntax--constant.syntax--numeric'
|
||||
).innerText
|
||||
).toBe('3')
|
||||
expect(
|
||||
element.querySelector(
|
||||
'.line .syntax--source.syntax--js .syntax--keyword.syntax--control'
|
||||
).innerText
|
||||
).toBe('if')
|
||||
expect(
|
||||
element.querySelector(
|
||||
'.line .syntax--source.syntax--js .syntax--constant.syntax--numeric'
|
||||
).innerText
|
||||
).toBe('3')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when there is a text selection', function () {
|
||||
it('directly copies the selection to the clipboard', function () {
|
||||
const selection = window.getSelection()
|
||||
selection.removeAllRanges()
|
||||
const range = document.createRange()
|
||||
range.setStart(document.querySelector('atom-text-editor'), 0)
|
||||
range.setEnd(document.querySelector('p').firstChild, 3)
|
||||
selection.addRange(range)
|
||||
|
||||
atom.commands.dispatch(preview.element, 'core:copy')
|
||||
const clipboardText = atom.clipboard.read()
|
||||
|
||||
expect(clipboardText).toBe(`\
|
||||
if a === 3 {
|
||||
b = 5
|
||||
}
|
||||
|
||||
enc\
|
||||
`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when markdown-preview:select-all is triggered', function () {
|
||||
it('selects the entire Markdown preview', function () {
|
||||
const filePath = atom.project
|
||||
.getDirectories()[0]
|
||||
.resolve('subdir/code-block.md')
|
||||
const preview2 = new MarkdownPreviewView({ filePath })
|
||||
jasmine.attachToDOM(preview2.element)
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(function () {
|
||||
atom.commands.dispatch(preview.element, 'markdown-preview:select-all')
|
||||
const { commonAncestorContainer } = window.getSelection().getRangeAt(0)
|
||||
expect(commonAncestorContainer).toEqual(preview.element)
|
||||
})
|
||||
|
||||
waitsForPromise(() => preview2.renderMarkdown())
|
||||
|
||||
runs(() => {
|
||||
atom.commands.dispatch(preview2.element, 'markdown-preview:select-all')
|
||||
const selection = window.getSelection()
|
||||
expect(selection.rangeCount).toBe(1)
|
||||
const { commonAncestorContainer } = selection.getRangeAt(0)
|
||||
expect(commonAncestorContainer).toEqual(preview2.element)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('when markdown-preview:zoom-in or markdown-preview:zoom-out are triggered', function () {
|
||||
it('increases or decreases the zoom level of the markdown preview element', function () {
|
||||
jasmine.attachToDOM(preview.element)
|
||||
|
||||
waitsForPromise(() => preview.renderMarkdown())
|
||||
|
||||
runs(function () {
|
||||
const originalZoomLevel = getComputedStyle(preview.element).zoom
|
||||
atom.commands.dispatch(preview.element, 'markdown-preview:zoom-in')
|
||||
expect(getComputedStyle(preview.element).zoom).toBeGreaterThan(
|
||||
originalZoomLevel
|
||||
)
|
||||
atom.commands.dispatch(preview.element, 'markdown-preview:zoom-out')
|
||||
expect(getComputedStyle(preview.element).zoom).toBe(originalZoomLevel)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
156
packages/markdown-preview/styles/markdown-preview-default.less
Normal file
156
packages/markdown-preview/styles/markdown-preview-default.less
Normal file
@ -0,0 +1,156 @@
|
||||
|
||||
// Default Markdown Preview styles
|
||||
|
||||
// These are the default Markdown Preview styles.
|
||||
// They use the syntax-variables to adapt to the color scheme of syntax themes.
|
||||
|
||||
|
||||
@import "syntax-variables";
|
||||
|
||||
.markdown-preview:not([data-use-github-style]) {
|
||||
|
||||
@fg: @syntax-text-color;
|
||||
@bg: @syntax-background-color;
|
||||
|
||||
@fg-accent: @syntax-cursor-color;
|
||||
@fg-strong: contrast(@bg, darken(@fg, 32%), lighten(@fg, 32%));
|
||||
@fg-subtle: contrast(@fg, lighten(@fg, 16%), darken(@fg, 16%));
|
||||
|
||||
@border: contrast(@bg, lighten(@bg, 16%), darken(@bg, 16%));
|
||||
|
||||
@margin: 1.5em;
|
||||
|
||||
|
||||
padding: 2em;
|
||||
font-size: 1.2em;
|
||||
color: @fg;
|
||||
background-color: @bg;
|
||||
overflow: auto;
|
||||
|
||||
& > :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
|
||||
// Headings --------------------
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
line-height: 1.2;
|
||||
margin-top: @margin;
|
||||
margin-bottom: @margin/3;
|
||||
color: @fg-strong;
|
||||
}
|
||||
|
||||
h1 { font-size: 2.4em; font-weight: 300; }
|
||||
h2 { font-size: 1.8em; font-weight: 400; }
|
||||
h3 { font-size: 1.5em; font-weight: 500; }
|
||||
h4 { font-size: 1.2em; font-weight: 600; }
|
||||
h5 { font-size: 1.1em; font-weight: 600; }
|
||||
h6 { font-size: 1.0em; font-weight: 600; }
|
||||
|
||||
|
||||
// Emphasis --------------------
|
||||
|
||||
strong {
|
||||
color: @fg-strong;
|
||||
}
|
||||
|
||||
del {
|
||||
color: @fg-subtle;
|
||||
}
|
||||
|
||||
|
||||
// Link --------------------
|
||||
|
||||
a,
|
||||
a code {
|
||||
color: @fg-accent;
|
||||
}
|
||||
|
||||
|
||||
// Images --------------------
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
|
||||
// Paragraph --------------------
|
||||
|
||||
& > p {
|
||||
margin-top: 0;
|
||||
margin-bottom: @margin;
|
||||
}
|
||||
|
||||
|
||||
// List --------------------
|
||||
|
||||
& > ul,
|
||||
& > ol {
|
||||
margin-bottom: @margin;
|
||||
}
|
||||
|
||||
|
||||
// Blockquotes --------------------
|
||||
|
||||
blockquote {
|
||||
margin: @margin 0;
|
||||
font-size: inherit;
|
||||
color: @fg-subtle;
|
||||
border-color: @border;
|
||||
border-width: 4px;
|
||||
}
|
||||
|
||||
|
||||
// HR --------------------
|
||||
|
||||
hr {
|
||||
margin: @margin*2 0;
|
||||
border-top: 2px dashed @border;
|
||||
background: none;
|
||||
}
|
||||
|
||||
|
||||
// Table --------------------
|
||||
|
||||
table {
|
||||
margin: @margin 0;
|
||||
}
|
||||
|
||||
th {
|
||||
color: @fg-strong;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: .66em 1em;
|
||||
border: 1px solid @border;
|
||||
}
|
||||
|
||||
|
||||
// Code --------------------
|
||||
|
||||
code {
|
||||
color: @fg-strong;
|
||||
background-color: contrast(@syntax-background-color, lighten(@syntax-background-color, 8%), darken(@syntax-background-color, 6%));
|
||||
}
|
||||
|
||||
atom-text-editor {
|
||||
margin: @margin 0;
|
||||
padding: 1em;
|
||||
font-size: .92em;
|
||||
border-radius: 3px;
|
||||
background-color: contrast(@syntax-background-color, lighten(@syntax-background-color, 4%), darken(@syntax-background-color, 4%));
|
||||
}
|
||||
|
||||
|
||||
// KBD --------------------
|
||||
|
||||
kbd {
|
||||
color: @fg-strong;
|
||||
border: 1px solid @border;
|
||||
border-bottom: 2px solid darken(@border, 6%);
|
||||
background-color: contrast(@syntax-background-color, lighten(@syntax-background-color, 8%), darken(@syntax-background-color, 6%));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
|
||||
// GitHub.com styles
|
||||
|
||||
// These are the GitHub Flavored Markdown styles also found on github.com.
|
||||
// They can be anabled in the markdown-preview settings by turning on "Use GitHub.com styles".
|
||||
|
||||
|
||||
@import (reference) "../assets/primer-markdown";
|
||||
|
||||
.markdown-preview[data-use-github-style] {
|
||||
|
||||
// Includes GitHub.com styles from `../assets/primer-markdown.less`.
|
||||
// Source: https://github.com/primer/primer/tree/master/modules/primer-markdown
|
||||
.markdown-body();
|
||||
|
||||
|
||||
// The styles below override/complement the GitHub.com styles
|
||||
// It's needed because some markup or global styles are different
|
||||
padding: 30px;
|
||||
font-size: 16px;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
overflow: scroll;
|
||||
|
||||
a {
|
||||
color: #337ab7;
|
||||
}
|
||||
|
||||
code {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
atom-text-editor {
|
||||
padding: .8em 1em;
|
||||
margin-bottom: 1em;
|
||||
font-size: .85em;
|
||||
border-radius: 4px;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
41
packages/markdown-preview/styles/markdown-preview.less
Normal file
41
packages/markdown-preview/styles/markdown-preview.less
Normal file
@ -0,0 +1,41 @@
|
||||
|
||||
// Global Markdown Preview styles
|
||||
|
||||
.markdown-preview {
|
||||
atom-text-editor {
|
||||
// only show scrollbars on hover
|
||||
.scrollbars-visible-always & {
|
||||
.vertical-scrollbar,
|
||||
.horizontal-scrollbar {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
.scrollbars-visible-always &:hover {
|
||||
.vertical-scrollbar,
|
||||
.horizontal-scrollbar {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
user-select: auto;
|
||||
}
|
||||
|
||||
// move task list checkboxes
|
||||
.task-list-item input[type=checkbox] {
|
||||
position: absolute;
|
||||
margin: .25em 0 0 -1.4em;
|
||||
}
|
||||
|
||||
.task-list-item {
|
||||
list-style-type: none;
|
||||
}
|
||||
}
|
||||
|
||||
.markdown-spinner {
|
||||
margin: auto;
|
||||
background-image: url(images/octocat-spinner-128.gif);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 64px;
|
||||
background-position: top center;
|
||||
padding-top: 70px;
|
||||
text-align: center;
|
||||
}
|
1
packages/styleguide/.gitignore
vendored
Normal file
1
packages/styleguide/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
8
packages/styleguide/README.md
Normal file
8
packages/styleguide/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Styleguide package
|
||||
|
||||
Styleguide will show you all the UI components used in Pulsar. It is useful as a reference when developing themes and packages.
|
||||
|
||||
* <kbd>cmd-ctrl-shift-g</kbd> (macOS) and <kbd>ctrl-shift-g</kbd> (Windows and Linux) opens it in a new tab
|
||||
* You can click on the section headings to expand/collapse them
|
||||
|
||||
![Demo](https://cloud.githubusercontent.com/assets/378023/15767543/ccecf9bc-2983-11e6-9c5e-d228d39f52b0.png)
|
5
packages/styleguide/keymaps/styleguide.cson
Normal file
5
packages/styleguide/keymaps/styleguide.cson
Normal file
@ -0,0 +1,5 @@
|
||||
'.platform-darwin':
|
||||
'cmd-ctrl-G': 'styleguide:show'
|
||||
|
||||
'.platform-win32, .platform-linux':
|
||||
'ctrl-G': 'styleguide:show'
|
17
packages/styleguide/lib/code-block.js
Normal file
17
packages/styleguide/lib/code-block.js
Normal file
@ -0,0 +1,17 @@
|
||||
const {TextEditor} = require('atom')
|
||||
|
||||
module.exports =
|
||||
class CodeBlock {
|
||||
constructor (props) {
|
||||
this.editor = new TextEditor({readonly: true, keyboardInputEnabled: false})
|
||||
this.element = document.createElement('div')
|
||||
this.element.appendChild(this.editor.getElement())
|
||||
atom.grammars.assignLanguageMode(this.editor, props.grammarScopeName)
|
||||
this.update(props)
|
||||
}
|
||||
|
||||
update ({cssClass, code}) {
|
||||
this.editor.setText(code)
|
||||
this.element.classList.add(cssClass)
|
||||
}
|
||||
}
|
68
packages/styleguide/lib/example-select-list-view.js
Normal file
68
packages/styleguide/lib/example-select-list-view.js
Normal file
@ -0,0 +1,68 @@
|
||||
/** @babel */
|
||||
/** @jsx etch.dom */
|
||||
|
||||
import SelectListView from 'atom-select-list'
|
||||
import etch from 'etch'
|
||||
import dedent from 'dedent'
|
||||
import CodeBlock from './code-block'
|
||||
|
||||
export default class ExampleSelectListView {
|
||||
constructor () {
|
||||
this.jsExampleCode = dedent`
|
||||
import SelectListView from 'atom-select-list'
|
||||
|
||||
const selectListView = new SelectListView({
|
||||
items: ['one', 'two', 'three'],
|
||||
elementForItem: (item) => {
|
||||
const li = document.createElement('li')
|
||||
li.textContent = item
|
||||
return li
|
||||
},
|
||||
didConfirmSelection: (item) => {
|
||||
console.log('confirmed', item)
|
||||
},
|
||||
didCancelSelection: () => {
|
||||
console.log('cancelled')
|
||||
}
|
||||
})
|
||||
`
|
||||
etch.initialize(this)
|
||||
}
|
||||
|
||||
elementForItem (item) {
|
||||
const li = document.createElement('li')
|
||||
li.textContent = item
|
||||
return li
|
||||
}
|
||||
|
||||
didConfirmSelection (item) {
|
||||
console.log('confirmed', item)
|
||||
}
|
||||
|
||||
didCancelSelection () {
|
||||
console.log('cancelled')
|
||||
}
|
||||
|
||||
render () {
|
||||
return (
|
||||
<div className='example'>
|
||||
<div className='example-rendered'>
|
||||
<atom-panel className='modal'>
|
||||
<SelectListView
|
||||
items={['one', 'two', 'three']}
|
||||
elementForItem={this.elementForItem.bind(this)}
|
||||
onDidConfirmSelection={this.didConfirmSelection.bind(this)}
|
||||
onDidCancelSelection={this.didCancelSelection.bind(this)} />
|
||||
</atom-panel>
|
||||
</div>
|
||||
<div className='example-code show-example-space-pen'>
|
||||
<CodeBlock cssClass='example-space-pen' grammarScopeName='source.js' code={this.jsExampleCode} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
update () {
|
||||
|
||||
}
|
||||
}
|
73
packages/styleguide/lib/styleguide-section.js
Normal file
73
packages/styleguide/lib/styleguide-section.js
Normal file
@ -0,0 +1,73 @@
|
||||
/** @babel */
|
||||
/** @jsx etch.dom */
|
||||
|
||||
import etch from 'etch'
|
||||
|
||||
export default class StyleguideSection {
|
||||
constructor (props, children) {
|
||||
this.collapsed = props.collapsed
|
||||
this.title = props.title
|
||||
this.name = props.name
|
||||
this.children = children
|
||||
etch.initialize(this)
|
||||
if (props.onDidInitialize) {
|
||||
props.onDidInitialize(this)
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.loaded) {
|
||||
let className = 'bordered'
|
||||
if (this.collapsed) {
|
||||
className += ' collapsed'
|
||||
}
|
||||
return (
|
||||
<section className={className} dataset={{name: this.name}}>
|
||||
<h1 className='section-heading' onclick={() => this.toggle()}>{this.title}</h1>
|
||||
{this.children}
|
||||
</section>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<section className='bordered collapsed' dataset={{name: this.name}}>
|
||||
<h1 className='section-heading' onclick={() => this.toggle()}>{this.title}</h1>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
update (props, children) {
|
||||
if (props.title) {
|
||||
this.title = props.title
|
||||
}
|
||||
|
||||
if (props.name) {
|
||||
this.name = props.name
|
||||
}
|
||||
|
||||
if (children) {
|
||||
this.children = children
|
||||
}
|
||||
|
||||
if (props.didExpandOrCollapseSection) {
|
||||
this.didExpandOrCollapseSection = props.onDidExpandOrCollapseSection
|
||||
}
|
||||
|
||||
return etch.update(this)
|
||||
}
|
||||
|
||||
toggle () {
|
||||
return this.collapsed ? this.expand() : this.collapse()
|
||||
}
|
||||
|
||||
expand () {
|
||||
this.collapsed = false
|
||||
this.loaded = true
|
||||
return etch.update(this)
|
||||
}
|
||||
|
||||
collapse () {
|
||||
this.collapsed = true
|
||||
return etch.update(this)
|
||||
}
|
||||
}
|
1266
packages/styleguide/lib/styleguide-view.js
Normal file
1266
packages/styleguide/lib/styleguide-view.js
Normal file
File diff suppressed because it is too large
Load Diff
24
packages/styleguide/lib/styleguide.js
Normal file
24
packages/styleguide/lib/styleguide.js
Normal file
@ -0,0 +1,24 @@
|
||||
const {CompositeDisposable} = require('atom')
|
||||
let StyleguideView = null
|
||||
|
||||
const STYLEGUIDE_URI = 'atom://styleguide'
|
||||
|
||||
module.exports = {
|
||||
activate () {
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
this.subscriptions.add(atom.workspace.addOpener(filePath => {
|
||||
if (filePath === STYLEGUIDE_URI) return this.createStyleguideView({uri: STYLEGUIDE_URI})
|
||||
}))
|
||||
this.subscriptions.add(atom.commands.add('atom-workspace', 'styleguide:show', () => atom.workspace.open(STYLEGUIDE_URI))
|
||||
)
|
||||
},
|
||||
|
||||
deactivate () {
|
||||
this.subscriptions.dispose()
|
||||
},
|
||||
|
||||
createStyleguideView (state) {
|
||||
if (StyleguideView == null) StyleguideView = require('./styleguide-view')
|
||||
return new StyleguideView(state)
|
||||
}
|
||||
}
|
10
packages/styleguide/menus/styleguide.cson
Normal file
10
packages/styleguide/menus/styleguide.cson
Normal file
@ -0,0 +1,10 @@
|
||||
'menu': [
|
||||
'label': 'Packages'
|
||||
'submenu': [
|
||||
'label': 'Styleguide'
|
||||
'submenu': [
|
||||
'label': 'Show'
|
||||
'command': 'styleguide:show'
|
||||
]
|
||||
]
|
||||
]
|
50
packages/styleguide/package-lock.json
generated
Normal file
50
packages/styleguide/package-lock.json
generated
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"name": "styleguide",
|
||||
"version": "0.49.12",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "styleguide",
|
||||
"version": "0.49.12",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"atom-select-list": "^0.7.0",
|
||||
"dedent": "^0.7.0",
|
||||
"etch": "0.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/atom-select-list": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/atom-select-list/-/atom-select-list-0.7.2.tgz",
|
||||
"integrity": "sha512-a707OB1DhLGjzqtFrtMQKH7BBxFuCh8UBoUWxgFOrLrSwVh3g+/TlVPVDOz12+U0mDu3mIrnYLqQyhywQOTxhw==",
|
||||
"dependencies": {
|
||||
"etch": "^0.12.6",
|
||||
"fuzzaldrin": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/atom-select-list/node_modules/etch": {
|
||||
"version": "0.12.8",
|
||||
"resolved": "https://registry.npmjs.org/etch/-/etch-0.12.8.tgz",
|
||||
"integrity": "sha512-dFLRe4wLroVtwzyy1vGlE3BSDZHiL0kZME5XgNGzZIULcYTvVno8vbiIleAesoKJmwWaxDTzG+4eppg2zk14JQ=="
|
||||
},
|
||||
"node_modules/dedent": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
|
||||
"integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA=="
|
||||
},
|
||||
"node_modules/etch": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/etch/-/etch-0.9.0.tgz",
|
||||
"integrity": "sha512-UG0mzvvs8JyBo4tDG39mqGuZ7zZGKFn9QOzO+BhrKe17R/f+3U+jFgA/bjW/gTA2ykytcE/Qm826ltykCiIrFA=="
|
||||
},
|
||||
"node_modules/fuzzaldrin": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz",
|
||||
"integrity": "sha512-zgllBYwfHR5P3CncJiGbGVPpa3iFYW1yuPaIv8DiTVRrcg5/6uETNL5zvIoKflG1aifXVUZTlIroDehw4WygGA=="
|
||||
}
|
||||
}
|
||||
}
|
19
packages/styleguide/package.json
Normal file
19
packages/styleguide/package.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "styleguide",
|
||||
"main": "./lib/styleguide",
|
||||
"version": "0.49.12",
|
||||
"description": "A visual styleguide of the Pulsars's UI components.",
|
||||
"repository": "https://github.com/pulsar-edit/pulsar",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"atom-select-list": "^0.7.0",
|
||||
"dedent": "^0.7.0",
|
||||
"etch": "0.9.0"
|
||||
},
|
||||
"deserializers": {
|
||||
"StyleguideView": "createStyleguideView"
|
||||
},
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
}
|
||||
}
|
103
packages/styleguide/spec/async-spec-helpers.js
Normal file
103
packages/styleguide/spec/async-spec-helpers.js
Normal file
@ -0,0 +1,103 @@
|
||||
/** @babel */
|
||||
|
||||
export function beforeEach (fn) {
|
||||
global.beforeEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function afterEach (fn) {
|
||||
global.afterEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
|
||||
module.exports[name] = function (description, fn) {
|
||||
if (fn === undefined) {
|
||||
global[name](description)
|
||||
return
|
||||
}
|
||||
|
||||
global[name](description, function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export async function conditionPromise (condition, description = 'anonymous condition') {
|
||||
const startTime = Date.now()
|
||||
|
||||
while (true) {
|
||||
await timeoutPromise(100)
|
||||
|
||||
if (await condition()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (Date.now() - startTime > 5000) {
|
||||
throw new Error('Timed out waiting on ' + description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function timeoutPromise (timeout) {
|
||||
return new Promise(function (resolve) {
|
||||
global.setTimeout(resolve, timeout)
|
||||
})
|
||||
}
|
||||
|
||||
function waitsForPromise (fn) {
|
||||
const promise = fn()
|
||||
global.waitsFor('spec promise to resolve', function (done) {
|
||||
promise.then(done, function (error) {
|
||||
jasmine.getEnv().currentSpec.fail(error)
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function emitterEventPromise (emitter, event, timeout = 15000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`Timed out waiting for '${event}' event`))
|
||||
}, timeout)
|
||||
emitter.once(event, () => {
|
||||
clearTimeout(timeoutHandle)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function promisify (original) {
|
||||
return function (...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((err, ...results) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(...results)
|
||||
}
|
||||
})
|
||||
|
||||
return original(...args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function promisifySome (obj, fnNames) {
|
||||
const result = {}
|
||||
for (const fnName of fnNames) {
|
||||
result[fnName] = promisify(obj[fnName])
|
||||
}
|
||||
return result
|
||||
}
|
18
packages/styleguide/spec/styleguide-spec.js
Normal file
18
packages/styleguide/spec/styleguide-spec.js
Normal file
@ -0,0 +1,18 @@
|
||||
const {it, fit, ffit, beforeEach, afterEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
|
||||
|
||||
describe('Style Guide', () => {
|
||||
beforeEach(async () => {
|
||||
await atom.packages.activatePackage('styleguide')
|
||||
})
|
||||
|
||||
describe('the Styleguide view', () => {
|
||||
let styleGuideView
|
||||
beforeEach(async () => {
|
||||
styleGuideView = await atom.workspace.open('atom://styleguide')
|
||||
})
|
||||
|
||||
it('opens the style guide', () => {
|
||||
expect(styleGuideView.element.textContent).toContain('Styleguide')
|
||||
})
|
||||
})
|
||||
})
|
134
packages/styleguide/styles/components.less
Normal file
134
packages/styleguide/styles/components.less
Normal file
@ -0,0 +1,134 @@
|
||||
@import "ui-variables";
|
||||
|
||||
//
|
||||
// This adds some component styles specifically for the Styleguide
|
||||
// --------------------------------
|
||||
|
||||
.styleguide {
|
||||
|
||||
// Icons ---------------
|
||||
|
||||
[data-name="icons"] {
|
||||
.example-rendered {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: flex-start;
|
||||
}
|
||||
|
||||
.example-code {
|
||||
display: none; // remove, doesn't make much sense here
|
||||
}
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
flex: 1 0 200px;
|
||||
padding: 10px 0 10px 40px;
|
||||
color: @text-color-subtle;
|
||||
&:before {
|
||||
position: absolute;
|
||||
margin-left: -32px;
|
||||
color: @text-color-highlight;
|
||||
text-align: center;
|
||||
}
|
||||
&:hover {
|
||||
color: @text-color-highlight;
|
||||
&:before {
|
||||
color: @text-color-selected;
|
||||
font-size: 32px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-top: -8px;
|
||||
margin-left: -40px; // 32px (initial) + 8px (grow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make the Gist logo bigger
|
||||
.icon-logo-gist {
|
||||
&:before,
|
||||
&:hover:before {
|
||||
font-size: 2.5em;
|
||||
margin-top: -.05em;
|
||||
margin-left: -1.3em;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
// Make the GitHub logo bigger
|
||||
.icon-logo-github {
|
||||
&:before,
|
||||
&:hover:before {
|
||||
font-size: 3em;
|
||||
margin-top: .08em;
|
||||
margin-left: -1.2em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inputs + controls ---------------
|
||||
|
||||
.input-search,
|
||||
.input-textarea {
|
||||
margin-top: @component-padding;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
display: block;
|
||||
width: -webkit-max-content;
|
||||
margin: 0 1em 1em 0;
|
||||
}
|
||||
|
||||
.input-color,
|
||||
.input-number,
|
||||
.input-select {
|
||||
margin: 0 @component-padding 0 0 !important;
|
||||
}
|
||||
|
||||
|
||||
// Site colors ---------------
|
||||
|
||||
.ui-site-1,
|
||||
.ui-site-2,
|
||||
.ui-site-3,
|
||||
.ui-site-4,
|
||||
.ui-site-5 {
|
||||
height: 10px;
|
||||
width: 100px;
|
||||
}
|
||||
|
||||
|
||||
// Modals ---------------
|
||||
|
||||
atom-panel.modal {
|
||||
// makde them responsive in the styleguide
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
// Misc ---------------
|
||||
|
||||
.popover-list {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.popover-list,
|
||||
.select-list {
|
||||
atom-text-editor[mini] { height: 27px; }
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
opacity: 1;
|
||||
display: inline-block;
|
||||
margin-right: @component-padding;
|
||||
}
|
||||
|
||||
[data-name="error-messages"] .example-rendered {
|
||||
min-height: 60px; // don't cut off centered messages
|
||||
}
|
||||
|
||||
}
|
129
packages/styleguide/styles/styleguide.less
Normal file
129
packages/styleguide/styles/styleguide.less
Normal file
@ -0,0 +1,129 @@
|
||||
@import "ui-variables";
|
||||
@import "syntax-variables";
|
||||
|
||||
@styleguide-spacing: @component-padding *1.5;
|
||||
@styleguide-bg: darken(@base-background-color, 2%);
|
||||
|
||||
.styleguide {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.styleguide-controls {
|
||||
position: absolute;
|
||||
right: @component-padding;
|
||||
top: @component-padding;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.styleguide-header {
|
||||
padding: @styleguide-spacing;
|
||||
border-bottom: 1px solid @base-border-color;
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0 0 .5em 0;
|
||||
color: @text-color-highlight;
|
||||
}
|
||||
p {
|
||||
font-size: 1.2em;
|
||||
&:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.styleguide-sections {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
|
||||
& > section {
|
||||
background-color: @styleguide-bg;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid @base-border-color;
|
||||
border-top: none;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.collapsed {
|
||||
background-color: @base-background-color;
|
||||
> .section-heading {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding-bottom: @styleguide-spacing;
|
||||
color: @text-color;
|
||||
&:hover {
|
||||
color: @text-color-highlight;
|
||||
background-color: @background-color-highlight;
|
||||
}
|
||||
&:active {
|
||||
background-color: @base-background-color;
|
||||
}
|
||||
}
|
||||
> * {
|
||||
display: none
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-heading.section-heading {
|
||||
padding: @styleguide-spacing @styleguide-spacing 0 @styleguide-spacing;
|
||||
cursor: pointer;
|
||||
font-weight: normal;
|
||||
font-size: 1.8em;
|
||||
color: @text-color-highlight;
|
||||
}
|
||||
|
||||
section > h2 {
|
||||
font-size: 1.5em;
|
||||
line-height: 1.2;
|
||||
margin: 1em @styleguide-spacing 0 @styleguide-spacing;
|
||||
color: @text-color-highlight;
|
||||
}
|
||||
|
||||
section > p {
|
||||
font-size: 1.1em;
|
||||
margin: .5em @styleguide-spacing 1em @styleguide-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
// Example -------------------------------
|
||||
|
||||
.styleguide .example {
|
||||
@example-background: @base-background-color;
|
||||
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
border-radius: @component-border-radius;
|
||||
padding: @component-padding / 2;
|
||||
|
||||
.example-rendered,
|
||||
.example-code {
|
||||
position: relative;
|
||||
flex: 1 1 300px;
|
||||
min-width: 0;
|
||||
margin: @component-padding / 2;
|
||||
border-radius: @component-border-radius;
|
||||
border: 1px solid @tool-panel-border-color;
|
||||
}
|
||||
|
||||
.example-rendered {
|
||||
padding: @component-padding;
|
||||
background: @example-background;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.example-code {
|
||||
background-color: @syntax-background-color;
|
||||
pre {
|
||||
border: none;
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
148
packages/styleguide/styles/variables.less
Normal file
148
packages/styleguide/styles/variables.less
Normal file
@ -0,0 +1,148 @@
|
||||
@import "ui-variables";
|
||||
@import "syntax-variables";
|
||||
|
||||
.styleguide [data-name="variables"] {
|
||||
|
||||
// Text colors
|
||||
.color( text-color );
|
||||
.color( text-color-subtle );
|
||||
.color( text-color-highlight );
|
||||
.color( text-color-selected );
|
||||
.color( text-color-info );
|
||||
.color( text-color-success );
|
||||
.color( text-color-warning );
|
||||
.color( text-color-error );
|
||||
|
||||
// Background colors
|
||||
.color( background-color-info );
|
||||
.color( background-color-success );
|
||||
.color( background-color-warning );
|
||||
.color( background-color-error );
|
||||
.color( background-color-highlight );
|
||||
.color( background-color-selected );
|
||||
.color( app-background-color );
|
||||
|
||||
// Base colors
|
||||
.color( base-background-color );
|
||||
.color( base-border-color );
|
||||
|
||||
// Pane colors
|
||||
.color( pane-item-background-color );
|
||||
.color( pane-item-border-color );
|
||||
|
||||
// Input colors
|
||||
.color( input-background-color );
|
||||
.color( input-border-color );
|
||||
|
||||
// Panel colors
|
||||
.color( tool-panel-background-color );
|
||||
.color( tool-panel-border-color );
|
||||
.color( inset-panel-background-color );
|
||||
.color( inset-panel-border-color );
|
||||
.color( panel-heading-background-color );
|
||||
.color( panel-heading-border-color );
|
||||
.color( overlay-background-color );
|
||||
.color( overlay-border-color );
|
||||
|
||||
// Button colors
|
||||
.color( button-background-color );
|
||||
.color( button-background-color-hover );
|
||||
.color( button-background-color-selected );
|
||||
.color( button-border-color );
|
||||
|
||||
// Tab colors
|
||||
.color( tab-bar-background-color );
|
||||
.color( tab-bar-border-color );
|
||||
.color( tab-background-color );
|
||||
.color( tab-background-color-active );
|
||||
.color( tab-border-color );
|
||||
|
||||
// Tree-view colors
|
||||
.color( tree-view-background-color );
|
||||
.color( tree-view-border-color );
|
||||
|
||||
// Site colors
|
||||
.color( ui-site-color-1 );
|
||||
.color( ui-site-color-2 );
|
||||
.color( ui-site-color-3 );
|
||||
.color( ui-site-color-4 );
|
||||
.color( ui-site-color-5 );
|
||||
|
||||
// Component sizes
|
||||
.size( disclosure-arrow-size );
|
||||
.size( component-padding );
|
||||
.size( component-icon-padding );
|
||||
.size( component-icon-size );
|
||||
.size( component-line-height );
|
||||
.size( tab-height );
|
||||
.size( font-size );
|
||||
|
||||
// Misc
|
||||
.radius( component-border-radius );
|
||||
.font( font-family );
|
||||
|
||||
|
||||
// Visualize --------------------------------
|
||||
|
||||
.is-color:before,
|
||||
.is-size:after,
|
||||
.is-radius:after {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.is-color:before {
|
||||
margin-right: @component-padding*1.5;
|
||||
width: 20%;
|
||||
}
|
||||
.is-size:after {
|
||||
margin-left: @component-padding*1.5;
|
||||
height: 4px;
|
||||
background-color: @text-color;
|
||||
}
|
||||
.is-radius:after {
|
||||
width: 20px;
|
||||
margin-left: @component-padding*1.5;
|
||||
background-color: @text-color;
|
||||
}
|
||||
.is-font:after {
|
||||
margin-left: @component-padding;
|
||||
color: @text-color-highlight;
|
||||
}
|
||||
|
||||
|
||||
// Mixins --------------------------------
|
||||
|
||||
.color(@variable) {
|
||||
.is-color.@{variable}:before {
|
||||
background-color: @@variable;
|
||||
}
|
||||
}
|
||||
|
||||
.size(@variable) {
|
||||
.is-size.@{variable}:after {
|
||||
width: @@variable;
|
||||
}
|
||||
}
|
||||
|
||||
.radius(@variable) {
|
||||
.is-radius.@{variable}:after {
|
||||
border-radius: @@variable;
|
||||
}
|
||||
}
|
||||
|
||||
.font(@variable) {
|
||||
.is-font.@{variable}:after {
|
||||
content: @@variable;
|
||||
font-family: @@variable;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Custom styling for vars --------------------------------
|
||||
|
||||
.example-code {
|
||||
display: none; // remove, doesn't make much sense here
|
||||
}
|
||||
}
|
@ -53,40 +53,40 @@ export default class ChangeLogView {
|
||||
<p>Feel free to read our <a href="https://github.com/pulsar-edit/pulsar/blob/master/CHANGELOG.md">Full Change Log</a>.</p>
|
||||
<ul>
|
||||
<li>
|
||||
Fixed a bug where macOS menus like "Open" don't do anything.
|
||||
Signed Pulsar macOS Binaries
|
||||
</li>
|
||||
<li>
|
||||
Fixed a bug where macOS wouldn't open files by dragging them onto the dock.
|
||||
Fixed a bug where `pulsar` on Windows could never trigger
|
||||
</li>
|
||||
<li>
|
||||
Fixed a bug where devtools won't open.
|
||||
Fixed `github` package shelling out to `git` on macOS
|
||||
</li>
|
||||
<li>
|
||||
Fixed a bug where the editor refused to open with the message "GPU process isn't usable. Goodbye"
|
||||
Fixed minor bugs found during fixes to tests
|
||||
</li>
|
||||
<li>
|
||||
Fixed logo artifacts on Linux.
|
||||
Improved our testing infastructure to aide in finding and fixing further bugs
|
||||
</li>
|
||||
<li>
|
||||
Fixed Windows Taskbar Icon being 'Cut in Half'
|
||||
Updated many dependencies of Pulsar and its core packages
|
||||
</li>
|
||||
<li>
|
||||
Fixed commands like `--version`, `--package` or `--help` did not show outputs.
|
||||
New Pulsar Icon on macOS
|
||||
</li>
|
||||
<li>
|
||||
Fixed additional flags not being sent to `--package`.
|
||||
Selected text is styled by default
|
||||
</li>
|
||||
<li>
|
||||
Small improvement on the binary size.
|
||||
Restored `right-clicked` CSS class on tags
|
||||
</li>
|
||||
<li>
|
||||
Fixed "install command line tools" on Mac and Windows.
|
||||
Fixed syntax highlighting on C++
|
||||
</li>
|
||||
<li>
|
||||
Cached queries for featured packages (featured packages will load faster, and fewer errors on the settings-view regarding package info).
|
||||
Updated JavaScript snippets to modern ES6 syntax
|
||||
</li>
|
||||
<li>
|
||||
Added warning when `settings-view` is disabled, describing how to re-enable it.
|
||||
PPM no longer assumes `master` for git branches
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
|
1
packages/wrap-guide/.gitignore
vendored
Normal file
1
packages/wrap-guide/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules
|
35
packages/wrap-guide/README.md
Normal file
35
packages/wrap-guide/README.md
Normal file
@ -0,0 +1,35 @@
|
||||
# Wrap Guide package
|
||||
|
||||
The `wrap-guide` package places a vertical line in each editor at a certain column to guide your formatting, so lines do not exceed a certain width.
|
||||
|
||||
By default, the wrap-guide is placed at the value of `editor.preferredLineLength` config setting. The 80th column is used as the fallback if the config value is unset.
|
||||
|
||||
![](https://f.cloud.github.com/assets/671378/2241976/dbf6a8f6-9ced-11e3-8fef-d8a226301530.png)
|
||||
|
||||
## Configuration
|
||||
|
||||
You can customize where the column is placed for different file types by opening the Settings View and configuring the "Preferred Line Length" value. If you do not want the guide to show for a particular language, that can be set using scoped configuration. For example, to turn off the guide for GitHub-Flavored Markdown, you can add the following to your `config.cson`:
|
||||
|
||||
```coffeescript
|
||||
'.source.gfm':
|
||||
'wrap-guide':
|
||||
'enabled': false
|
||||
```
|
||||
|
||||
It is possible to configure the color and/or width of the line by adding the following CSS/LESS to your `styles.less`:
|
||||
|
||||
```css
|
||||
atom-text-editor .wrap-guide {
|
||||
width: 10px;
|
||||
background-color: red;
|
||||
}
|
||||
```
|
||||
|
||||
Multiple guide lines are also supported. For example, add the following to your `config.cson` to create four columns at the indicated positions:
|
||||
|
||||
```coffeescript
|
||||
'wrap-guide':
|
||||
'columns': [72, 80, 100, 120]
|
||||
```
|
||||
|
||||
> Note: When using multiple guide lines, the right-most guide line functions as your `editor.preferredLineLength` setting.
|
26
packages/wrap-guide/lib/main.coffee
Normal file
26
packages/wrap-guide/lib/main.coffee
Normal file
@ -0,0 +1,26 @@
|
||||
{CompositeDisposable} = require 'atom'
|
||||
WrapGuideElement = require './wrap-guide-element'
|
||||
|
||||
module.exports =
|
||||
activate: ->
|
||||
@subscriptions = new CompositeDisposable()
|
||||
@wrapGuides = new Map()
|
||||
|
||||
@subscriptions.add atom.workspace.observeTextEditors (editor) =>
|
||||
return if @wrapGuides.has(editor)
|
||||
|
||||
editorElement = atom.views.getView(editor)
|
||||
wrapGuideElement = new WrapGuideElement(editor, editorElement)
|
||||
|
||||
@wrapGuides.set(editor, wrapGuideElement)
|
||||
@subscriptions.add editor.onDidDestroy =>
|
||||
@wrapGuides.get(editor).destroy()
|
||||
@wrapGuides.delete(editor)
|
||||
|
||||
deactivate: ->
|
||||
@subscriptions.dispose()
|
||||
@wrapGuides.forEach (wrapGuide, editor) -> wrapGuide.destroy()
|
||||
@wrapGuides.clear()
|
||||
|
||||
uniqueAscending: (list) ->
|
||||
(list.filter((item, index) -> list.indexOf(item) is index)).sort((a, b) -> a - b)
|
137
packages/wrap-guide/lib/wrap-guide-element.coffee
Normal file
137
packages/wrap-guide/lib/wrap-guide-element.coffee
Normal file
@ -0,0 +1,137 @@
|
||||
{CompositeDisposable} = require 'atom'
|
||||
|
||||
module.exports =
|
||||
class WrapGuideElement
|
||||
constructor: (@editor, @editorElement) ->
|
||||
@subscriptions = new CompositeDisposable()
|
||||
@configSubscriptions = new CompositeDisposable()
|
||||
@element = document.createElement('div')
|
||||
@element.setAttribute('is', 'wrap-guide')
|
||||
@element.classList.add('wrap-guide-container')
|
||||
@attachToLines()
|
||||
@handleEvents()
|
||||
@updateGuide()
|
||||
|
||||
@element.updateGuide = @updateGuide.bind(this)
|
||||
@element.getDefaultColumn = @getDefaultColumn.bind(this)
|
||||
|
||||
attachToLines: ->
|
||||
scrollView = @editorElement.querySelector('.scroll-view')
|
||||
scrollView?.appendChild(@element)
|
||||
|
||||
handleEvents: ->
|
||||
updateGuideCallback = => @updateGuide()
|
||||
|
||||
@handleConfigEvents()
|
||||
|
||||
@subscriptions.add atom.config.onDidChange 'editor.fontSize', =>
|
||||
# Wait for editor to finish updating before updating wrap guide
|
||||
# TODO: Use async/await once this file is converted to JS
|
||||
@editorElement.getComponent().getNextUpdatePromise().then -> updateGuideCallback()
|
||||
|
||||
@subscriptions.add @editorElement.onDidChangeScrollLeft(updateGuideCallback)
|
||||
@subscriptions.add @editor.onDidChangePath(updateGuideCallback)
|
||||
@subscriptions.add @editor.onDidChangeGrammar =>
|
||||
@configSubscriptions.dispose()
|
||||
@handleConfigEvents()
|
||||
updateGuideCallback()
|
||||
|
||||
@subscriptions.add @editor.onDidDestroy =>
|
||||
@subscriptions.dispose()
|
||||
@configSubscriptions.dispose()
|
||||
|
||||
@subscriptions.add @editorElement.onDidAttach =>
|
||||
@attachToLines()
|
||||
updateGuideCallback()
|
||||
|
||||
handleConfigEvents: ->
|
||||
{uniqueAscending} = require './main'
|
||||
|
||||
updatePreferredLineLengthCallback = (args) =>
|
||||
# ensure that the right-most wrap guide is the preferredLineLength
|
||||
columns = atom.config.get('wrap-guide.columns', scope: @editor.getRootScopeDescriptor())
|
||||
if columns.length > 0
|
||||
columns[columns.length - 1] = args.newValue
|
||||
columns = uniqueAscending(i for i in columns when i <= args.newValue)
|
||||
atom.config.set 'wrap-guide.columns', columns,
|
||||
scopeSelector: ".#{@editor.getGrammar().scopeName}"
|
||||
@updateGuide()
|
||||
@configSubscriptions.add atom.config.onDidChange(
|
||||
'editor.preferredLineLength',
|
||||
scope: @editor.getRootScopeDescriptor(),
|
||||
updatePreferredLineLengthCallback
|
||||
)
|
||||
|
||||
updateGuideCallback = => @updateGuide()
|
||||
@configSubscriptions.add atom.config.onDidChange(
|
||||
'wrap-guide.enabled',
|
||||
scope: @editor.getRootScopeDescriptor(),
|
||||
updateGuideCallback
|
||||
)
|
||||
|
||||
updateGuidesCallback = (args) =>
|
||||
# ensure that multiple guides stay sorted in ascending order
|
||||
columns = uniqueAscending(args.newValue)
|
||||
if columns?.length
|
||||
atom.config.set('wrap-guide.columns', columns)
|
||||
atom.config.set 'editor.preferredLineLength', columns[columns.length - 1],
|
||||
scopeSelector: ".#{@editor.getGrammar().scopeName}"
|
||||
@updateGuide()
|
||||
@configSubscriptions.add atom.config.onDidChange(
|
||||
'wrap-guide.columns',
|
||||
scope: @editor.getRootScopeDescriptor(),
|
||||
updateGuidesCallback
|
||||
)
|
||||
|
||||
getDefaultColumn: ->
|
||||
atom.config.get('editor.preferredLineLength', scope: @editor.getRootScopeDescriptor())
|
||||
|
||||
getGuidesColumns: (path, scopeName) ->
|
||||
columns = atom.config.get('wrap-guide.columns', scope: @editor.getRootScopeDescriptor()) ? []
|
||||
return columns if columns.length > 0
|
||||
return [@getDefaultColumn()]
|
||||
|
||||
isEnabled: ->
|
||||
atom.config.get('wrap-guide.enabled', scope: @editor.getRootScopeDescriptor()) ? true
|
||||
|
||||
hide: ->
|
||||
@element.style.display = 'none'
|
||||
|
||||
show: ->
|
||||
@element.style.display = 'block'
|
||||
|
||||
updateGuide: ->
|
||||
if @isEnabled()
|
||||
@updateGuides()
|
||||
else
|
||||
@hide()
|
||||
|
||||
updateGuides: ->
|
||||
@removeGuides()
|
||||
@appendGuides()
|
||||
if @element.children.length
|
||||
@show()
|
||||
else
|
||||
@hide()
|
||||
|
||||
destroy: ->
|
||||
@element.remove()
|
||||
@subscriptions.dispose()
|
||||
@configSubscriptions.dispose()
|
||||
|
||||
removeGuides: ->
|
||||
while @element.firstChild
|
||||
@element.removeChild(@element.firstChild)
|
||||
|
||||
appendGuides: ->
|
||||
columns = @getGuidesColumns(@editor.getPath(), @editor.getGrammar().scopeName)
|
||||
for column in columns
|
||||
@appendGuide(column) unless column < 0
|
||||
|
||||
appendGuide: (column) ->
|
||||
columnWidth = @editorElement.getDefaultCharacterWidth() * column
|
||||
columnWidth -= @editorElement.getScrollLeft()
|
||||
guide = document.createElement('div')
|
||||
guide.classList.add('wrap-guide')
|
||||
guide.style.left = "#{Math.round(columnWidth)}px"
|
||||
@element.appendChild(guide)
|
16
packages/wrap-guide/package-lock.json
generated
Normal file
16
packages/wrap-guide/package-lock.json
generated
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "wrap-guide",
|
||||
"version": "0.41.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wrap-guide",
|
||||
"version": "0.41.0",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
packages/wrap-guide/package.json
Normal file
25
packages/wrap-guide/package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "wrap-guide",
|
||||
"version": "0.41.0",
|
||||
"main": "./lib/main",
|
||||
"description": "Displays a vertical line at the 80th character in the editor.\nThis packages uses the config value of `editor.preferredLineLength` when set.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/pulsar-edit/pulsar",
|
||||
"engines": {
|
||||
"atom": "*"
|
||||
},
|
||||
"configSchema": {
|
||||
"columns": {
|
||||
"default": [],
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"description": "Display guides at each of the listed character widths. Leave blank for one guide at your `editor.preferredLineLength`."
|
||||
},
|
||||
"enabled": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
103
packages/wrap-guide/spec/async-spec-helpers.js
Normal file
103
packages/wrap-guide/spec/async-spec-helpers.js
Normal file
@ -0,0 +1,103 @@
|
||||
/** @babel */
|
||||
|
||||
export function beforeEach (fn) {
|
||||
global.beforeEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function afterEach (fn) {
|
||||
global.afterEach(function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
|
||||
module.exports[name] = function (description, fn) {
|
||||
if (fn === undefined) {
|
||||
global[name](description)
|
||||
return
|
||||
}
|
||||
|
||||
global[name](description, function () {
|
||||
const result = fn()
|
||||
if (result instanceof Promise) {
|
||||
waitsForPromise(() => result)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export async function conditionPromise (condition, description = 'anonymous condition') {
|
||||
const startTime = Date.now()
|
||||
|
||||
while (true) {
|
||||
await timeoutPromise(100)
|
||||
|
||||
if (await condition()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (Date.now() - startTime > 5000) {
|
||||
throw new Error('Timed out waiting on ' + description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function timeoutPromise (timeout) {
|
||||
return new Promise(function (resolve) {
|
||||
global.setTimeout(resolve, timeout)
|
||||
})
|
||||
}
|
||||
|
||||
function waitsForPromise (fn) {
|
||||
const promise = fn()
|
||||
global.waitsFor('spec promise to resolve', function (done) {
|
||||
promise.then(done, function (error) {
|
||||
jasmine.getEnv().currentSpec.fail(error)
|
||||
done()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function emitterEventPromise (emitter, event, timeout = 15000) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`Timed out waiting for '${event}' event`))
|
||||
}, timeout)
|
||||
emitter.once(event, () => {
|
||||
clearTimeout(timeoutHandle)
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export function promisify (original) {
|
||||
return function (...args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
args.push((err, ...results) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(...results)
|
||||
}
|
||||
})
|
||||
|
||||
return original(...args)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function promisifySome (obj, fnNames) {
|
||||
const result = {}
|
||||
for (const fnName of fnNames) {
|
||||
result[fnName] = promisify(obj[fnName])
|
||||
}
|
||||
return result
|
||||
}
|
20
packages/wrap-guide/spec/helpers.js
Normal file
20
packages/wrap-guide/spec/helpers.js
Normal file
@ -0,0 +1,20 @@
|
||||
const helpers = {
|
||||
getWrapGuides () {
|
||||
wrapGuides = []
|
||||
for (const editor of atom.workspace.getTextEditors()) {
|
||||
const guide = editor.getElement().querySelector('.wrap-guide')
|
||||
if (guide) wrapGuides.push(guide)
|
||||
}
|
||||
return wrapGuides
|
||||
},
|
||||
|
||||
getLeftPosition (element) {
|
||||
return parseInt(element.style.left)
|
||||
},
|
||||
|
||||
getLeftPositions (elements) {
|
||||
return Array.prototype.map.call(elements, element => helpers.getLeftPosition(element))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = helpers
|
275
packages/wrap-guide/spec/wrap-guide-element-spec.coffee
Normal file
275
packages/wrap-guide/spec/wrap-guide-element-spec.coffee
Normal file
@ -0,0 +1,275 @@
|
||||
{getLeftPosition, getLeftPositions} = require './helpers'
|
||||
{uniqueAscending} = require '../lib/main'
|
||||
|
||||
describe "WrapGuideElement", ->
|
||||
[editor, editorElement, wrapGuide, workspaceElement] = []
|
||||
|
||||
beforeEach ->
|
||||
workspaceElement = atom.views.getView(atom.workspace)
|
||||
workspaceElement.style.height = "200px"
|
||||
workspaceElement.style.width = "1500px"
|
||||
|
||||
jasmine.attachToDOM(workspaceElement)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('wrap-guide')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-javascript')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.packages.activatePackage('language-coffee-script')
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open('sample.js')
|
||||
|
||||
runs ->
|
||||
editor = atom.workspace.getActiveTextEditor()
|
||||
editorElement = editor.getElement()
|
||||
wrapGuide = editorElement.querySelector(".wrap-guide-container")
|
||||
|
||||
describe ".activate", ->
|
||||
getWrapGuides = ->
|
||||
wrapGuides = []
|
||||
atom.workspace.getTextEditors().forEach (editor) ->
|
||||
guides = editor.getElement().querySelectorAll(".wrap-guide")
|
||||
wrapGuides.push(guides) if guides
|
||||
wrapGuides
|
||||
|
||||
it "appends a wrap guide to all existing and new editors", ->
|
||||
expect(atom.workspace.getTextEditors().length).toBe 1
|
||||
|
||||
expect(getWrapGuides().length).toBe 1
|
||||
expect(getLeftPosition(getWrapGuides()[0][0])).toBeGreaterThan(0)
|
||||
|
||||
atom.workspace.getActivePane().splitRight(copyActiveItem: true)
|
||||
expect(atom.workspace.getTextEditors().length).toBe 2
|
||||
expect(getWrapGuides().length).toBe 2
|
||||
expect(getLeftPosition(getWrapGuides()[0][0])).toBeGreaterThan(0)
|
||||
expect(getLeftPosition(getWrapGuides()[1][0])).toBeGreaterThan(0)
|
||||
|
||||
it "positions the guide at the configured column", ->
|
||||
width = editor.getDefaultCharWidth() * wrapGuide.getDefaultColumn()
|
||||
expect(width).toBeGreaterThan(0)
|
||||
expect(Math.abs(getLeftPosition(wrapGuide.firstChild) - width)).toBeLessThan 1
|
||||
expect(wrapGuide).toBeVisible()
|
||||
|
||||
it "appends multiple wrap guides to all existing and new editors", ->
|
||||
columns = [10, 20, 30]
|
||||
atom.config.set("wrap-guide.columns", columns)
|
||||
|
||||
waitsForPromise ->
|
||||
editorElement.getComponent().getNextUpdatePromise()
|
||||
|
||||
runs ->
|
||||
expect(atom.workspace.getTextEditors().length).toBe 1
|
||||
expect(getWrapGuides().length).toBe 1
|
||||
positions = getLeftPositions(getWrapGuides()[0])
|
||||
expect(positions.length).toBe(columns.length)
|
||||
expect(positions[0]).toBeGreaterThan(0)
|
||||
expect(positions[1]).toBeGreaterThan(positions[0])
|
||||
expect(positions[2]).toBeGreaterThan(positions[1])
|
||||
|
||||
atom.workspace.getActivePane().splitRight(copyActiveItem: true)
|
||||
expect(atom.workspace.getTextEditors().length).toBe 2
|
||||
expect(getWrapGuides().length).toBe 2
|
||||
pane1_positions = getLeftPositions(getWrapGuides()[0])
|
||||
expect(pane1_positions.length).toBe(columns.length)
|
||||
expect(pane1_positions[0]).toBeGreaterThan(0)
|
||||
expect(pane1_positions[1]).toBeGreaterThan(pane1_positions[0])
|
||||
expect(pane1_positions[2]).toBeGreaterThan(pane1_positions[1])
|
||||
pane2_positions = getLeftPositions(getWrapGuides()[1])
|
||||
expect(pane2_positions.length).toBe(pane1_positions.length)
|
||||
expect(pane2_positions[0]).toBe(pane1_positions[0])
|
||||
expect(pane2_positions[1]).toBe(pane1_positions[1])
|
||||
expect(pane2_positions[2]).toBe(pane1_positions[2])
|
||||
|
||||
it "positions multiple guides at the configured columns", ->
|
||||
columnCount = 5
|
||||
columns = (c * 10 for c in [1..columnCount])
|
||||
atom.config.set("wrap-guide.columns", columns)
|
||||
waitsForPromise ->
|
||||
editorElement.getComponent().getNextUpdatePromise()
|
||||
|
||||
runs ->
|
||||
positions = getLeftPositions(getWrapGuides()[0])
|
||||
expect(positions.length).toBe(columnCount)
|
||||
expect(wrapGuide.children.length).toBe(columnCount)
|
||||
|
||||
for i in columnCount - 1
|
||||
width = editor.getDefaultCharWidth() * columns[i]
|
||||
expect(width).toBeGreaterThan(0)
|
||||
expect(Math.abs(getLeftPosition(wrapGuide.children[i]) - width)).toBeLessThan 1
|
||||
expect(wrapGuide).toBeVisible()
|
||||
|
||||
describe "when the font size changes", ->
|
||||
it "updates the wrap guide position", ->
|
||||
initial = getLeftPosition(wrapGuide.firstChild)
|
||||
expect(initial).toBeGreaterThan(0)
|
||||
fontSize = atom.config.get("editor.fontSize")
|
||||
atom.config.set("editor.fontSize", fontSize + 10)
|
||||
|
||||
waitsForPromise ->
|
||||
editorElement.getComponent().getNextUpdatePromise()
|
||||
|
||||
runs ->
|
||||
expect(getLeftPosition(wrapGuide.firstChild)).toBeGreaterThan(initial)
|
||||
expect(wrapGuide.firstChild).toBeVisible()
|
||||
|
||||
it "updates the wrap guide position for hidden editors when they become visible", ->
|
||||
initial = getLeftPosition(wrapGuide.firstChild)
|
||||
expect(initial).toBeGreaterThan(0)
|
||||
|
||||
waitsForPromise ->
|
||||
atom.workspace.open()
|
||||
|
||||
runs ->
|
||||
fontSize = atom.config.get("editor.fontSize")
|
||||
atom.config.set("editor.fontSize", fontSize + 10)
|
||||
atom.workspace.getActivePane().activatePreviousItem()
|
||||
|
||||
waitsForPromise ->
|
||||
editorElement.getComponent().getNextUpdatePromise()
|
||||
|
||||
runs ->
|
||||
expect(getLeftPosition(wrapGuide.firstChild)).toBeGreaterThan(initial)
|
||||
expect(wrapGuide.firstChild).toBeVisible()
|
||||
|
||||
describe "when the column config changes", ->
|
||||
it "updates the wrap guide position", ->
|
||||
initial = getLeftPosition(wrapGuide.firstChild)
|
||||
expect(initial).toBeGreaterThan(0)
|
||||
column = atom.config.get("editor.preferredLineLength")
|
||||
atom.config.set("editor.preferredLineLength", column + 10)
|
||||
expect(getLeftPosition(wrapGuide.firstChild)).toBeGreaterThan(initial)
|
||||
expect(wrapGuide).toBeVisible()
|
||||
|
||||
describe "when the preferredLineLength changes", ->
|
||||
it "updates the wrap guide positions", ->
|
||||
initial = [10, 15, 20, 30]
|
||||
atom.config.set 'wrap-guide.columns', initial,
|
||||
scopeSelector: ".#{editor.getGrammar().scopeName}"
|
||||
waitsForPromise ->
|
||||
editorElement.getComponent().getNextUpdatePromise()
|
||||
|
||||
runs ->
|
||||
atom.config.set 'editor.preferredLineLength', 15,
|
||||
scopeSelector: ".#{editor.getGrammar().scopeName}"
|
||||
waitsForPromise ->
|
||||
editorElement.getComponent().getNextUpdatePromise()
|
||||
|
||||
runs ->
|
||||
columns = atom.config.get('wrap-guide.columns', scope: editor.getRootScopeDescriptor())
|
||||
expect(columns.length).toBe(2)
|
||||
expect(columns[0]).toBe(10)
|
||||
expect(columns[1]).toBe(15)
|
||||
|
||||
describe "when the columns config changes", ->
|
||||
it "updates the wrap guide positions", ->
|
||||
initial = getLeftPositions(wrapGuide.children)
|
||||
expect(initial.length).toBe(1)
|
||||
expect(initial[0]).toBeGreaterThan(0)
|
||||
|
||||
columns = [10, 20, 30]
|
||||
atom.config.set("wrap-guide.columns", columns)
|
||||
waitsForPromise ->
|
||||
editorElement.getComponent().getNextUpdatePromise()
|
||||
|
||||
runs ->
|
||||
positions = getLeftPositions(wrapGuide.children)
|
||||
expect(positions.length).toBe(columns.length)
|
||||
expect(positions[0]).toBeGreaterThan(0)
|
||||
expect(positions[1]).toBeGreaterThan(positions[0])
|
||||
expect(positions[2]).toBeGreaterThan(positions[1])
|
||||
expect(wrapGuide).toBeVisible()
|
||||
|
||||
it "updates the preferredLineLength", ->
|
||||
initial = atom.config.get('editor.preferredLineLength', scope: editor.getRootScopeDescriptor())
|
||||
atom.config.set("wrap-guide.columns", [initial, initial + 10])
|
||||
waitsForPromise ->
|
||||
editorElement.getComponent().getNextUpdatePromise()
|
||||
|
||||
runs ->
|
||||
length = atom.config.get('editor.preferredLineLength', scope: editor.getRootScopeDescriptor())
|
||||
expect(length).toBe(initial + 10)
|
||||
|
||||
it "keeps guide positions unique and in ascending order", ->
|
||||
initial = getLeftPositions(wrapGuide.children)
|
||||
expect(initial.length).toBe(1)
|
||||
expect(initial[0]).toBeGreaterThan(0)
|
||||
|
||||
reverseColumns = [30, 20, 10]
|
||||
columns = [reverseColumns[reverseColumns.length - 1], reverseColumns..., reverseColumns[0]]
|
||||
uniqueColumns = uniqueAscending(columns)
|
||||
expect(uniqueColumns.length).toBe(3)
|
||||
expect(uniqueColumns[0]).toBeGreaterThan(0)
|
||||
expect(uniqueColumns[1]).toBeGreaterThan(uniqueColumns[0])
|
||||
expect(uniqueColumns[2]).toBeGreaterThan(uniqueColumns[1])
|
||||
|
||||
atom.config.set("wrap-guide.columns", columns)
|
||||
waitsForPromise ->
|
||||
editorElement.getComponent().getNextUpdatePromise()
|
||||
|
||||
runs ->
|
||||
positions = getLeftPositions(wrapGuide.children)
|
||||
expect(positions.length).toBe(uniqueColumns.length)
|
||||
expect(positions[0]).toBeGreaterThan(0)
|
||||
expect(positions[1]).toBeGreaterThan(positions[0])
|
||||
expect(positions[2]).toBeGreaterThan(positions[1])
|
||||
expect(wrapGuide).toBeVisible()
|
||||
|
||||
describe "when the editor's scroll left changes", ->
|
||||
it "updates the wrap guide position to a relative position on screen", ->
|
||||
editor.setText("a long line which causes the editor to scroll")
|
||||
editorElement.style.width = "100px"
|
||||
|
||||
waitsFor -> editorElement.component.getMaxScrollLeft() > 10
|
||||
|
||||
runs ->
|
||||
initial = getLeftPosition(wrapGuide.firstChild)
|
||||
expect(initial).toBeGreaterThan(0)
|
||||
editorElement.setScrollLeft(10)
|
||||
expect(getLeftPosition(wrapGuide.firstChild)).toBe(initial - 10)
|
||||
expect(wrapGuide.firstChild).toBeVisible()
|
||||
|
||||
describe "when the editor's grammar changes", ->
|
||||
it "updates the wrap guide position", ->
|
||||
atom.config.set('editor.preferredLineLength', 20, scopeSelector: '.source.js')
|
||||
initial = getLeftPosition(wrapGuide.firstChild)
|
||||
expect(initial).toBeGreaterThan(0)
|
||||
expect(wrapGuide).toBeVisible()
|
||||
|
||||
editor.setGrammar(atom.grammars.grammarForScopeName('text.plain.null-grammar'))
|
||||
expect(getLeftPosition(wrapGuide.firstChild)).toBeGreaterThan(initial)
|
||||
expect(wrapGuide).toBeVisible()
|
||||
|
||||
it 'listens for preferredLineLength updates for the new grammar', ->
|
||||
editor.setGrammar(atom.grammars.grammarForScopeName('source.coffee'))
|
||||
initial = getLeftPosition(wrapGuide.firstChild)
|
||||
atom.config.set('editor.preferredLineLength', 20, scopeSelector: '.source.coffee')
|
||||
expect(getLeftPosition(wrapGuide.firstChild)).toBeLessThan(initial)
|
||||
|
||||
it 'listens for wrap-guide.enabled updates for the new grammar', ->
|
||||
editor.setGrammar(atom.grammars.grammarForScopeName('source.coffee'))
|
||||
expect(wrapGuide).toBeVisible()
|
||||
atom.config.set('wrap-guide.enabled', false, scopeSelector: '.source.coffee')
|
||||
expect(wrapGuide).not.toBeVisible()
|
||||
|
||||
describe 'scoped config', ->
|
||||
it '::getDefaultColumn returns the scope-specific column value', ->
|
||||
atom.config.set('editor.preferredLineLength', 132, scopeSelector: '.source.js')
|
||||
|
||||
expect(wrapGuide.getDefaultColumn()).toBe 132
|
||||
|
||||
it 'updates the guide when the scope-specific column changes', ->
|
||||
initial = getLeftPosition(wrapGuide.firstChild)
|
||||
column = atom.config.get('editor.preferredLineLength', scope: editor.getRootScopeDescriptor())
|
||||
atom.config.set('editor.preferredLineLength', column + 10, scope: '.source.js')
|
||||
expect(getLeftPosition(wrapGuide.firstChild)).toBeGreaterThan(initial)
|
||||
|
||||
it 'updates the guide when wrap-guide.enabled is set to false', ->
|
||||
expect(wrapGuide).toBeVisible()
|
||||
|
||||
atom.config.set('wrap-guide.enabled', false, scopeSelector: '.source.js')
|
||||
|
||||
expect(wrapGuide).not.toBeVisible()
|
48
packages/wrap-guide/spec/wrap-guide-spec.js
Normal file
48
packages/wrap-guide/spec/wrap-guide-spec.js
Normal file
@ -0,0 +1,48 @@
|
||||
const {getWrapGuides, getLeftPosition} = require('./helpers')
|
||||
|
||||
const {it, fit, ffit, afterEach, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
|
||||
|
||||
describe('Wrap Guide', () => {
|
||||
let editor, editorElement, wrapGuide = []
|
||||
|
||||
beforeEach(async () => {
|
||||
await atom.packages.activatePackage('wrap-guide')
|
||||
|
||||
editor = await atom.workspace.open('sample.js')
|
||||
editorElement = editor.getElement()
|
||||
wrapGuide = editorElement.querySelector('.wrap-guide-container')
|
||||
|
||||
jasmine.attachToDOM(atom.views.getView(atom.workspace))
|
||||
})
|
||||
|
||||
describe('package activation', () => {
|
||||
it('appends a wrap guide to all existing and new editors', () => {
|
||||
expect(atom.workspace.getTextEditors().length).toBe(1)
|
||||
expect(getWrapGuides().length).toBe(1)
|
||||
expect(getLeftPosition(getWrapGuides()[0])).toBeGreaterThan(0)
|
||||
|
||||
atom.workspace.getActivePane().splitRight({copyActiveItem: true})
|
||||
expect(atom.workspace.getTextEditors().length).toBe(2)
|
||||
expect(getWrapGuides().length).toBe(2)
|
||||
expect(getLeftPosition(getWrapGuides()[0])).toBeGreaterThan(0)
|
||||
expect(getLeftPosition(getWrapGuides()[1])).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('positions the guide at the configured column', () => {
|
||||
width = editor.getDefaultCharWidth() * wrapGuide.getDefaultColumn()
|
||||
expect(width).toBeGreaterThan(0)
|
||||
expect(Math.abs(getLeftPosition(wrapGuide.firstChild) - width)).toBeLessThan(1)
|
||||
expect(wrapGuide.firstChild).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
describe('package deactivation', () => {
|
||||
beforeEach(async () => {
|
||||
await atom.packages.deactivatePackage('wrap-guide')
|
||||
})
|
||||
|
||||
it('disposes of all wrap guides', () => {
|
||||
expect(getWrapGuides().length).toBe(0)
|
||||
})
|
||||
})
|
||||
})
|
14
packages/wrap-guide/styles/wrap-guide.less
Normal file
14
packages/wrap-guide/styles/wrap-guide.less
Normal file
@ -0,0 +1,14 @@
|
||||
@import "syntax-variables";
|
||||
|
||||
atom-text-editor {
|
||||
.wrap-guide {
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
background-color: @syntax-wrap-guide-color;
|
||||
-webkit-transform: translateZ(0);
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
2
ppm
2
ppm
@ -1 +1 @@
|
||||
Subproject commit 4645ba2905747897b02f56d1a09ca9b3a60a6b8b
|
||||
Subproject commit 915cbf6e5f9ea1141ef5dcaf86343237e17bf380
|
24
resources/linux/dev.pulsar_edit.Pulsar.metainfo.xml
Normal file
24
resources/linux/dev.pulsar_edit.Pulsar.metainfo.xml
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop-application">
|
||||
<id>dev.pulsar_edit.Pulsar</id>
|
||||
<name>Pulsar</name>
|
||||
<developer_name>Pulsar Edit</developer_name>
|
||||
<summary>A Community-led Hyper-Hackable Text Editor</summary>
|
||||
<metadata_license>MIT</metadata_license>
|
||||
<project_license>MIT</project_license>
|
||||
<url type="homepage">https://pulsar-edit.dev</url>
|
||||
<url type="bugtracker">https://github.com/pulsar-edit/pulsar/issues/</url>
|
||||
<update_contact>admin@pulsar-edit.dev</update_contact>
|
||||
<description>
|
||||
<p>Pulsar aims to not only reach feature parity with the original Atom, but to bring Pulsar into the 21st century by updating the underlying architecture, and supporting modern features.</p>
|
||||
</description>
|
||||
<screenshots>
|
||||
<screenshot>
|
||||
<image type="source">https://raw.githubusercontent.com/pulsar-edit/pulsar/master/resources/readme.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<kudos>
|
||||
<kudo>HiDpiIcon</kudo>
|
||||
</kudos>
|
||||
<content_rating type="oars-1.1" />
|
||||
</component>
|
@ -182,6 +182,9 @@ let options = {
|
||||
"icon": icnsIcon,
|
||||
"category": "public.app-category.developer-tools",
|
||||
"minimumSystemVersion": "10.8",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "resources/mac/entitlements.plist",
|
||||
"entitlementsInherit": "resources/mac/entitlements.plist",
|
||||
"extendInfo": {
|
||||
// This contains extra values that will be inserted into the App's plist
|
||||
"CFBundleExecutable": "Pulsar",
|
||||
@ -195,6 +198,9 @@ let options = {
|
||||
]
|
||||
},
|
||||
},
|
||||
"dmg": {
|
||||
"sign": false
|
||||
},
|
||||
"win": {
|
||||
"icon": icoIcon,
|
||||
"extraResources": [
|
||||
@ -227,6 +233,7 @@ let options = {
|
||||
},
|
||||
"extraMetadata": {
|
||||
},
|
||||
"afterSign": "script/mac-notarise.js",
|
||||
"asarUnpack": [
|
||||
"node_modules/github/bin/*",
|
||||
"node_modules/github/lib/*", // Resolves Error in console
|
||||
|
48
script/mac-notarise.js
Normal file
48
script/mac-notarise.js
Normal file
@ -0,0 +1,48 @@
|
||||
const { notarize } = require("@electron/notarize");
|
||||
|
||||
// https://kilianvalkhof.com/2019/electron/notarizing-your-electron-application/
|
||||
|
||||
/**
|
||||
* @param {import("electron-builder").AfterPackContext} ctx
|
||||
*/
|
||||
exports.default = async function notarizing(ctx) {
|
||||
if (ctx.electronPlatformName !== "darwin") return;
|
||||
|
||||
const appleId = process.env.APPLEID;
|
||||
const appleIdPassword = process.env.APPLEID_PASSWORD;
|
||||
const teamId = process.env.TEAM_ID;
|
||||
const appname = ctx.packager.appInfo.productFilename;
|
||||
|
||||
if (!appleId || !appleIdPassword) {
|
||||
console.error("environment variables APPLEID and APPLEID_PASSWORD are not both present, skipping notarisation");
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {Parameters<typeof notarize>[0]} */
|
||||
let notarise_args = {
|
||||
appBundleId: "dev.pulsar-edit.pulsar",
|
||||
appPath: `${ctx.appOutDir}/${appname}.app`,
|
||||
appleId,
|
||||
appleIdPassword
|
||||
};
|
||||
|
||||
if (!teamId) {
|
||||
console.log("no TEAM_ID, using (legacy) altool");
|
||||
notarise_args = {
|
||||
...notarise_args,
|
||||
tool: "legacy"
|
||||
}
|
||||
} else {
|
||||
console.log("using notarytool");
|
||||
|
||||
notarise_args = {
|
||||
...notarise_args,
|
||||
tool: "notarytool",
|
||||
teamId
|
||||
};
|
||||
}
|
||||
|
||||
require("debug").enable("electron-notarize");
|
||||
|
||||
return await notarize(notarise_args);
|
||||
}
|
49
yarn.lock
49
yarn.lock
@ -1393,6 +1393,14 @@
|
||||
global-agent "^3.0.0"
|
||||
global-tunnel-ng "^2.7.1"
|
||||
|
||||
"@electron/notarize@^1.2.3":
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@electron/notarize/-/notarize-1.2.3.tgz#38056a629e5a0b5fd56c975c4828c0f74285b644"
|
||||
integrity sha512-9oRzT56rKh5bspk3KpAVF8lPKHYQrBnRwcgiOeR0hdilVEQmszDaAu0IPCPrwwzJN0ugNs0rRboTreHMt/6mBQ==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
fs-extra "^9.0.1"
|
||||
|
||||
"@electron/universal@1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-1.2.1.tgz#3c2c4ff37063a4e9ab1e6ff57db0bc619bc82339"
|
||||
@ -3812,7 +3820,7 @@ dompurify@2.0.17:
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.0.17.tgz#505ffa126a580603df4007e034bdc9b6b738668e"
|
||||
integrity sha512-nNwwJfW55r8akD8MSFz6k75bzyT2y6JEa1O3JrZFBf+Y5R9JXXU4OsRl0B9hKoPgHTw2b7ER5yJ5Md97MMUJPg==
|
||||
|
||||
dompurify@^1.0.2, dompurify@^1.0.3:
|
||||
dompurify@^1.0.3:
|
||||
version "1.0.11"
|
||||
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-1.0.11.tgz#fe0f4a40d147f7cebbe31a50a1357539cfc1eb4d"
|
||||
integrity sha512-XywCTXZtc/qCX3iprD1pIklRVk/uhl8BKpkTxr+ZyMVUzSUg7wkQXRBp/euJ5J5moa1QvfpvaPQVP71z1O59dQ==
|
||||
@ -4957,9 +4965,9 @@ github-from-package@0.0.0:
|
||||
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
|
||||
integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
|
||||
|
||||
"github@https://codeload.github.com/pulsar-edit/github/tar.gz/refs/tags/v0.36.14-pretranspiled-take-2":
|
||||
version "0.36.14"
|
||||
resolved "https://codeload.github.com/pulsar-edit/github/tar.gz/refs/tags/v0.36.14-pretranspiled-take-2#22158525f8801ecbb084e23ea45ee92ba3d3f9e1"
|
||||
"github@https://codeload.github.com/pulsar-edit/github/tar.gz/refs/tags/v0.36.15-pretranspiled":
|
||||
version "0.36.15"
|
||||
resolved "https://codeload.github.com/pulsar-edit/github/tar.gz/refs/tags/v0.36.15-pretranspiled#6d8f680fb7f337c3ddf0127fe24b9cc6e77a7618"
|
||||
dependencies:
|
||||
"@atom/babel-plugin-chai-assert-async" "1.0.0"
|
||||
"@atom/babel7-transpiler" "1.0.0-1"
|
||||
@ -6672,15 +6680,14 @@ markdown-it@^12.3.2:
|
||||
mdurl "^1.0.1"
|
||||
uc.micro "^1.0.5"
|
||||
|
||||
"markdown-preview@https://codeload.github.com/atom/markdown-preview/legacy.tar.gz/refs/tags/v0.160.2":
|
||||
"markdown-preview@file:./packages/markdown-preview":
|
||||
version "0.160.2"
|
||||
resolved "https://codeload.github.com/atom/markdown-preview/legacy.tar.gz/refs/tags/v0.160.2#6d6f4075ea5b5ec5a683104b12f2e91ad33fa392"
|
||||
dependencies:
|
||||
cheerio "^1.0.0-rc.3"
|
||||
dompurify "^1.0.2"
|
||||
dompurify "^2.0.17"
|
||||
emoji-images "^0.1.1"
|
||||
fs-plus "^3.0.0"
|
||||
marked "^0.6.2"
|
||||
marked "^0.7.0"
|
||||
underscore-plus "^1.0.0"
|
||||
yaml-front-matter "^4.0.0"
|
||||
|
||||
@ -6694,10 +6701,10 @@ marked@^0.3.6:
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
|
||||
integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
|
||||
|
||||
marked@^0.6.2:
|
||||
version "0.6.3"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.3.tgz#79babad78af638ba4d522a9e715cdfdd2429e946"
|
||||
integrity sha512-Fqa7eq+UaxfMriqzYLayfqAE40WN03jf+zHjT18/uXNuzjq3TY0XTbrAoPeqSJrAmPz11VuUA+kBPYOhHt9oOQ==
|
||||
marked@^0.7.0:
|
||||
version "0.7.0"
|
||||
resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e"
|
||||
integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==
|
||||
|
||||
marked@^4.0.10:
|
||||
version "4.2.2"
|
||||
@ -7583,9 +7590,9 @@ parse5-htmlparser2-tree-adapter@^7.0.0:
|
||||
parse5 "^7.0.0"
|
||||
|
||||
parse5@^7.0.0:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.1.tgz#4649f940ccfb95d8754f37f73078ea20afe0c746"
|
||||
integrity sha512-kwpuwzB+px5WUg9pyK0IcK/shltJN5/OVhQagxhCQNtT9Y9QRZqNY2e1cmbu/paRh5LMnz/oVTVLBpjFmMZhSg==
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32"
|
||||
integrity sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==
|
||||
dependencies:
|
||||
entities "^4.4.0"
|
||||
|
||||
@ -8687,9 +8694,9 @@ smart-buffer@^4.0.2, smart-buffer@^4.2.0:
|
||||
resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
|
||||
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
|
||||
|
||||
"snippets@https://github.com/pulsar-edit/snippets.git#fe00fd6":
|
||||
version "1.6.1"
|
||||
resolved "https://github.com/pulsar-edit/snippets.git#fe00fd6933fa33f819d14cdd6938d538d25ba1dd"
|
||||
"snippets@github:pulsar-edit/snippets#bb00f909c6c645b173f27346875d8fa0c7af09f7":
|
||||
version "1.7.0"
|
||||
resolved "https://codeload.github.com/pulsar-edit/snippets/tar.gz/bb00f909c6c645b173f27346875d8fa0c7af09f7"
|
||||
dependencies:
|
||||
async "~0.2.6"
|
||||
atom-select-list "^0.7.0"
|
||||
@ -9038,9 +9045,8 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
|
||||
|
||||
"styleguide@https://codeload.github.com/atom/styleguide/legacy.tar.gz/refs/tags/v0.49.12":
|
||||
"styleguide@file:./packages/styleguide":
|
||||
version "0.49.12"
|
||||
resolved "https://codeload.github.com/atom/styleguide/legacy.tar.gz/refs/tags/v0.49.12#d2c09228e5da99017034227b8bc571fea56bc63b"
|
||||
dependencies:
|
||||
atom-select-list "^0.7.0"
|
||||
dedent "^0.7.0"
|
||||
@ -10092,9 +10098,8 @@ wrap-ansi@^7.0.0:
|
||||
string-width "^4.1.0"
|
||||
strip-ansi "^6.0.0"
|
||||
|
||||
"wrap-guide@https://codeload.github.com/atom/wrap-guide/legacy.tar.gz/refs/tags/v0.41.0":
|
||||
"wrap-guide@file:./packages/wrap-guide":
|
||||
version "0.41.0"
|
||||
resolved "https://codeload.github.com/atom/wrap-guide/legacy.tar.gz/refs/tags/v0.41.0#bd23ce8c207d589c742bd324135de81b6eb7ec02"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
|
Loading…
Reference in New Issue
Block a user